diff options
-rw-r--r-- | release/scripts/startup/bl_ui/space_view3d_toolbar.py | 9 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_image.c | 16 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_image_proj.c | 5187 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_intern.h | 3 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_ops.c | 2 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_stroke.c | 3 | ||||
-rw-r--r-- | source/blender/editors/space_view3d/view3d_draw.c | 5 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_scene_types.h | 1 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_sculpt_paint.c | 7 |
10 files changed, 5199 insertions, 35 deletions
diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py index 05c1793b29f..91a55892370 100644 --- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -1114,11 +1114,6 @@ class VIEW3D_PT_tools_projectpaint(View3DPanel, Panel): brush = context.tool_settings.image_paint.brush return (brush is not None) - def draw_header(self, context): - ipaint = context.tool_settings.image_paint - - self.layout.prop(ipaint, "use_projection", text="") - def draw(self, context): layout = self.layout @@ -1127,15 +1122,12 @@ class VIEW3D_PT_tools_projectpaint(View3DPanel, Panel): toolsettings = context.tool_settings ipaint = toolsettings.image_paint settings = toolsettings.image_paint - use_projection = ipaint.use_projection col = layout.column() - col.active = use_projection col.prop(ipaint, "use_occlude") col.prop(ipaint, "use_backface_culling") row = layout.row() - row.active = (use_projection) row.prop(ipaint, "use_normal_falloff") sub = row.row() @@ -1144,7 +1136,6 @@ class VIEW3D_PT_tools_projectpaint(View3DPanel, Panel): split = layout.split() - split.active = (use_projection) split.prop(ipaint, "use_stencil_layer", text="Stencil") row = split.row() diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index 5bed31e2e52..8e812bc1272 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -43,6 +43,7 @@ set(SRC paint_hide.c paint_image.c paint_image_2d.c + paint_image_proj.c paint_mask.c paint_ops.c paint_stroke.c diff --git a/source/blender/editors/sculpt_paint/paint_image.c b/source/blender/editors/sculpt_paint/paint_image.c index 8cc290aee1f..a99f82058b8 100644 --- a/source/blender/editors/sculpt_paint/paint_image.c +++ b/source/blender/editors/sculpt_paint/paint_image.c @@ -4956,8 +4956,6 @@ typedef struct PaintOperation { ViewContext vc; wmTimer *timer; - - short restore_projection; } PaintOperation; static void paint_redraw(bContext *C, ImagePaintState *s, int texpaint, int final) @@ -5069,12 +5067,7 @@ static int texture_paint_init(bContext *C, wmOperator *op) /* initialize from context */ if (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); + pop->mode = PAINT_MODE_3D_PROJECT; } else { pop->s.sima = CTX_wm_space_image(C); @@ -5239,9 +5232,6 @@ static void paint_exit(bContext *C, wmOperator *op) if (pop->timer) WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), pop->timer); - if (pop->restore_projection) - settings->imapaint.flag &= ~IMAGEPAINT_PROJECT_DISABLE; - settings->imapaint.flag &= ~IMAGEPAINT_DRAWING; imapaint_canvas_free(&pop->s); BKE_brush_painter_free(pop->painter); @@ -5259,10 +5249,10 @@ static void paint_exit(bContext *C, wmOperator *op) if (pop->s.dm_release) pop->s.dm->release(pop->s.dm); } - + paint_redraw(C, &pop->s, pop->mode == PAINT_MODE_3D, 1); undo_paint_push_end(UNDO_PAINT_IMAGE); - + 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) diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.c b/source/blender/editors/sculpt_paint/paint_image_proj.c new file mode 100644 index 00000000000..47654fc32fd --- /dev/null +++ b/source/blender/editors/sculpt_paint/paint_image_proj.c @@ -0,0 +1,5187 @@ +/* + * ***** 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. + * + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 ***** + */ + +/** \file blender/editors/sculpt_paint/paint_image.c + * \ingroup edsculpt + * \brief Functions to paint images in 2D and 3D. + */ + +#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_math.h" +#include "BLI_blenlib.h" +#include "BLI_linklist.h" +#include "BLI_memarena.h" +#include "BLI_threads.h" +#include "BLI_utildefines.h" + +#include "PIL_time.h" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +#include "DNA_brush_types.h" +#include "DNA_mesh_types.h" +#include "DNA_node_types.h" +#include "DNA_object_types.h" + +#include "BKE_camera.h" +#include "BKE_context.h" +#include "BKE_depsgraph.h" +#include "BKE_DerivedMesh.h" +#include "BKE_idprop.h" +#include "BKE_brush.h" +#include "BKE_image.h" +#include "BKE_library.h" +#include "BKE_main.h" +#include "BKE_mesh.h" +#include "BKE_node.h" +#include "BKE_object.h" +#include "BKE_paint.h" +#include "BKE_report.h" +#include "BKE_scene.h" +#include "BKE_colortools.h" + +#include "BKE_tessmesh.h" + +#include "BIF_gl.h" +#include "BIF_glutil.h" + +#include "UI_view2d.h" + +#include "ED_image.h" +#include "ED_screen.h" +#include "ED_sculpt.h" +#include "ED_uvedit.h" +#include "ED_view3d.h" +#include "ED_mesh.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" + +#include "GPU_draw.h" + +#include "IMB_colormanagement.h" + +#include "paint_intern.h" + +/* Defines and Structs */ +/* FTOCHAR as inline function */ +BLI_INLINE unsigned char f_to_char(const float val) +{ + return FTOCHAR(val); +} + + +#define IMAPAINT_CHAR_TO_FLOAT(c) ((c) / 255.0f) + +#define IMAPAINT_FLOAT_RGB_TO_CHAR(c, f) { \ + (c)[0] = f_to_char((f)[0]); \ + (c)[1] = f_to_char((f)[1]); \ + (c)[2] = f_to_char((f)[2]); \ +} (void)0 +#define IMAPAINT_FLOAT_RGBA_TO_CHAR(c, f) { \ + (c)[0] = f_to_char((f)[0]); \ + (c)[1] = f_to_char((f)[1]); \ + (c)[2] = f_to_char((f)[2]); \ + (c)[3] = f_to_char((f)[3]); \ +} (void)0 +#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]); \ +} (void)0 +#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]); \ +} (void)0 + +#define IMAPAINT_FLOAT_RGB_COPY(a, b) copy_v3_v3(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) + +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; + char *warnpackedfile; + char *warnmultifile; + + /* viewport texture paint only, but _not_ project paint */ + Object *ob; + int faceindex; + float uv[2]; + int do_facesel; + + DerivedMesh *dm; + int dm_totface; + int dm_release; + + MFace *dm_mface; + MTFace *dm_mtface; +} ImagePaintState; + +typedef struct ImagePaintPartialRedraw { + int x1, y1, x2, y2; /* XXX, could use 'rcti' */ + int enabled; +} ImagePaintPartialRedraw; + +typedef struct ImagePaintRegion { + int destx, desty; + int srcx, srcy; + int width, height; +} ImagePaintRegion; + +/* 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) + +#define PROJ_SRC_VIEW 1 +#define PROJ_SRC_IMAGE_CAM 2 +#define PROJ_SRC_IMAGE_VIEW 3 + +#define PROJ_VIEW_DATA_ID "view_data" +#define PROJ_VIEW_DATA_SIZE (4 * 4 + 4 * 4 + 3) /* viewmat + winmat + clipsta + clipend + is_ortho */ + + +/* 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.00075f + +/* vert flags */ +#define PROJ_VERT_CULL 1 + +/* 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; + void **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; + int source; /* PROJ_SRC_**** */ + + Brush *brush; + short tool, blend; + Object *ob; + /* end similarities with ImagePaintState */ + + DerivedMesh *dm; + int dm_totface; + int dm_totvert; + int dm_release; + + MVert *dm_mvert; + MFace *dm_mface; + MTFace *dm_mtface; + MTFace *dm_mtface_clone; /* other UV map, use for cloning between layers */ + MTFace *dm_mtface_stencil; + + /* 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 */ +#endif + char *vertFlags; /* store options per vert, now only store if the vert is pointing away from the view */ + 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 pixel_sizeof; /* result of project_paint_pixel_sizeof(), constant per stroke */ + + 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; + int winx, winy; /* from the carea or from the projection render */ + + /* options for projection painting */ + int do_layer_clone; + int do_layer_stencil; + int do_layer_stencil_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 */ + short do_new_shading_nodes; /* cache BKE_scene_use_new_shading_nodes value */ + 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; + bool do_masking; /* use masking during painting. Some operations such as airbrush may disable */ + 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 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; + + /* reproject vars */ + Image *reproject_image; + ImBuf *reproject_ibuf; + + + /* 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 */ + float worldCoSS[3]; + /* 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; + +/* blur, store surrounding colors */ +#define PROJ_PIXEL_SOFTEN_TOT 4 +/* blur picking offset (in screenspace) */ +#define PROJ_PIXEL_SOFTEN_OFS_PX 1.0f + +static const float proj_pixel_soften_v2[PROJ_PIXEL_SOFTEN_TOT][2] = { + {-PROJ_PIXEL_SOFTEN_OFS_PX, 0.0f}, + { 0.0f, -PROJ_PIXEL_SOFTEN_OFS_PX}, + { 0.0f, PROJ_PIXEL_SOFTEN_OFS_PX}, + { PROJ_PIXEL_SOFTEN_OFS_PX, 0.0f}, +}; + +/* Finish projection painting structs */ + +typedef struct UndoImageTile { + struct UndoImageTile *next, *prev; + + char idname[MAX_ID_NAME]; /* name instead of pointer*/ + char ibufname[IB_FILENAME_SIZE]; + + union { + float *fp; + unsigned int *uint; + void *pt; + } rect; + int x, y; + + short source, use_float; + char gen_type; +} UndoImageTile; + +static ImagePaintPartialRedraw imapaintpartial = {0, 0, 0, 0, 0}; + +/* UNDO */ + +static void undo_copy_tile(UndoImageTile *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(float *, tmpibuf->rect_float, tile->rect.fp); + } + else { + SWAP(unsigned int *, tmpibuf->rect, tile->rect.uint); + } + + 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 void *image_undo_push_tile(Image *ima, ImBuf *ibuf, ImBuf **tmpibuf, int x_tile, int y_tile) +{ + ListBase *lb = undo_paint_push_get_list(UNDO_PAINT_IMAGE); + UndoImageTile *tile; + int allocsize; + short use_float = ibuf->rect_float ? 1 : 0; + + for (tile = lb->first; tile; tile = tile->next) + if (tile->x == x_tile && tile->y == y_tile && ima->gen_type == tile->gen_type && ima->source == tile->source) + if (tile->use_float == use_float) + if (strcmp(tile->idname, ima->id.name) == 0 && strcmp(tile->ibufname, ibuf->name) == 0) + return tile->rect.pt; + + if (*tmpibuf == NULL) + *tmpibuf = IMB_allocImBuf(IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE, 32, IB_rectfloat | IB_rect); + + tile = MEM_callocN(sizeof(UndoImageTile), "UndoImageTile"); + BLI_strncpy(tile->idname, ima->id.name, sizeof(tile->idname)); + 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.pt = MEM_mapallocN(allocsize, "UndeImageTile.rect"); + + BLI_strncpy(tile->ibufname, ibuf->name, sizeof(tile->ibufname)); + + tile->gen_type = ima->gen_type; + tile->source = ima->source; + tile->use_float = use_float; + + undo_copy_tile(tile, *tmpibuf, ibuf, 0); + undo_paint_push_count_alloc(UNDO_PAINT_IMAGE, allocsize); + + BLI_addtail(lb, tile); + + return tile->rect.pt; +} + +static void image_undo_restore(bContext *C, ListBase *lb) +{ + Main *bmain = CTX_data_main(C); + Image *ima = NULL; + ImBuf *ibuf, *tmpibuf; + UndoImageTile *tile; + + tmpibuf = IMB_allocImBuf(IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE, 32, + IB_rectfloat | IB_rect); + + for (tile = lb->first; tile; tile = tile->next) { + short use_float; + + /* find image based on name, pointer becomes invalid with global undo */ + if (ima && strcmp(tile->idname, ima->id.name) == 0) { + /* ima is valid */ + } + else { + ima = BLI_findstring(&bmain->image, tile->idname, offsetof(ID, name)); + } + + ibuf = BKE_image_acquire_ibuf(ima, NULL, NULL); + + if (ima && ibuf && strcmp(tile->ibufname, ibuf->name) != 0) { + /* current ImBuf filename was changed, probably current frame + * was changed when paiting on image sequence, rather than storing + * full image user (which isn't so obvious, btw) try to find ImBuf with + * matched file name in list of already loaded images */ + + BKE_image_release_ibuf(ima, ibuf, NULL); + + ibuf = BLI_findstring(&ima->ibufs, tile->ibufname, offsetof(ImBuf, name)); + } + + if (!ima || !ibuf || !(ibuf->rect || ibuf->rect_float)) { + BKE_image_release_ibuf(ima, ibuf, NULL); + continue; + } + + if (ima->gen_type != tile->gen_type || ima->source != tile->source) { + BKE_image_release_ibuf(ima, ibuf, NULL); + continue; + } + + use_float = ibuf->rect_float ? 1 : 0; + + if (use_float != tile->use_float) { + BKE_image_release_ibuf(ima, ibuf, NULL); + continue; + } + + undo_copy_tile(tile, tmpibuf, ibuf, 1); + + GPU_free_image(ima); /* force OpenGL reload */ + if (ibuf->rect_float) + ibuf->userflags |= IB_RECT_INVALID; /* force recreate of char rect */ + if (ibuf->mipmap[0]) + ibuf->userflags |= IB_MIPMAP_INVALID; /* force mipmap recreatiom */ + ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID; + + BKE_image_release_ibuf(ima, ibuf, NULL); + } + + IMB_freeImBuf(tmpibuf); +} + +static void image_undo_free(ListBase *lb) +{ + UndoImageTile *tile; + + for (tile = lb->first; tile; tile = tile->next) + MEM_freeN(tile->rect.pt); +} + +static Image *project_paint_face_image(const ProjPaintState *ps, MTFace *dm_mtface, int face_index) +{ + Image *ima; + + if (ps->do_new_shading_nodes) { /* cached BKE_scene_use_new_shading_nodes result */ + MFace *mf = ps->dm_mface + face_index; + ED_object_get_active_image(ps->ob, mf->mat_nr + 1, &ima, NULL, NULL); + } + else { + ima = dm_mtface[face_index].tpage; + } + + return ima; +} + +/* 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; + } +} + +/* 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 barycentric_weights_v2_persp(const float v1[4], const float v2[4], const float v3[4], const float co[2], float w[3]) +{ + float wtot_inv, wtot; + + w[0] = area_tri_signed_v2(v2, v3, co) / v1[3]; + w[1] = area_tri_signed_v2(v3, v1, co) / v2[3]; + w[2] = area_tri_signed_v2(v1, v2, co) / 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 float VecZDepthOrtho(const float pt[2], + const float v1[3], const float v2[3], const float v3[3], + float w[3]) +{ + barycentric_weights_v2(v1, v2, v3, pt, w); + return (v1[2] * w[0]) + (v2[2] * w[1]) + (v3[2] * w[2]); +} + +static float VecZDepthPersp(const float pt[2], + const float v1[4], const float v2[4], const float v3[4], + float w[3]) +{ + float wtot_inv, wtot; + float w_tmp[3]; + + barycentric_weights_v2_persp(v1, v2, v3, pt, w); + /* for the depth we need the weights to match what + * barycentric_weights_v2 would return, in this case its easiest just to + * undo the 4th axis division and make it unit-sum + * + * don't call barycentric_weights_v2() because our callers expect 'w' + * to be weighted from the perspective */ + w_tmp[0] = w[0] * v1[3]; + w_tmp[1] = w[1] * v2[3]; + w_tmp[2] = w[2] * v3[3]; + + wtot = w_tmp[0] + w_tmp[1] + w_tmp[2]; + + if (wtot != 0.0f) { + wtot_inv = 1.0f / wtot; + + w_tmp[0] = w_tmp[0] * wtot_inv; + w_tmp[1] = w_tmp[1] * wtot_inv; + w_tmp[2] = w_tmp[2] * wtot_inv; + } + else /* dummy values for zero area face */ + w_tmp[0] = w_tmp[1] = w_tmp[2] = 1.0f / 3.0f; + /* done mimicing barycentric_weights_v2() */ + + return (v1[2] * w_tmp[0]) + (v2[2] * w_tmp[1]) + (v3[2] * w_tmp[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 (isect_point_tri_v2(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; + copy_v3_v3(w, w_tmp); + } + } + else if (mf->v4) { + v4 = ps->screenCoords[mf->v4]; + + if (isect_point_tri_v2(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; + copy_v3_v3(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)fmodf(uv[0], 1.0f); + *y = (float)fmodf(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; + Image *ima; + 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) { + interp_v2_v2v2v2(uv, tf->uv[0], tf->uv[1], tf->uv[2], w); + } + else { /* QUAD */ + interp_v2_v2v2v2(uv, tf->uv[0], tf->uv[2], tf->uv[3], w); + } + + ima = project_paint_face_image(ps, ps->dm_mtface, face_index); + ibuf = ima->ibufs.first; /* we must have got the imbuf before getting here */ + if (!ibuf) return 0; + + 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_wrap(ibuf, NULL, rgba_fp, x, y); + } + else { + float rgba_tmp_f[4]; + bilinear_interpolation_color_wrap(ibuf, NULL, rgba_tmp_f, x, y); + IMAPAINT_FLOAT_RGBA_TO_CHAR(rgba, rgba_tmp_f); + } + } + else { + if (rgba) { + bilinear_interpolation_color_wrap(ibuf, rgba, NULL, x, y); + } + else { + unsigned char rgba_tmp[4]; + bilinear_interpolation_color_wrap(ibuf, rgba_tmp, NULL, x, y); + IMAPAINT_CHAR_RGBA_TO_FLOAT(rgba_fp, rgba_tmp); + } + } + } + else { + //xi = (int)((uv[0]*ibuf->x) + 0.5f); + //yi = (int)((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) { + copy_v4_v4(rgba_fp, (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[4], float v2[4], float v3[4], 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 (!isect_point_tri_v2(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[4], float v2[4], float v3[4], + 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) barycentric_weights_v2(v1, v2, v3, pt, w); + else barycentric_weights_v2_persp(v1, v2, v3, pt, w); + } + + /* Test if we're in the clipped area, */ + if (side) interp_v3_v3v3v3(wco, ps->dm_mvert[mf->v1].co, ps->dm_mvert[mf->v3].co, ps->dm_mvert[mf->v4].co, w); + else interp_v3_v3v3v3(wco, ps->dm_mvert[mf->v1].co, ps->dm_mvert[mf->v2].co, ps->dm_mvert[mf->v3].co, w); + + if (!ED_view3d_clipping_test(ps->rv3d, wco, TRUE)) { + 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 doesn'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 */ + const short do_clip = ps->rv3d ? ps->rv3d->rflag & RV3D_CLIPPING : 0; + + /* 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 (do_clip) + 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 don't want to test the other side of the quad */ + if (isect_ret == 0 && mf->v4) { + if (do_clip) + 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 math_geom.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 = fabsf(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 = fabsf(p1[0] - p2[0]); /* yuck, horizontal line, we cant do much here */ + + if (x_diff < 0.000001f) { /* 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. */ +#ifndef PROJ_DEBUG_NOSEAMBLEED +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)fmodf(vec2a[0], 1.0f); + float ya = (float)fmodf(vec2a[1], 1.0f); + + float xb = (float)fmodf(vec2b[0], 1.0f); + float yb = (float)fmodf(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 ((fabsf(xa - xb) < PROJ_GEOM_TOLERANCE) && (fabsf(ya - yb) < PROJ_GEOM_TOLERANCE)) ? 1 : 0; +} +#endif + +/* 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 */ +#ifndef PROJ_DEBUG_NOSEAMBLEED +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); + + minmax_v2v2_v2(min_uv, max_uv, uv1); + minmax_v2v2_v2(min_uv, max_uv, uv2); + minmax_v2v2_v2(min_uv, max_uv, uv3); + if (is_quad) + minmax_v2v2_v2(min_uv, max_uv, uv4); + + 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; +} +#endif + +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--) { + minmax_v2v2_v2(min_uv, max_uv, (*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 indices + * '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; + unsigned 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 indices from face vert order indices */ + 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) { + Image *tpage = project_paint_face_image(ps, ps->dm_mtface, face_index); + Image *orig_tpage = project_paint_face_image(ps, ps->dm_mtface, orig_face); + + /* 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_tpage == 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; +} + +/* 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_inv[2]; + + ibuf_inv[0] = 1.0f / (float)ibuf_x; + ibuf_inv[1] = 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 */ + sub_v2_v2v2(dir1, puv[1], puv[0]); + sub_v2_v2v2(dir2, puv[2], puv[1]); + normalize_v2(dir1); + normalize_v2(dir2); + + if (is_quad) { + sub_v2_v2v2(dir3, puv[3], puv[2]); + sub_v2_v2v2(dir4, puv[0], puv[3]); + normalize_v2(dir3); + normalize_v2(dir4); + } + else { + sub_v2_v2v2(dir3, puv[0], puv[2]); + normalize_v2(dir3); + } + + /* TODO - angle_normalized_v2v2(...) * (M_PI/180.0f) + * This is incorrect. Its already given radians but without it wont work. + * need to look into a fix - campbell */ + if (is_quad) { + a1 = shell_angle_to_dist(angle_normalized_v2v2(dir4, dir1) * ((float)M_PI / 180.0f)); + a2 = shell_angle_to_dist(angle_normalized_v2v2(dir1, dir2) * ((float)M_PI / 180.0f)); + a3 = shell_angle_to_dist(angle_normalized_v2v2(dir2, dir3) * ((float)M_PI / 180.0f)); + a4 = shell_angle_to_dist(angle_normalized_v2v2(dir3, dir4) * ((float)M_PI / 180.0f)); + } + else { + a1 = shell_angle_to_dist(angle_normalized_v2v2(dir3, dir1) * ((float)M_PI / 180.0f)); + a2 = shell_angle_to_dist(angle_normalized_v2v2(dir1, dir2) * ((float)M_PI / 180.0f)); + a3 = shell_angle_to_dist(angle_normalized_v2v2(dir2, dir3) * ((float)M_PI / 180.0f)); + } + + if (is_quad) { + sub_v2_v2v2(no1, dir4, dir1); + sub_v2_v2v2(no2, dir1, dir2); + sub_v2_v2v2(no3, dir2, dir3); + sub_v2_v2v2(no4, dir3, dir4); + normalize_v2(no1); + normalize_v2(no2); + normalize_v2(no3); + normalize_v2(no4); + mul_v2_fl(no1, a1 * scaler); + mul_v2_fl(no2, a2 * scaler); + mul_v2_fl(no3, a3 * scaler); + mul_v2_fl(no4, a4 * scaler); + add_v2_v2v2(outset_uv[0], puv[0], no1); + add_v2_v2v2(outset_uv[1], puv[1], no2); + add_v2_v2v2(outset_uv[2], puv[2], no3); + add_v2_v2v2(outset_uv[3], puv[3], no4); + mul_v2_v2(outset_uv[0], ibuf_inv); + mul_v2_v2(outset_uv[1], ibuf_inv); + mul_v2_v2(outset_uv[2], ibuf_inv); + mul_v2_v2(outset_uv[3], ibuf_inv); + } + else { + sub_v2_v2v2(no1, dir3, dir1); + sub_v2_v2v2(no2, dir1, dir2); + sub_v2_v2v2(no3, dir2, dir3); + normalize_v2(no1); + normalize_v2(no2); + normalize_v2(no3); + mul_v2_fl(no1, a1 * scaler); + mul_v2_fl(no2, a2 * scaler); + mul_v2_fl(no3, a3 * scaler); + add_v2_v2v2(outset_uv[0], puv[0], no1); + add_v2_v2v2(outset_uv[1], puv[1], no2); + add_v2_v2v2(outset_uv[2], puv[2], no3); + + mul_v2_v2(outset_uv[0], ibuf_inv); + mul_v2_v2(outset_uv[1], ibuf_inv); + mul_v2_v2(outset_uv[2], ibuf_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 + + +/* 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]) +{ + barycentric_weights_v2(uv1co, uv2co, uv3co, uv, w); + interp_v3_v3v3v3(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[4], float v2co[4], float v3co[4], /* screenspace coords */ + float uv1co[2], float uv2co[2], float uv3co[2], + float pixelScreenCo[4], + float w[3]) +{ + + float wtot_inv, wtot; + barycentric_weights_v2(uv1co, uv2co, uv3co, uv, 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.0f / 3.0f; /* dummy values for zero area face */ + } + /* done re-weighting */ + + interp_v3_v3v3v3(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]; + } + + interp_v2_v2v2v2(uv_other, uvCo1, uvCo2, uvCo3, (float *)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_wrap(ibuf_other, NULL, rgba_f, x, y); + } + else { /* from char to float */ + bilinear_interpolation_color_wrap(ibuf_other, rgba_ub, NULL, x, y); + } + +} + +/* run this outside project_paint_uvpixel_init since pixels with mask 0 don't need init */ +static 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_stencil) { + /* another UV maps image is masking this one's */ + ImBuf *ibuf_other; + Image *other_tpage = project_paint_face_image(ps, ps->dm_mtface_stencil, face_index); + const MTFace *tf_other = ps->dm_mtface_stencil + face_index; + + if (other_tpage && (ibuf_other = BKE_image_acquire_ibuf(other_tpage, NULL, NULL))) { + /* BKE_image_acquire_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); + } + + BKE_image_release_ibuf(other_tpage, ibuf_other, NULL); + + if (!ps->do_layer_stencil_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]; + float no[3], angle; + if (mf->flag & ME_SMOOTH) { + short *no1, *no2, *no3; + 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_v3(no); + } + else { + /* incase the */ +#if 1 + /* normalizing per pixel isn't optimal, we could cache or check ps->*/ + if (mf->v4) + normal_quad_v3(no, + ps->dm_mvert[mf->v1].co, + ps->dm_mvert[mf->v2].co, + ps->dm_mvert[mf->v3].co, + ps->dm_mvert[mf->v4].co); + else + normal_tri_v3(no, + ps->dm_mvert[mf->v1].co, + ps->dm_mvert[mf->v2].co, + ps->dm_mvert[mf->v3].co); +#else + /* don't use because some modifiers dont have normal data (subsurf for eg) */ + copy_v3_v3(no, (float *)ps->dm->getTessFaceData(ps->dm, face_index, CD_NORMAL)); +#endif + } + + /* now we can use the normal as a mask */ + if (ps->is_ortho) { + angle = angle_normalized_v3v3((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_v3(viewDirPersp); + + angle = angle_normalized_v3v3(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 don't use it. */ + // if (ps->is_airbrush == 0) mask *= BKE_brush_alpha_get(ps->brush); + + return mask; +} + +static int project_paint_pixel_sizeof(const short tool) +{ + if ((tool == PAINT_TOOL_CLONE) || (tool == PAINT_TOOL_SMEAR)) { + return sizeof(ProjPixelClone); + } + else { + return sizeof(ProjPixel); + } +} + + +/* 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 float world_spaceCo[3], + const int side, + const float w[3]) +{ + ProjPixel *projPixel; + + /* 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; + + BLI_assert(ps->pixel_sizeof == project_paint_pixel_sizeof(ps->tool)); + projPixel = (ProjPixel *)BLI_memarena_alloc(arena, ps->pixel_sizeof); + //memset(projPixel, 0, size); + + if (ibuf->rect_float) { + projPixel->pixel.f_pt = 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 don't need them at the moment */ + if (ps->brush->mtex.brush_map_mode == MTEX_MAP_MODE_3D) { + copy_v3_v3(projPixel->worldCoSS, world_spaceCo); + } + + copy_v2_v2(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; + Image *other_tpage = project_paint_face_image(ps, ps->dm_mtface_clone, face_index); + const MTFace *tf_other = ps->dm_mtface_clone + face_index; + + if (other_tpage && (ibuf_other = BKE_image_acquire_ibuf(other_tpage, NULL, NULL))) { + /* BKE_image_acquire_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); + } + } + + BKE_image_release_ibuf(other_tpage, ibuf_other, NULL); + } + else { + if (ibuf->rect_float) { + ((ProjPixelClone *)projPixel)->clonepx.f[3] = 0; + } + else { + ((ProjPixelClone *)projPixel)->clonepx.ch[3] = 0; + } + } + + } + else { + float co[2]; + sub_v2_v2v2(co, projPixel->projCoSS, (float *)ps->cloneOffset); + + /* no need to initialize the bucket, we're only checking buckets faces and for this + * the faces are already 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 (fabsf(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 (fabsf(l1[0] - l2[0]) < PROJ_GEOM_TOLERANCE) { /* this is a single point (or close to)*/ + if (BLI_rctf_isect_pt_v(rect, l1)) { + copy_v2_v2(l1_clip, l1); + copy_v2_v2(l2_clip, l2); + return 1; + } + else { + return 0; + } + } + + copy_v2_v2(l1_clip, l1); + copy_v2_v2(l2_clip, l2); + CLAMP(l1_clip[0], rect->xmin, rect->xmax); + CLAMP(l2_clip[0], rect->xmin, rect->xmax); + return 1; + } + else if (fabsf(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 (fabsf(l1[1] - l2[1]) < PROJ_GEOM_TOLERANCE) { /* this is a single point (or close to)*/ + if (BLI_rctf_isect_pt_v(rect, l1)) { + copy_v2_v2(l1_clip, l1); + copy_v2_v2(l2_clip, l2); + return 1; + } + else { + return 0; + } + } + + copy_v2_v2(l1_clip, l1); + copy_v2_v2(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_rctf_isect_pt_v(rect, l1)) { + copy_v2_v2(l1_clip, l1); + ok1 = 1; + } + + if (BLI_rctf_isect_pt_v(rect, l2)) { + copy_v2_v2(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 don't return hits on adjacent faces */ +#ifndef PROJ_DEBUG_NOSEAMBLEED +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; + + sub_v3_v3v3(insetCos[0], origCos[0], cent); + sub_v3_v3v3(insetCos[1], origCos[1], cent); + sub_v3_v3v3(insetCos[2], origCos[2], cent); + sub_v3_v3v3(insetCos[3], origCos[3], cent); + + mul_v3_fl(insetCos[0], inset); + mul_v3_fl(insetCos[1], inset); + mul_v3_fl(insetCos[2], inset); + mul_v3_fl(insetCos[3], inset); + + add_v3_v3(insetCos[0], cent); + add_v3_v3(insetCos[1], cent); + add_v3_v3(insetCos[2], cent); + add_v3_v3(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; + + sub_v3_v3v3(insetCos[0], origCos[0], cent); + sub_v3_v3v3(insetCos[1], origCos[1], cent); + sub_v3_v3v3(insetCos[2], origCos[2], cent); + + mul_v3_fl(insetCos[0], inset); + mul_v3_fl(insetCos[1], inset); + mul_v3_fl(insetCos[2], inset); + + add_v3_v3(insetCos[0], cent); + add_v3_v3(insetCos[1], cent); + add_v3_v3(insetCos[2], cent); +} +#endif //PROJ_DEBUG_NOSEAMBLEED + +static float len_squared_v2v2_alt(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 len_squared_v2v2 + * 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 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 already 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 0 + if (BLI_rctf_isect_pt_v(bucket_bounds, cent)) + return 1; +#endif + + 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 (len_squared_v2v2_alt(cent, bucket_bounds->xmin, bucket_bounds->ymin) < radius_squared) ? 1 : 0; + } + /* top left test */ + else if (cent[1] > bucket_bounds->ymax) { + return (len_squared_v2v2_alt(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 (len_squared_v2v2_alt(cent, bucket_bounds->xmax, bucket_bounds->ymin) < radius_squared) ? 1 : 0; + } + /* top right test */ + else if (cent[1] > bucket_bounds->ymax) { + return (len_squared_v2v2_alt(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 reliable 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; + barycentric_weights_v2(v1coSS, v2coSS, v3coSS, uv, w); + interp_v2_v2v2v2(bucket_bounds_uv[flip ? 3 : 0], uv1co, uv2co, uv3co, w); + + //uv[0] = bucket_bounds->xmax; // set above + uv[1] = bucket_bounds->ymax; + barycentric_weights_v2(v1coSS, v2coSS, v3coSS, uv, w); + interp_v2_v2v2v2(bucket_bounds_uv[flip ? 2 : 1], uv1co, uv2co, uv3co, w); + + uv[0] = bucket_bounds->xmin; + //uv[1] = bucket_bounds->ymax; // set above + barycentric_weights_v2(v1coSS, v2coSS, v3coSS, uv, w); + interp_v2_v2v2v2(bucket_bounds_uv[flip ? 1 : 2], uv1co, uv2co, uv3co, w); + + //uv[0] = bucket_bounds->xmin; // set above + uv[1] = bucket_bounds->ymin; + barycentric_weights_v2(v1coSS, v2coSS, v3coSS, uv, w); + interp_v2_v2v2v2(bucket_bounds_uv[flip ? 0 : 3], uv1co, uv2co, uv3co, w); +} + +/* same as above but use barycentric_weights_v2_persp */ +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; + barycentric_weights_v2_persp(v1coSS, v2coSS, v3coSS, uv, w); + interp_v2_v2v2v2(bucket_bounds_uv[flip ? 3 : 0], uv1co, uv2co, uv3co, w); + + //uv[0] = bucket_bounds->xmax; // set above + uv[1] = bucket_bounds->ymax; + barycentric_weights_v2_persp(v1coSS, v2coSS, v3coSS, uv, w); + interp_v2_v2v2v2(bucket_bounds_uv[flip ? 2 : 1], uv1co, uv2co, uv3co, w); + + uv[0] = bucket_bounds->xmin; + //uv[1] = bucket_bounds->ymax; // set above + barycentric_weights_v2_persp(v1coSS, v2coSS, v3coSS, uv, w); + interp_v2_v2v2v2(bucket_bounds_uv[flip ? 1 : 2], uv1co, uv2co, uv3co, w); + + //uv[0] = bucket_bounds->xmin; // set above + uv[1] = bucket_bounds->ymin; + barycentric_weights_v2_persp(v1coSS, v2coSS, v3coSS, uv, w); + interp_v2_v2v2v2(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 ((area_tri_v2(pt, v1, v2) + area_tri_v2(pt, v2, v3) + area_tri_v2(pt, v3, v1)) / (area_tri_v2(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 = ((line_point_side_v2(v1coSS, v2coSS, v3coSS) > 0.0f) != (line_point_side_v2(uv1co, uv2co, uv3co) > 0.0f)); + + float bucket_bounds_ss[4][2]; + + /* get the UV space bounding box */ + inside_bucket_flag |= BLI_rctf_isect_pt_v(bucket_bounds, v1coSS); + inside_bucket_flag |= BLI_rctf_isect_pt_v(bucket_bounds, v2coSS) << 1; + inside_bucket_flag |= BLI_rctf_isect_pt_v(bucket_bounds, v3coSS) << 2; + + if (inside_bucket_flag == ISECT_ALL3) { + /* all screenspace points are inside the bucket bounding box, this means we don't need to clip and can simply return the UVs */ + if (flip) { /* facing the back? */ + copy_v2_v2(bucket_bounds_uv[0], uv3co); + copy_v2_v2(bucket_bounds_uv[1], uv2co); + copy_v2_v2(bucket_bounds_uv[2], uv1co); + } + else { + copy_v2_v2(bucket_bounds_uv[0], uv1co); + copy_v2_v2(bucket_bounds_uv[1], uv2co); + copy_v2_v2(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]; + float w[3]; + + /* 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) { copy_v2_v2(isectVCosSS[*tot], bucket_bounds_ss[0]); (*tot)++; } + if (inside_face_flag & ISECT_2) { copy_v2_v2(isectVCosSS[*tot], bucket_bounds_ss[1]); (*tot)++; } + if (inside_face_flag & ISECT_3) { copy_v2_v2(isectVCosSS[*tot], bucket_bounds_ss[2]); (*tot)++; } + if (inside_face_flag & ISECT_4) { copy_v2_v2(isectVCosSS[*tot], bucket_bounds_ss[3]); (*tot)++; } + + if (inside_bucket_flag & ISECT_1) { copy_v2_v2(isectVCosSS[*tot], v1coSS); (*tot)++; } + if (inside_bucket_flag & ISECT_2) { copy_v2_v2(isectVCosSS[*tot], v2coSS); (*tot)++; } + if (inside_bucket_flag & ISECT_3) { copy_v2_v2(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) { copy_v2_v2(isectVCosSS[*tot], v1_clipSS); (*tot)++; } + if ((inside_bucket_flag & ISECT_2) == 0) { copy_v2_v2(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) { copy_v2_v2(isectVCosSS[*tot], v1_clipSS); (*tot)++; } + if ((inside_bucket_flag & ISECT_3) == 0) { copy_v2_v2(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) { copy_v2_v2(isectVCosSS[*tot], v1_clipSS); (*tot)++; } + if ((inside_bucket_flag & ISECT_1) == 0) { copy_v2_v2(isectVCosSS[*tot], v2_clipSS); (*tot)++; } + } + } + + + if ((*tot) < 3) { /* no intersections to speak of */ + *tot = 0; + return; + } + + /* 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] = atan2f(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 (fabsf(isectVCosSS[0][0] - isectVCosSS[(*tot) - 1][0]) < PROJ_GEOM_TOLERANCE && + fabsf(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 (fabsf(isectVCosSS[i - 1][0] - isectVCosSS[i][0]) < PROJ_GEOM_TOLERANCE && + fabsf(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++) { + barycentric_weights_v2(v1coSS, v2coSS, v3coSS, isectVCosSS[i], w); + interp_v2_v2v2v2(bucket_bounds_uv[i], uv1co, uv2co, uv3co, w); + } + } + else { + for (i = 0; i < (*tot); i++) { + barycentric_weights_v2_persp(v1coSS, v2coSS, v3coSS, isectVCosSS[i], w); + interp_v2_v2v2v2(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]).xyz, V(bb[1]).xyz, V(bb[2]).xyz, V(bb[3]).xyz]) + * me.faces.extend([(0,1,2,3),]) + * me.verts.extend([V(uv[0]).xyz, V(uv[1]).xyz, V(uv[2]).xyz]) + * me.faces.extend([(4,5,6),]) + * + * vs = [V(p).xyz 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 (line_point_side_v2 > 0.0f) cases */ +static int IsectPoly2Df(const float pt[2], float uv[][2], const int tot) +{ + int i; + if (line_point_side_v2(uv[tot - 1], uv[0], pt) < 0.0f) + return 0; + + for (i = 1; i < tot; i++) { + if (line_point_side_v2(uv[i - 1], uv[i], pt) < 0.0f) + return 0; + + } + + return 1; +} +static int IsectPoly2Df_twoside(const float pt[2], float uv[][2], const int tot) +{ + int i; + int side = (line_point_side_v2(uv[tot - 1], uv[0], pt) > 0.0f); + + for (i = 1; i < tot; i++) { + if ((line_point_side_v2(uv[i - 1], uv[i], pt) > 0.0f) != side) + return 0; + + } + + return 1; +} + +/* One of the most important function for projection 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, const short clamp_u, const short clamp_v) +{ + /* 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]; + bool do_3d_mapping = ps->brush->mtex.brush_map_mode == MTEX_MAP_MODE_3D; + + 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 = (float)ibuf->x, ibuf_yf = (float)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; + const short do_backfacecull = ps->do_backfacecull; + const short do_clip = ps->rv3d ? ps->rv3d->rflag & RV3D_CLIPPING : 0; + + 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 offsetting all the pixels by 0.5 which causes + * problems when wrapping negative coords */ + xhalfpx = (0.5f + (PROJ_GEOM_TOLERANCE / 3.0f)) / ibuf_xf; + yhalfpx = (0.5f + (PROJ_GEOM_TOLERANCE / 4.0f)) / ibuf_yf; + + /* Note about (PROJ_GEOM_TOLERANCE/x) above... + * Needed to add this offset since UV coords are often quads aligned to pixels. + * In this case pixels can be exactly between 2 triangles causing nasty + * artifacts. + * + * This workaround can be removed and painting will still work on most cases + * but since the first thing most people try is painting onto a quad- better make it work. + */ + + + + 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 0 + if (uv_clip_tot > 6) { + printf("this should never happen! %d\n", uv_clip_tot); + } +#endif + + if (pixel_bounds_array(uv_clip, &bounds_px, ibuf->x, ibuf->y, uv_clip_tot)) { + + if (clamp_u) { + CLAMP(bounds_px.xmin, 0, ibuf->x); + CLAMP(bounds_px.xmax, 0, ibuf->x); + } + + if (clamp_v) { + CLAMP(bounds_px.ymin, 0, ibuf->y); + CLAMP(bounds_px.ymax, 0, ibuf->y); + } + + /* 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 */ + + /* Note about IsectPoly2Df_twoside, checking the face or uv flipping doesnt work, + * could check the poly direction but better to do this */ + if ((do_backfacecull == TRUE && IsectPoly2Df(uv, uv_clip, uv_clip_tot)) || + (do_backfacecull == FALSE && IsectPoly2Df_twoside(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 pity we need to get the worldspace pixel location here */ + if (do_clip || do_3d_mapping) { + interp_v3_v3v3v3(wco, ps->dm_mvert[(*(&mf->v1 + i1))].co, ps->dm_mvert[(*(&mf->v1 + i2))].co, ps->dm_mvert[(*(&mf->v1 + i3))].co, w); + if (do_clip && ED_view3d_clipping_test(ps->rv3d, wco, TRUE)) { + 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 == FALSE) || + !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, wco, 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 *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 = len_v2v2(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 = len_v2v2(vCoSS[fidx1], bucket_clip_edges[0]) / ftot; + fac2 = len_v2v2(vCoSS[fidx1], bucket_clip_edges[1]) / ftot; + + interp_v2_v2v2(seam_subsection[0], tf_uv_pxoffset[fidx1], tf_uv_pxoffset[fidx2], fac1); + interp_v2_v2v2(seam_subsection[1], tf_uv_pxoffset[fidx1], tf_uv_pxoffset[fidx2], fac2); + + interp_v2_v2v2(seam_subsection[2], outset_uv[fidx1], outset_uv[fidx2], fac2); + interp_v2_v2v2(seam_subsection[3], outset_uv[fidx1], outset_uv[fidx2], 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 */ + interp_v3_v3v3(edge_verts_inset_clip[0], insetCos[fidx1], insetCos[fidx2], fac1); + interp_v3_v3v3(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 (isect_point_quad_v2(uv, seam_subsection[0], seam_subsection[1], seam_subsection[2], seam_subsection[3])) { + float fac; + + /* We need to find the closest point along the face edge, + * getting the screen_px_from_*** wont work because our actual location + * is not relevant, since we are outside the face, Use VecLerpf to find + * our location on the side of the face's UV */ +#if 0 + 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); +#endif + + /* Since this is a seam we need to work out where on the line this pixel is */ + //fac = line_point_factor_v2(uv, uv_seam_quad[0], uv_seam_quad[1]); + + fac = line_point_factor_v2(uv, seam_subsection[0], seam_subsection[1]); + if (fac < 0.0f) { copy_v3_v3(pixelScreenCo, edge_verts_inset_clip[0]); } + else if (fac > 1.0f) { copy_v3_v3(pixelScreenCo, edge_verts_inset_clip[1]); } + else { interp_v3_v3v3(pixelScreenCo, edge_verts_inset_clip[0], edge_verts_inset_clip[1], fac); } + + if (!is_ortho) { + pixelScreenCo[3] = 1.0f; + mul_m4_v4((float(*)[4])ps->projectMat, pixelScreenCo); /* cast because of const */ + pixelScreenCo[0] = (float)(ps->winx / 2.0f) + (ps->winx / 2.0f) * pixelScreenCo[0] / pixelScreenCo[3]; + pixelScreenCo[1] = (float)(ps->winy / 2.0f) + (ps->winy / 2.0f) * pixelScreenCo[1] / pixelScreenCo[3]; + pixelScreenCo[2] = pixelScreenCo[2] / pixelScreenCo[3]; /* Use the depth for bucket point occlusion */ + } + + if ((ps->do_occlude == FALSE) || + !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) { +#if 1 + /* get the UV on the line since we want to copy the pixels from there for bleeding */ + float uv_close[2]; + float uv_fac = closest_to_line_v2(uv_close, uv, tf_uv_pxoffset[fidx1], tf_uv_pxoffset[fidx2]); + if (uv_fac < 0.0f) copy_v2_v2(uv_close, tf_uv_pxoffset[fidx1]); + else if (uv_fac > 1.0f) copy_v2_v2(uv_close, tf_uv_pxoffset[fidx2]); + + if (side) { + barycentric_weights_v2(tf_uv_pxoffset[0], tf_uv_pxoffset[2], tf_uv_pxoffset[3], uv_close, w); + } + else { + barycentric_weights_v2(tf_uv_pxoffset[0], tf_uv_pxoffset[1], tf_uv_pxoffset[2], uv_close, w); + } +#else /* this is buggy with quads, don't use for now */ + + /* Cheat, we know where we are along the edge so work out the weights from that */ + uv_fac = fac1 + (uv_fac * (fac2 - fac1)); + + w[0] = w[1] = w[2] = 0.0; + if (side) { + w[fidx1 ? fidx1 - 1 : 0] = 1.0f - uv_fac; + w[fidx2 ? fidx2 - 1 : 0] = uv_fac; + } + else { + w[fidx1] = 1.0f - uv_fac; + w[fidx2] = uv_fac; + } +#endif + } + + /* a pity we need to get the worldspace pixel location here */ + if (do_clip || do_3d_mapping) { + if (side) interp_v3_v3v3v3(wco, ps->dm_mvert[mf->v1].co, ps->dm_mvert[mf->v3].co, ps->dm_mvert[mf->v4].co, w); + else interp_v3_v3v3v3(wco, ps->dm_mvert[mf->v1].co, ps->dm_mvert[mf->v2].co, ps->dm_mvert[mf->v3].co, w); + + if (do_clip && ED_view3d_clipping_test(ps->rv3d, wco, TRUE)) { + 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, wco, 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 indices 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 */ + /* XXX: the offset of 0.5 is always truncated to zero and the offset of 1.5f is always truncated to 1, is this really correct?? - jwilkins */ + bucketMin[0] = (int)((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)((int)(((float)(min[1] - ps->screenMin[1]) / ps->screen_height) * ps->buckets_y) + 0.5f); + + bucketMax[0] = (int)((int)(((float)(max[0] - ps->screenMin[0]) / ps->screen_width) * ps->buckets_x) + 1.5f); + bucketMax[1] = (int)((int)(((float)(max[1] - ps->screenMin[1]) / ps->screen_height) * ps->buckets_y) + 1.5f); + + /* in case 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 = 0; + ImBuf *ibuf = NULL; + Image *tpage_last = NULL, *tpage; + Image *ima = NULL; + + if (ps->image_tot == 1) { + /* Simple loop, no context switching */ + ibuf = ps->projImages[0].ibuf; + ima = ps->projImages[0].ima; + + 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, ima->tpageflag & IMA_CLAMP_U, ima->tpageflag & IMA_CLAMP_V); + } + } + 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 */ + tpage = project_paint_face_image(ps, ps->dm_mtface, face_index); + if (tpage_last != tpage) { + tpage_last = tpage; + + 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; + ima = ps->projImages[image_index].ima; + break; + } + } + } + /* context switching done */ + + project_paint_face_init(ps, thread_index, bucket_index, face_index, image_index, bucket_bounds, ibuf, ima->tpageflag & IMA_CLAMP_U, ima->tpageflag & IMA_CLAMP_V); + } + } + + 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, int bucket_x, int bucket_y, 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 = NULL; + 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_rctf_isect_pt_v(&bucket_bounds, v)) { + 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 (isect_point_quad_v2(p1, v1, v2, v3, v4) || + isect_point_quad_v2(p2, v1, v2, v3, v4) || + isect_point_quad_v2(p3, v1, v2, v3, v4) || + isect_point_quad_v2(p4, v1, v2, v3, v4) || + + /* we can avoid testing v3,v1 because another intersection MUST exist if this intersects */ + (isect_line_line_v2(p1, p2, v1, v2) || isect_line_line_v2(p1, p2, v2, v3) || isect_line_line_v2(p1, p2, v3, v4)) || + (isect_line_line_v2(p2, p3, v1, v2) || isect_line_line_v2(p2, p3, v2, v3) || isect_line_line_v2(p2, p3, v3, v4)) || + (isect_line_line_v2(p3, p4, v1, v2) || isect_line_line_v2(p3, p4, v2, v3) || isect_line_line_v2(p3, p4, v3, v4)) || + (isect_line_line_v2(p4, p1, v1, v2) || isect_line_line_v2(p4, p1, v2, v3) || isect_line_line_v2(p4, p1, v3, v4))) + { + return 1; + } + } + else { + if (isect_point_tri_v2(p1, v1, v2, v3) || + isect_point_tri_v2(p2, v1, v2, v3) || + isect_point_tri_v2(p3, v1, v2, v3) || + isect_point_tri_v2(p4, v1, v2, v3) || + /* we can avoid testing v3,v1 because another intersection MUST exist if this intersects */ + (isect_line_line_v2(p1, p2, v1, v2) || isect_line_line_v2(p1, p2, v2, v3)) || + (isect_line_line_v2(p2, p3, v1, v2) || isect_line_line_v2(p2, p3, v2, v3)) || + (isect_line_line_v2(p3, p4, v1, v2) || isect_line_line_v2(p3, p4, v2, v3)) || + (isect_line_line_v2(p4, p1, v1, v2) || isect_line_line_v2(p4, p1, v2, v3))) + { + return 1; + } + } + + return 0; +} + +/* Add faces to the bucket but don't 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 int face_index) +{ + float min[2], max[2], *vCoSS; + int bucketMin[2], bucketMax[2]; /* for ps->bucketRect indexing */ + int fidx, bucket_x, bucket_y; + 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)]; + minmax_v2v2_v2(min, max, vCoSS); + } 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++) { + if (project_bucket_face_isect(ps, bucket_x, bucket_y, mf)) { + int bucket_index = bucket_x + (bucket_y * ps->buckets_x); + 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 +} + +static int project_paint_view_clip(View3D *v3d, RegionView3D *rv3d, float *clipsta, float *clipend) +{ + int orth = ED_view3d_clip_range_get(v3d, rv3d, clipsta, clipend); + + if (orth) { /* only needed for ortho */ + float fac = 2.0f / ((*clipend) - (*clipsta)); + *clipsta *= fac; + *clipend *= fac; + } + + return orth; +} + +/* 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; /* 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, *tpage; + + /* Face vars */ + MFace *mf; + MTFace *tf; + + int a, i; /* generic looping vars */ + int image_index = -1, face_index; + MVert *mv; + + MemArena *arena; /* at the moment this is just ps->arena_mt[0], but use this to show were not multithreading */ + + const int diameter = 2 * BKE_brush_size_get(ps->scene, ps->brush); + + /* ---- end defines ---- */ + + if (ps->source == PROJ_SRC_VIEW) + ED_view3d_clipping_local(ps->rv3d, ps->ob->obmat); /* faster clipping lookups */ + + /* paint onto the derived mesh */ + + /* Workaround for subsurf selection, try the display mesh first */ + if (ps->source == PROJ_SRC_IMAGE_CAM) { + /* using render mesh, assume only camera was rendered from */ + ps->dm = mesh_create_derived_render(ps->scene, ps->ob, ps->scene->customdata_mask | CD_MASK_MTFACE); + ps->dm_release = TRUE; + } + else if (ps->ob->derivedFinal && CustomData_has_layer(&ps->ob->derivedFinal->faceData, CD_MTFACE)) { + ps->dm = ps->ob->derivedFinal; + ps->dm_release = FALSE; + } + else { + ps->dm = mesh_get_derived_final(ps->scene, ps->ob, ps->scene->customdata_mask | CD_MASK_MTFACE); + ps->dm_release = TRUE; + } + + if (!CustomData_has_layer(&ps->dm->faceData, CD_MTFACE) ) { + + if (ps->dm_release) + ps->dm->release(ps->dm); + + ps->dm = NULL; + return; + } + + ps->dm_mvert = ps->dm->getVertArray(ps->dm); + ps->dm_mface = ps->dm->getTessFaceArray(ps->dm); + ps->dm_mtface = ps->dm->getTessFaceDataArray(ps->dm, CD_MTFACE); + + ps->dm_totvert = ps->dm->getNumVerts(ps->dm); + ps->dm_totface = ps->dm->getNumTessFaces(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 = FALSE; + ps->dm_mtface_clone = NULL; + printf("ACK!\n"); + } + } + + if (ps->do_layer_stencil) { + //int layer_num = CustomData_get_stencil_layer(&ps->dm->faceData, CD_MTFACE); + int layer_num = CustomData_get_stencil_layer(&((Mesh *)ps->ob->data)->fdata, CD_MTFACE); + if (layer_num != -1) + ps->dm_mtface_stencil = CustomData_get_layer_n(&ps->dm->faceData, CD_MTFACE, layer_num); + + if (ps->dm_mtface_stencil == NULL || ps->dm_mtface_stencil == ps->dm_mtface) { + ps->do_layer_stencil = FALSE; + ps->dm_mtface_stencil = NULL; + } + } + + /* when using subsurf or multires, mface arrays are thrown away, we need to keep a copy */ + if (ps->dm->type != DM_TYPE_CDDM) { + ps->dm_mvert = MEM_dupallocN(ps->dm_mvert); + ps->dm_mface = MEM_dupallocN(ps->dm_mface); + /* looks like these are ok for now.*/ +#if 0 + ps->dm_mtface = MEM_dupallocN(ps->dm_mtface); + ps->dm_mtface_clone = MEM_dupallocN(ps->dm_mtface_clone); + ps->dm_mtface_stencil = MEM_dupallocN(ps->dm_mtface_stencil); +#endif + } + + ps->viewDir[0] = 0.0f; + ps->viewDir[1] = 0.0f; + ps->viewDir[2] = 1.0f; + + { + float viewmat[4][4]; + float viewinv[4][4]; + + invert_m4_m4(ps->ob->imat, ps->ob->obmat); + + if (ps->source == PROJ_SRC_VIEW) { + /* normal drawing */ + ps->winx = ps->ar->winx; + ps->winy = ps->ar->winy; + + copy_m4_m4(viewmat, ps->rv3d->viewmat); + copy_m4_m4(viewinv, ps->rv3d->viewinv); + + ED_view3d_ob_project_mat_get(ps->rv3d, ps->ob, ps->projectMat); + + ps->is_ortho = project_paint_view_clip(ps->v3d, ps->rv3d, &ps->clipsta, &ps->clipend); + } + else { + /* re-projection */ + float winmat[4][4]; + float vmat[4][4]; + + ps->winx = ps->reproject_ibuf->x; + ps->winy = ps->reproject_ibuf->y; + + if (ps->source == PROJ_SRC_IMAGE_VIEW) { + /* image stores camera data, tricky */ + IDProperty *idgroup = IDP_GetProperties(&ps->reproject_image->id, 0); + IDProperty *view_data = IDP_GetPropertyFromGroup(idgroup, PROJ_VIEW_DATA_ID); + + float *array = (float *)IDP_Array(view_data); + + /* use image array, written when creating image */ + memcpy(winmat, array, sizeof(winmat)); array += sizeof(winmat) / sizeof(float); + memcpy(viewmat, array, sizeof(viewmat)); array += sizeof(viewmat) / sizeof(float); + ps->clipsta = array[0]; + ps->clipend = array[1]; + ps->is_ortho = array[2] ? 1 : 0; + + invert_m4_m4(viewinv, viewmat); + } + else if (ps->source == PROJ_SRC_IMAGE_CAM) { + Object *cam_ob = ps->scene->camera; + CameraParams params; + + /* viewmat & viewinv */ + copy_m4_m4(viewinv, cam_ob->obmat); + normalize_m4(viewinv); + invert_m4_m4(viewmat, viewinv); + + /* window matrix, clipping and ortho */ + BKE_camera_params_init(¶ms); + BKE_camera_params_from_object(¶ms, cam_ob); + BKE_camera_params_compute_viewplane(¶ms, ps->winx, ps->winy, 1.0f, 1.0f); + BKE_camera_params_compute_matrix(¶ms); + + copy_m4_m4(winmat, params.winmat); + ps->clipsta = params.clipsta; + ps->clipend = params.clipend; + ps->is_ortho = params.is_ortho; + } + + /* same as #ED_view3d_ob_project_mat_get */ + mult_m4_m4m4(vmat, viewmat, ps->ob->obmat); + mult_m4_m4m4(ps->projectMat, winmat, vmat); + } + + + /* viewDir - object relative */ + invert_m4_m4(ps->ob->imat, ps->ob->obmat); + copy_m3_m4(mat, viewinv); + mul_m3_v3(mat, ps->viewDir); + copy_m3_m4(mat, ps->ob->imat); + mul_m3_v3(mat, ps->viewDir); + normalize_v3(ps->viewDir); + + /* viewPos - object relative */ + copy_v3_v3(ps->viewPos, viewinv[3]); + copy_m3_m4(mat, ps->ob->imat); + mul_m3_v3(mat, ps->viewPos); + add_v3_v3(ps->viewPos, ps->ob->imat[3]); + } + + /* 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, mv = ps->dm_mvert; a < ps->dm_totvert; a++, mv++, projScreenCo += 4) { + mul_v3_m4v3(projScreenCo, ps->projectMat, mv->co); + + /* screen space, not clamped */ + projScreenCo[0] = (float)(ps->winx / 2.0f) + (ps->winx / 2.0f) * projScreenCo[0]; + projScreenCo[1] = (float)(ps->winy / 2.0f) + (ps->winy / 2.0f) * projScreenCo[1]; + minmax_v2v2_v2(ps->screenMin, ps->screenMax, projScreenCo); + } + } + else { + for (a = 0, mv = ps->dm_mvert; a < ps->dm_totvert; a++, mv++, projScreenCo += 4) { + copy_v3_v3(projScreenCo, mv->co); + projScreenCo[3] = 1.0f; + + mul_m4_v4(ps->projectMat, projScreenCo); + + if (projScreenCo[3] > ps->clipsta) { + /* screen space, not clamped */ + projScreenCo[0] = (float)(ps->winx / 2.0f) + (ps->winx / 2.0f) * projScreenCo[0] / projScreenCo[3]; + projScreenCo[1] = (float)(ps->winy / 2.0f) + (ps->winy / 2.0f) * projScreenCo[1] / projScreenCo[3]; + projScreenCo[2] = projScreenCo[2] / projScreenCo[3]; /* Use the depth for bucket point occlusion */ + minmax_v2v2_v2(ps->screenMin, ps->screenMax, projScreenCo); + } + 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; + + if (ps->source == PROJ_SRC_VIEW) { +#ifdef PROJ_DEBUG_WINCLIP + CLAMP(ps->screenMin[0], (float)(-diameter), (float)(ps->winx + diameter)); + CLAMP(ps->screenMax[0], (float)(-diameter), (float)(ps->winx + diameter)); + + CLAMP(ps->screenMin[1], (float)(-diameter), (float)(ps->winy + diameter)); + CLAMP(ps->screenMax[1], (float)(-diameter), (float)(ps->winy + diameter)); +#endif + } + else { /* re-projection, use bounds */ + ps->screenMin[0] = 0; + ps->screenMax[0] = (float)(ps->winx); + + ps->screenMin[1] = 0; + ps->screenMax[1] = (float)(ps->winy); + } + + /* 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)diameter) / PROJ_BUCKET_BRUSH_DIV)); + ps->buckets_y = (int)(ps->screen_height / (((float)diameter) / 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, "project paint arena"); + } + + arena = ps->arena_mt[0]; + + if (ps->do_backfacecull && ps->do_mask_normal) { + float viewDirPersp[3]; + + ps->vertFlags = MEM_callocN(sizeof(char) * ps->dm_totvert, "paint-vertFlags"); + + for (a = 0, mv = ps->dm_mvert; a < ps->dm_totvert; a++, mv++) { + normal_short_to_float_v3(no, mv->no); + + if (ps->is_ortho) { + if (angle_normalized_v3v3(ps->viewDir, no) >= ps->normal_angle) { /* 1 vert of this face is towards us */ + ps->vertFlags[a] |= PROJ_VERT_CULL; + } + } + else { + sub_v3_v3v3(viewDirPersp, ps->viewPos, mv->co); + normalize_v3(viewDirPersp); + if (angle_normalized_v3v3(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 + + tpage = project_paint_face_image(ps, ps->dm_mtface, face_index); + + if (tpage && ((((Mesh *)ps->ob->data)->editflag & ME_EDIT_PAINT_FACE_SEL) == 0 || mf->flag & ME_FACE_SEL)) { + + float *v1coSS, *v2coSS, *v3coSS, *v4coSS = NULL; + + 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 (line_point_side_v2(v1coSS, v2coSS, v3coSS) < 0.0f) { + continue; + } + + } + } + + if (tpage_last != tpage) { + + image_index = BLI_linklist_index(image_LinkList, tpage); + + if (image_index == -1 && BKE_image_has_ibuf(tpage, NULL)) { /* MemArena dosnt have an append func */ + BLI_linklist_append(&image_LinkList, tpage); + image_index = ps->image_tot; + ps->image_tot++; + } + + tpage_last = 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, 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; + projIma->touch = 0; + projIma->ibuf = BKE_image_acquire_ibuf(projIma->ima, NULL, NULL); + projIma->partRedrawRect = BLI_memarena_alloc(arena, sizeof(ImagePaintPartialRedraw) * PROJ_BOUNDBOX_SQUARED); + 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]; + copy_v3_v3(projCo, give_cursor(ps->scene, ps->v3d)); + mul_m4_v3(ps->ob->imat, projCo); + + projCo[3] = 1.0f; + mul_m4_v4(ps->projectMat, projCo); + ps->cloneOffset[0] = mouse[0] - ((float)(ps->winx / 2.0f) + (ps->winx / 2.0f) * projCo[0] / projCo[3]); + ps->cloneOffset[1] = mouse[1] - ((float)(ps->winy / 2.0f) + (ps->winy / 2.0f) * projCo[1] / projCo[3]); + } +} + +static void project_paint_end(ProjPaintState *ps) +{ + int a; + ProjPaintImage *projIma; + + /* build undo data from original pixel colors */ + if (U.uiflag & USER_GLOBALUNDO) { + ProjPixel *projPixel; + ImBuf *tmpibuf = NULL, *tmpibuf_float = NULL; + LinkNode *pixel_node; + void *tilerect; + 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 = 0; + + for (a = 0, last_projIma = ps->projImages; a < ps->image_tot; a++, last_projIma++) { + int size = sizeof(void **) * IMAPAINT_TILE_NUMBER(last_projIma->ibuf->x) * IMAPAINT_TILE_NUMBER(last_projIma->ibuf->y); + last_projIma->undoRect = (void **) BLI_memarena_alloc(arena, size); + memset(last_projIma->undoRect, 0, size); + last_projIma->ibuf->userflags |= IB_BITMAPDIRTY; + } + + 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 */ + tilerect = last_projIma->undoRect[tile_index] = image_undo_push_tile(last_projIma->ima, last_projIma->ibuf, is_float ? (&tmpibuf_float) : (&tmpibuf), x_tile, y_tile); + } + else { + tilerect = 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 along the way slows down painting */ + + if (is_float) { + float *rgba_fp = (float *)tilerect + (((projPixel->x_px - x_round) + (projPixel->y_px - y_round) * IMAPAINT_TILE_SIZE)) * 4; + copy_v4_v4(rgba_fp, projPixel->origColor.f); + } + else { + ((unsigned int *)tilerect)[(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 */ + + /* dereference used image buffers */ + for (a = 0, projIma = ps->projImages; a < ps->image_tot; a++, projIma++) { + BKE_image_release_ibuf(projIma->ima, projIma->ibuf, NULL); + } + + BKE_image_release_ibuf(ps->reproject_image, ps->reproject_ibuf, NULL); + + MEM_freeN(ps->screenCoords); + MEM_freeN(ps->bucketRect); + MEM_freeN(ps->bucketFaces); + MEM_freeN(ps->bucketFlags); + +#ifndef PROJ_DEBUG_NOSEAMBLEED + if (ps->seam_bleed_px > 0.0f) { + MEM_freeN(ps->vertFaces); + MEM_freeN(ps->faceSeamFlags); + MEM_freeN(ps->faceSeamUVs); + } +#endif + + if (ps->vertFlags) MEM_freeN(ps->vertFlags); + + for (a = 0; a < ps->thread_tot; a++) { + BLI_memarena_free(ps->arena_mt[a]); + } + + /* copy for subsurf/multires, so throw away */ + if (ps->dm->type != DM_TYPE_CDDM) { + if (ps->dm_mvert) MEM_freeN(ps->dm_mvert); + if (ps->dm_mface) MEM_freeN(ps->dm_mface); + /* looks like these don't need copying */ +#if 0 + if (ps->dm_mtface) MEM_freeN(ps->dm_mtface); + if (ps->dm_mtface_clone) MEM_freeN(ps->dm_mtface_clone); + if (ps->dm_mtface_stencil) MEM_freeN(ps->dm_mtface_stencil); +#endif + } + + if (ps->dm_release) + 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 = 0; + while (tot--) { + pr->x1 = min_ii(pr->x1, pr_other->x1); + pr->y1 = min_ii(pr->y1, pr_other->y1); + + pr->x2 = max_ii(pr->x2, pr_other->x2); + pr->y2 = max_ii(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, true); + 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]) +{ + if (ps->source == PROJ_SRC_VIEW) { + float min_brush[2], max_brush[2]; + const float radius = (float)BKE_brush_size_get(ps->scene, ps->brush); + + /* so we don't have a bucket bounds that is way too small to paint into */ + // if (radius < 1.0f) radius = 1.0f; // this doesn't work yet :/ + + min_brush[0] = mval_f[0] - radius; + min_brush[1] = mval_f[1] - radius; + + max_brush[0] = mval_f[0] + radius; + max_brush[1] = mval_f[1] + radius; + + /* 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]; + } + else { /* reproject: PROJ_SRC_* */ + ps->bucketMin[0] = 0; + ps->bucketMin[1] = 0; + + ps->bucketMax[0] = ps->buckets_x; + ps->bucketMax[1] = ps->buckets_y; + + ps->context_bucket_x = 0; + ps->context_bucket_y = 0; + } + return 1; +} + + +static int project_bucket_iter_next(ProjPaintState *ps, int *bucket_index, rctf *bucket_bounds, const float mval[2]) +{ + const int diameter = 2 * BKE_brush_size_get(ps->scene, ps->brush); + + 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 ((ps->source != PROJ_SRC_VIEW) || + project_bucket_isect_circle(mval, (float)(diameter * diameter), 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; + + struct ImagePool *pool; +} ProjectHandle; + +static void blend_color_mix(unsigned char cp[4], const unsigned char cp1[4], const unsigned char cp2[4], 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; + cp[3] = (mfac * cp1[3] + fac * cp2[3]) / 255; +} + +static void blend_color_mix_float(float cp[4], const float cp1[4], const float cp2[4], const float fac) +{ + const float mfac = 1.0f - 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]; + cp[3] = mfac * cp1[3] + fac * cp2[3]; +} + +static void blend_color_mix_accum(unsigned char cp[4], const unsigned char cp1[4], const unsigned char cp2[4], 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; + const int alpha = cp1[3] + ((fac * cp2[3]) / 255); + + 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; + cp[3] = alpha > 255 ? 255 : alpha; +} +static void blend_color_mix_accum_float(float cp[4], const float cp1[4], const unsigned char cp2[4], const float fac) +{ + const float mfac = 1.0f - fac; + const float alpha = cp1[3] + (fac * (cp2[3] / 255.0f)); + + cp[0] = (mfac * cp1[0] + (fac * (cp2[0] / 255.0f))); + cp[1] = (mfac * cp1[1] + (fac * (cp2[1] / 255.0f))); + cp[2] = (mfac * cp1[2] + (fac * (cp2[2] / 255.0f))); + cp[3] = alpha > 1.0f ? 1.0f : alpha; +} + + +static void do_projectpaint_clone(ProjPaintState *ps, ProjPixel *projPixel, float alpha, float mask) +{ + if (ps->do_masking && 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 alpha, float mask) +{ + if (ps->do_masking && 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 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); */ + blend_color_mix(((ProjPixelClone *)projPixel)->clonepx.ch, projPixel->pixel.ch_pt, rgba_ub, (int)(alpha * mask * 255)); + BLI_linklist_prepend_arena(smearPixels, (void *)projPixel, smearArena); +} + +static void do_projectpaint_smear_f(ProjPaintState *ps, ProjPixel *projPixel, float alpha, float mask, MemArena *smearArena, LinkNode **smearPixels_f, float co[2]) +{ + float rgba[4]; + + if (project_paint_PickColor(ps, co, rgba, NULL, 1) == 0) + return; + + /* (ProjPixelClone *)projPixel)->clonepx.uint = IMB_blend_color(*((unsigned int *)rgba_smear), *((unsigned int *)rgba_ub), (int)(alpha*mask*255), ps->blend); */ + blend_color_mix_float(((ProjPixelClone *)projPixel)->clonepx.f, projPixel->pixel.f_pt, rgba, alpha * mask); + BLI_linklist_prepend_arena(smearPixels_f, (void *)projPixel, smearArena); +} + +/* do_projectpaint_soften for float & byte + */ +static float inv_pow2(float f) +{ + f = 1.0f - f; + f = f * f; + return 1.0f - f; +} + +static void do_projectpaint_soften_f(ProjPaintState *ps, ProjPixel *projPixel, float alpha, float mask, MemArena *softenArena, LinkNode **softenPixels) +{ + unsigned int accum_tot = 0; + unsigned int i; + + float *rgba = projPixel->newColor.f; + + /* sigh, alpha values tend to need to be a _lot_ stronger with blur */ + mask = inv_pow2(mask); + alpha = inv_pow2(alpha); + + /* rather then painting, accumulate surrounding colors */ + zero_v4(rgba); + + for (i = 0; i < PROJ_PIXEL_SOFTEN_TOT; i++) { + float co_ofs[2]; + float rgba_tmp[4]; + sub_v2_v2v2(co_ofs, projPixel->projCoSS, proj_pixel_soften_v2[i]); + if (project_paint_PickColor(ps, co_ofs, rgba_tmp, NULL, TRUE)) { + add_v4_v4(rgba, rgba_tmp); + accum_tot++; + } + } + + if (LIKELY(accum_tot != 0)) { + mul_v4_fl(rgba, 1.0f / (float)accum_tot); + blend_color_mix_float(rgba, projPixel->pixel.f_pt, rgba, alpha); + if (mask < 1.0f) blend_color_mix_float(rgba, projPixel->origColor.f, rgba, mask); + BLI_linklist_prepend_arena(softenPixels, (void *)projPixel, softenArena); + } +} + +static void do_projectpaint_soften(ProjPaintState *ps, ProjPixel *projPixel, float alpha, float mask, MemArena *softenArena, LinkNode **softenPixels) +{ + unsigned int accum_tot = 0; + unsigned int i; + + float rgba[4]; /* convert to byte after */ + + /* sigh, alpha values tend to need to be a _lot_ stronger with blur */ + mask = inv_pow2(mask); + alpha = inv_pow2(alpha); + + /* rather then painting, accumulate surrounding colors */ + zero_v4(rgba); + + for (i = 0; i < PROJ_PIXEL_SOFTEN_TOT; i++) { + float co_ofs[2]; + float rgba_tmp[4]; + sub_v2_v2v2(co_ofs, projPixel->projCoSS, proj_pixel_soften_v2[i]); + if (project_paint_PickColor(ps, co_ofs, rgba_tmp, NULL, TRUE)) { + add_v4_v4(rgba, rgba_tmp); + accum_tot++; + } + } + + if (LIKELY(accum_tot != 0)) { + unsigned char *rgba_ub = projPixel->newColor.ch; + + mul_v4_fl(rgba, 1.0f / (float)accum_tot); + IMAPAINT_FLOAT_RGBA_TO_CHAR(rgba_ub, rgba); + + blend_color_mix(rgba_ub, projPixel->pixel.ch_pt, rgba_ub, (int)(alpha * 255)); + if (mask != 1.0f) blend_color_mix(rgba_ub, projPixel->origColor.ch, rgba_ub, (int)(mask * 255)); + BLI_linklist_prepend_arena(softenPixels, (void *)projPixel, softenArena); + } +} + +BLI_INLINE void rgba_float_to_uchar__mul_v3(unsigned char rgba_ub[4], const float rgba[4], const float rgb[3]) +{ + rgba_ub[0] = f_to_char(rgba[0] * rgb[0]); + rgba_ub[1] = f_to_char(rgba[1] * rgb[1]); + rgba_ub[2] = f_to_char(rgba[2] * rgb[2]); + rgba_ub[3] = f_to_char(rgba[3]); +} + +static void do_projectpaint_draw(ProjPaintState *ps, ProjPixel *projPixel, const float rgba[4], float alpha, float mask) +{ + unsigned char rgba_ub[4]; + + if (ps->is_texbrush) { + rgba_float_to_uchar__mul_v3(rgba_ub, rgba, ps->brush->rgb); + } + else { + IMAPAINT_FLOAT_RGB_TO_CHAR(rgba_ub, ps->brush->rgb); + rgba_ub[3] = 255; + } + + if (ps->do_masking && 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[4], float alpha, float mask, int use_color_correction) +{ + if (ps->is_texbrush) { + /* rgba already holds a texture result here from higher level function */ + if (use_color_correction) { + float rgba_br[3]; + srgb_to_linearrgb_v3_v3(rgba_br, ps->brush->rgb); + mul_v3_v3(rgba, rgba_br); + } + else { + mul_v3_v3(rgba, ps->brush->rgb); + } + } + else { + if (use_color_correction) { + srgb_to_linearrgb_v3_v3(rgba, ps->brush->rgb); + } + else { + copy_v3_v3(rgba, ps->brush->rgb); + } + rgba[3] = 1.0; + } + + if (ps->do_masking && 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; + struct ImagePool *pool = ((ProjectHandle *)ph_v)->pool; + /* Done with args from ProjectHandle */ + + LinkNode *node; + ProjPixel *projPixel; + Brush *brush = ps->brush; + + int last_index = -1; + ProjPaintImage *last_projIma = NULL; + ImagePaintPartialRedraw *last_partial_redraw_cell; + + float rgba[4], alpha, dist_nosqrt, dist; + + float falloff; + int bucket_index; + int is_floatbuf = 0; + int use_color_correction = FALSE; + const short tool = ps->tool; + rctf bucket_bounds; + + /* for smear only */ + float pos_ofs[2] = {0}; + float co[2]; + float mask = 1.0f; /* airbrush wont use mask */ + unsigned short mask_short; + const float radius = (float)BKE_brush_size_get(ps->scene, brush); + const float radius_squared = radius * radius; /* avoid a square root with every dist comparison */ + + short lock_alpha = ELEM(brush->blend, IMB_BLEND_ERASE_ALPHA, IMB_BLEND_ADD_ALPHA) ? 0 : brush->flag & BRUSH_LOCK_ALPHA; + + LinkNode *smearPixels = NULL; + LinkNode *smearPixels_f = NULL; + MemArena *smearArena = NULL; /* mem arena for this brush projection only */ + + LinkNode *softenPixels = NULL; + LinkNode *softenPixels_f = NULL; + MemArena *softenArena = 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, "paint smear arena"); + } + else if (tool == PAINT_TOOL_SOFTEN) { + softenArena = BLI_memarena_new(1 << 16, "paint soften arena"); + } + + /* 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); + } + + if (ps->source != PROJ_SRC_VIEW) { + + /* Re-Projection, simple, no brushes! */ + + for (node = ps->bucketRect[bucket_index]; node; node = node->next) { + projPixel = (ProjPixel *)node->link; + + /* copy of code below */ + 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; + use_color_correction = TRUE; + } + /* end copy */ + + if (is_floatbuf) { + /* re-project buffer is assumed byte - TODO, allow float */ + bicubic_interpolation_color(ps->reproject_ibuf, projPixel->newColor.ch, NULL, + projPixel->projCoSS[0], projPixel->projCoSS[1]); + if (projPixel->newColor.ch[3]) { + mask = ((float)projPixel->mask) / 65535.0f; + blend_color_mix_accum_float(projPixel->pixel.f_pt, projPixel->origColor.f, + projPixel->newColor.ch, (mask * (projPixel->newColor.ch[3] / 255.0f))); + } + } + else { + /* re-project buffer is assumed byte - TODO, allow float */ + bicubic_interpolation_color(ps->reproject_ibuf, projPixel->newColor.ch, NULL, + projPixel->projCoSS[0], projPixel->projCoSS[1]); + if (projPixel->newColor.ch[3]) { + mask = ((float)projPixel->mask) / 65535.0f; + blend_color_mix_accum(projPixel->pixel.ch_pt, projPixel->origColor.ch, + projPixel->newColor.ch, (int)(mask * projPixel->newColor.ch[3])); + } + } + } + } + else { + /* Normal brush painting */ + + for (node = ps->bucketRect[bucket_index]; node; node = node->next) { + + projPixel = (ProjPixel *)node->link; + + dist_nosqrt = len_squared_v2v2(projPixel->projCoSS, pos); + + /*if (dist < radius) {*/ /* correct but uses a sqrtf */ + if (dist_nosqrt <= radius_squared) { + float samplecos[3]; + dist = sqrtf(dist_nosqrt); + + falloff = BKE_brush_curve_strength_clamp(ps->brush, dist, radius); + + if (ps->is_texbrush) { + MTex *mtex = &brush->mtex; + if (mtex->brush_map_mode == MTEX_MAP_MODE_VIEW) { + sub_v2_v2v2(samplecos, projPixel->projCoSS, pos); + } + /* taking 3d copy to account for 3D mapping too. It gets concatenated during sampling */ + else if (mtex->brush_map_mode == MTEX_MAP_MODE_3D) + copy_v3_v3(samplecos, projPixel->worldCoSS); + else + copy_v3_v3(samplecos, projPixel->projCoSS); + } + + if (falloff > 0.0f) { + if (ps->is_texbrush) { + /* note, for clone and smear, we only use the alpha, could be a special function */ + BKE_brush_sample_tex(ps->scene, brush, samplecos, rgba, thread_index, pool); + alpha = rgba[3]; + } + else { + alpha = 1.0f; + } + + if (!ps->do_masking) { + /* for an aurbrush there is no real mask, so just multiply the alpha by it */ + alpha *= falloff * BKE_brush_alpha_get(ps->scene, brush); + 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 = (unsigned short)(projPixel->mask * (BKE_brush_alpha_get(ps->scene, brush) * 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) { + + /* copy of code above */ + 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; + use_color_correction = TRUE; + } + /* end copy */ + + last_partial_redraw_cell = last_projIma->partRedrawRect + projPixel->bb_cell_index; + last_partial_redraw_cell->x1 = min_ii(last_partial_redraw_cell->x1, (int)projPixel->x_px); + last_partial_redraw_cell->y1 = min_ii(last_partial_redraw_cell->y1, (int)projPixel->y_px); + + last_partial_redraw_cell->x2 = max_ii(last_partial_redraw_cell->x2, (int)projPixel->x_px + 1); + last_partial_redraw_cell->y2 = max_ii(last_partial_redraw_cell->y2, (int)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, alpha, mask); /* rgba isn't used for cloning, only alpha */ + } + } + else { + if (((ProjPixelClone *)projPixel)->clonepx.ch[3]) { + do_projectpaint_clone(ps, projPixel, alpha, mask); /* rgba isn't used for cloning, only alpha */ + } + } + break; + case PAINT_TOOL_SMEAR: + sub_v2_v2v2(co, projPixel->projCoSS, pos_ofs); + + if (is_floatbuf) do_projectpaint_smear_f(ps, projPixel, alpha, mask, smearArena, &smearPixels_f, co); + else do_projectpaint_smear(ps, projPixel, alpha, mask, smearArena, &smearPixels, co); + break; + case PAINT_TOOL_SOFTEN: + if (is_floatbuf) do_projectpaint_soften_f(ps, projPixel, alpha, mask, softenArena, &softenPixels_f); + else do_projectpaint_soften(ps, projPixel, alpha, mask, softenArena, &softenPixels); + break; + default: + if (is_floatbuf) do_projectpaint_draw_f(ps, projPixel, rgba, alpha, mask, use_color_correction); + else do_projectpaint_draw(ps, projPixel, rgba, alpha, mask); + break; + } + } + + if (lock_alpha) { + if (is_floatbuf) projPixel->pixel.f_pt[3] = projPixel->origColor.f[3]; + else projPixel->pixel.ch_pt[3] = projPixel->origColor.ch[3]; + } + + /* 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) { + projPixel = node->link; + copy_v4_v4(projPixel->pixel.f_pt, ((ProjPixelClone *)projPixel)->clonepx.f); + } + + BLI_memarena_free(smearArena); + } + else if (tool == PAINT_TOOL_SOFTEN) { + + for (node = softenPixels; node; node = node->next) { /* this wont run for a float image */ + projPixel = node->link; + *projPixel->pixel.uint_pt = projPixel->newColor.uint; + } + + for (node = softenPixels_f; node; node = node->next) { + projPixel = node->link; + copy_v4_v4(projPixel->pixel.f_pt, projPixel->newColor.f); + } + + BLI_memarena_free(softenArena); + } + + return NULL; +} + +static int project_paint_op(void *state, const float lastpos[2], const float pos[2]) +{ + /* First unpack args from the struct */ + ProjPaintState *ps = (ProjPaintState *)state; + int touch_any = 0; + + ProjectHandle handles[BLENDER_MAX_THREADS]; + ListBase threads; + int a, i; + + struct ImagePool *pool; + + if (!project_bucket_iter_init(ps, pos)) { + return 0; + } + + if (ps->thread_tot > 1) + BLI_init_threads(&threads, do_projectpaint_thread, ps->thread_tot); + + pool = BKE_image_pool_new(); + + /* 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; + copy_v2_v2(handles[a].mval, pos); + copy_v2_v2(handles[a].prevmval, lastpos); + + /* thread specific */ + 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); + } + + handles[a].pool = pool; + + 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]); + + + BKE_image_pool_free(pool); + + /* 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_stroke(ProjPaintState *ps, const int prevmval_i[2], const int mval_i[2], float pressure) +{ + int a, redraw; + float pos[2], prev_pos[2]; + + pos[0] = (float)(mval_i[0]); + pos[1] = (float)(mval_i[1]); + + prev_pos[0] = (float)(prevmval_i[0]); + prev_pos[1] = (float)(prevmval_i[1]); + + for (a = 0; a < ps->image_tot; a++) + partial_redraw_array_init(ps->projImages[a].partRedrawRect); + + redraw = project_paint_op(ps, prev_pos, pos) ? 1 : 0; + + if (project_image_refresh_tagged(ps)) + return redraw; + + return 0; +} + +/* Imagepaint Partial Redraw & Dirty Region */ + +static void imapaint_clear_partial_redraw(void) +{ + 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; + 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 = min_ii(imapaintpartial.x1, x); + imapaintpartial.y1 = min_ii(imapaintpartial.y1, y); + imapaintpartial.x2 = max_ii(imapaintpartial.x2, x + w); + imapaintpartial.y2 = max_ii(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++) + image_undo_push_tile(ima, 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 (imapaintpartial.x1 != imapaintpartial.x2 && + imapaintpartial.y1 != imapaintpartial.y2) + { + IMB_partial_display_buffer_update_delayed(ibuf, imapaintpartial.x1, imapaintpartial.y1, + imapaintpartial.x2, imapaintpartial.y2); + } + + if (ibuf->mipmap[0]) + ibuf->userflags |= IB_MIPMAP_INVALID; + + /* todo: should set_tpage create ->rect? */ + if (texpaint || (sima && sima->lock)) { + int w = imapaintpartial.x2 - imapaintpartial.x1; + int h = imapaintpartial.y2 - imapaintpartial.y1; + /* Testing with partial update in uv editor too */ + GPU_paint_update_image(image, imapaintpartial.x1, imapaintpartial.y1, w, h); //!texpaint); + } +} + +/* Image Paint Operations */ + +/* keep these functions in sync */ +static void imapaint_ibuf_rgb_get(ImBuf *ibuf, int x, int y, const short is_torus, float r_rgb[3]) +{ + if (is_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; + IMAPAINT_FLOAT_RGB_COPY(r_rgb, rrgbf); + } + else { + char *rrgb = (char *)ibuf->rect + (ibuf->x * y + x) * 4; + IMAPAINT_CHAR_RGB_TO_FLOAT(r_rgb, rrgb); + } +} +static void imapaint_ibuf_rgb_set(ImBuf *ibuf, int x, int y, const short is_torus, const float rgb[3]) +{ + if (is_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; + IMAPAINT_FLOAT_RGB_COPY(rrgbf, rgb); + } + else { + char *rrgb = (char *)ibuf->rect + (ibuf->x * y + x) * 4; + IMAPAINT_FLOAT_RGB_TO_CHAR(rrgb, rgb); + } +} + +static int imapaint_ibuf_add_if(ImBuf *ibuf, unsigned int x, unsigned int y, float *outrgb, short torus) +{ + float inrgb[3]; + + // XXX: signed unsigned mismatch + if ((x >= (unsigned int)(ibuf->x)) || (y >= (unsigned int)(ibuf->y))) { + if (torus) imapaint_ibuf_rgb_get(ibuf, x, y, 1, inrgb); + else return 0; + } + else { + imapaint_ibuf_rgb_get(ibuf, x, y, 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, const short is_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 (!is_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_rgb_get(ibuf, xi, yi, is_torus, outrgb); + + count += imapaint_ibuf_add_if(ibuf, xi - 1, yi - 1, outrgb, is_torus); + count += imapaint_ibuf_add_if(ibuf, xi - 1, yi, outrgb, is_torus); + count += imapaint_ibuf_add_if(ibuf, xi - 1, yi + 1, outrgb, is_torus); + + count += imapaint_ibuf_add_if(ibuf, xi, yi - 1, outrgb, is_torus); + count += imapaint_ibuf_add_if(ibuf, xi, yi + 1, outrgb, is_torus); + + count += imapaint_ibuf_add_if(ibuf, xi + 1, yi - 1, outrgb, is_torus); + count += imapaint_ibuf_add_if(ibuf, xi + 1, yi, outrgb, is_torus); + count += imapaint_ibuf_add_if(ibuf, xi + 1, yi + 1, outrgb, is_torus); + + mul_v3_fl(outrgb, 1.0f / (float)count); + + /* write into brush buffer */ + xo = out_off[0] + x; + yo = out_off[1] + y; + imapaint_ibuf_rgb_set(ibufb, xo, yo, 0, outrgb); + } + } +} + +static void imapaint_set_region(ImagePaintRegion *region, int destx, int desty, int srcx, int srcy, int width, int height) +{ + region->destx = destx; + region->desty = desty; + region->srcx = srcx; + region->srcy = srcy; + region->width = width; + region->height = height; +} + +static int imapaint_torus_split_region(ImagePaintRegion region[4], ImBuf *dbuf, ImBuf *sbuf) +{ + int destx = region->destx; + int desty = region->desty; + int srcx = region->srcx; + int srcy = region->srcy; + int width = region->width; + int height = region->height; + int origw, origh, w, h, tot = 0; + + /* convert destination and source coordinates to be within image */ + destx = destx % dbuf->x; + if (destx < 0) destx += dbuf->x; + desty = desty % dbuf->y; + if (desty < 0) desty += dbuf->y; + srcx = srcx % sbuf->x; + if (srcx < 0) srcx += sbuf->x; + srcy = srcy % sbuf->y; + if (srcy < 0) srcy += sbuf->y; + + /* clip width of blending area to destination imbuf, to avoid writing the + * same pixel twice */ + origw = w = (width > dbuf->x) ? dbuf->x : width; + origh = h = (height > dbuf->y) ? dbuf->y : height; + + /* clip within image */ + IMB_rectclip(dbuf, sbuf, &destx, &desty, &srcx, &srcy, &w, &h); + imapaint_set_region(®ion[tot++], destx, desty, srcx, srcy, w, h); + + /* do 3 other rects if needed */ + if (w < origw) + imapaint_set_region(®ion[tot++], (destx + w) % dbuf->x, desty, (srcx + w) % sbuf->x, srcy, origw - w, h); + if (h < origh) + imapaint_set_region(®ion[tot++], destx, (desty + h) % dbuf->y, srcx, (srcy + h) % sbuf->y, w, origh - h); + if ((w < origw) && (h < origh)) + imapaint_set_region(®ion[tot++], (destx + w) % dbuf->x, (desty + h) % dbuf->y, (srcx + w) % sbuf->x, (srcy + h) % sbuf->y, origw - w, origh - h); + + return tot; +} + +static void imapaint_lift_smear(ImBuf *ibuf, ImBuf *ibufb, int *pos) +{ + ImagePaintRegion region[4]; + int a, tot; + + imapaint_set_region(region, 0, 0, pos[0], pos[1], ibufb->x, ibufb->y); + tot = imapaint_torus_split_region(region, ibufb, ibuf); + + for (a = 0; a < tot; a++) + IMB_rectblend(ibufb, ibuf, region[a].destx, region[a].desty, + region[a].srcx, region[a].srcy, + region[a].width, region[a].height, 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->planes, ibufb->flags); + + 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, const float pos[2], int ipos[2]) +{ + ipos[0] = (int)floorf((pos[0] - ibufb->x / 2) + 1.0f); + ipos[1] = (int)floorf((pos[1] - ibufb->y / 2) + 1.0f); +} + +/* dosnt run for projection painting + * only the old style painting in the 3d view */ +static int imapaint_paint_op(void *state, ImBuf *ibufb, const float lastpos[2], const float pos[2]) +{ + ImagePaintState *s = ((ImagePaintState *)state); + ImBuf *clonebuf = NULL, *frombuf; + ImagePaintRegion region[4]; + 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]; + int a, tot; + + 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); + } + + frombuf = (clonebuf) ? clonebuf : ibufb; + + if (torus) { + imapaint_set_region(region, bpos[0], bpos[1], 0, 0, frombuf->x, frombuf->y); + tot = imapaint_torus_split_region(region, s->canvas, frombuf); + } + else { + imapaint_set_region(region, bpos[0], bpos[1], 0, 0, frombuf->x, frombuf->y); + tot = 1; + } + + /* blend into canvas */ + for (a = 0; a < tot; a++) { + imapaint_dirty_region(s->image, s->canvas, + region[a].destx, region[a].desty, + region[a].width, region[a].height); + + IMB_rectblend(s->canvas, frombuf, + region[a].destx, region[a].desty, + region[a].srcx, region[a].srcy, + region[a].width, region[a].height, blend); + } + + if (clonebuf) IMB_freeImBuf(clonebuf); + + return 1; +} + + +static int imapaint_canvas_set(ImagePaintState *s, Image *ima) +{ + ImBuf *ibuf = BKE_image_acquire_ibuf(ima, s->sima ? &s->sima->iuser : NULL, NULL); + + /* verify that we can paint and set canvas */ + if (ima == NULL) { + return 0; + } + else 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 (!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_acquire_ibuf(ima, s->sima ? &s->sima->iuser : NULL, NULL); + + if (!ima || !ibuf || !(ibuf->rect || ibuf->rect_float)) { + BKE_image_release_ibuf(ima, ibuf, NULL); + BKE_image_release_ibuf(s->image, s->canvas, NULL); + return 0; + } + + s->clonecanvas = ibuf; + + /* temporarily add float rect for cloning */ + if (s->canvas->rect_float && !s->clonecanvas->rect_float) { + IMB_float_from_rect(s->clonecanvas); + } + else if (!s->canvas->rect_float && !s->clonecanvas->rect) + IMB_rect_from_float(s->clonecanvas); + } + + return 1; +} + +static void imapaint_canvas_free(ImagePaintState *s) +{ + BKE_image_release_ibuf(s->image, s->canvas, NULL); + BKE_image_release_ibuf(s->brush->clone.image, s->clonecanvas, NULL); +} + +static int imapaint_paint_sub_stroke(ImagePaintState *s, BrushPainter *painter, Image *image, float *uv, int update, float pressure) +{ + ImBuf *ibuf = BKE_image_acquire_ibuf(image, s->sima ? &s->sima->iuser : NULL, NULL); + float pos[2]; + int is_data; + + if (!ibuf) + return 0; + + is_data = ibuf->colormanage_flag & IMB_COLORMANAGE_IS_DATA; + + pos[0] = uv[0] * ibuf->x; + pos[1] = uv[1] * ibuf->y; + + BKE_brush_painter_require_imbuf(painter, ((ibuf->rect_float) ? 1 : 0), 0, 0); + + /* OCIO_TODO: float buffers are now always linear, so always use color correction + * this should probably be changed when texture painting color space is supported + */ + if (BKE_brush_painter_paint(painter, imapaint_paint_op, pos, 0, pressure, s, is_data == FALSE)) { + if (update) + imapaint_image_update(s->sima, image, ibuf, false); + BKE_image_release_ibuf(image, ibuf, NULL); + return 1; + } + else { + BKE_image_release_ibuf(image, ibuf, NULL); + return 0; + } +} + +static int imapaint_paint_stroke(ImagePaintState *s, BrushPainter *painter, const int mval[2], float pressure) +{ + float newuv[2]; + int redraw = 0; + + UI_view2d_region_to_view(s->v2d, mval[0], mval[1], &newuv[0], &newuv[1]); + redraw |= imapaint_paint_sub_stroke(s, painter, s->image, newuv, + 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 paint_brush(&settings->imapaint.paint); +} + +static int image_paint_poll(bContext *C) +{ + Object *obact = CTX_data_active_object(C); + + if (!image_paint_brush(C)) + return 0; + + if ((obact && obact->mode & OB_MODE_TEXTURE_PAINT) && CTX_wm_region_view3d(C)) { + return 1; + } + else { + SpaceImage *sima = CTX_wm_space_image(C); + + if (sima) { + ARegion *ar = CTX_wm_region(C); + + if ((sima->mode == SI_MODE_PAINT) && ar->regiontype == RGN_TYPE_WINDOW) { + return 1; + } + } + } + + return 0; +} + +/************************ paint operator ************************/ + +typedef enum PaintMode { + PAINT_MODE_2D, + PAINT_MODE_3D_PROJECT +} PaintMode; + +typedef struct PaintOperation { + PaintMode mode; + + BrushPainter *painter; + ImagePaintState s; + ProjPaintState ps; + + int first; + int prevmouse[2]; + int orig_brush_size; + double starttime; + + ViewContext vc; + wmTimer *timer; +} PaintOperation; + +static void paint_redraw(const bContext *C, ImagePaintState *s, int final) +{ + if (final) { + if (s->image && !(s->sima && s->sima->lock)) + GPU_free_image(s->image); + + /* compositor listener deals with updating */ + WM_event_add_notifier(C, NC_IMAGE | NA_EDITED, s->image); + } + 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); + } +} + +/* initialize project paint settings from context */ +static void project_state_init(bContext *C, Object *ob, ProjPaintState *ps) +{ + Scene *scene = CTX_data_scene(C); + ToolSettings *settings = scene->toolsettings; + + /* brush */ + ps->brush = paint_brush(&settings->imapaint.paint); + if (ps->brush) { + Brush *brush = ps->brush; + ps->tool = brush->imagepaint_tool; + ps->blend = brush->blend; + + /* disable for 3d mapping also because painting on mirrored mesh can create "stripes" */ + ps->do_masking = (brush->flag & BRUSH_AIRBRUSH || brush->mtex.brush_map_mode == MTEX_MAP_MODE_VIEW || + brush->mtex.brush_map_mode == MTEX_MAP_MODE_3D) ? false : true; + ps->is_texbrush = (brush->mtex.tex) ? 1 : 0; + } + else { + /* brush may be NULL*/ + ps->do_masking = false; + ps->is_texbrush = false; + } + + /* sizeof(ProjPixel), since we alloc this a _lot_ */ + ps->pixel_sizeof = project_paint_pixel_sizeof(ps->tool); + BLI_assert(ps->pixel_sizeof >= sizeof(ProjPixel)); + + /* these can be NULL */ + ps->v3d = CTX_wm_view3d(C); + ps->rv3d = CTX_wm_region_view3d(C); + ps->ar = CTX_wm_region(C); + + ps->scene = scene; + ps->ob = ob; /* allow override of active object */ + + /* setup projection painting data */ + ps->do_backfacecull = (settings->imapaint.flag & IMAGEPAINT_PROJECT_BACKFACE) ? 0 : 1; + ps->do_occlude = (settings->imapaint.flag & IMAGEPAINT_PROJECT_XRAY) ? 0 : 1; + ps->do_mask_normal = (settings->imapaint.flag & IMAGEPAINT_PROJECT_FLAT) ? 0 : 1; + ps->do_new_shading_nodes = BKE_scene_use_new_shading_nodes(scene); /* only cache the value */ + + if (ps->tool == PAINT_TOOL_CLONE) + ps->do_layer_clone = (settings->imapaint.flag & IMAGEPAINT_PROJECT_LAYER_CLONE); + + ps->do_layer_stencil = (settings->imapaint.flag & IMAGEPAINT_PROJECT_LAYER_STENCIL) ? 1 : 0; + ps->do_layer_stencil_inv = (settings->imapaint.flag & IMAGEPAINT_PROJECT_LAYER_STENCIL_INV) ? 1 : 0; + + +#ifndef PROJ_DEBUG_NOSEAMBLEED + ps->seam_bleed_px = settings->imapaint.seam_bleed; /* pixel num to bleed */ +#endif + + if (ps->do_mask_normal) { + ps->normal_angle_inner = settings->imapaint.normal_angle; + ps->normal_angle = (ps->normal_angle_inner + 90.0f) * 0.5f; + } + else { + ps->normal_angle_inner = ps->normal_angle = settings->imapaint.normal_angle; + } + + ps->normal_angle_inner *= (float)(M_PI_2 / 90); + ps->normal_angle *= (float)(M_PI_2 / 90); + ps->normal_angle_range = ps->normal_angle - ps->normal_angle_inner; + + if (ps->normal_angle_range <= 0.0f) + ps->do_mask_normal = FALSE; /* no need to do blending */ +} + +static void paint_brush_init_tex(Brush *brush) +{ + /* init mtex nodes */ + if (brush) { + MTex *mtex = &brush->mtex; + if (mtex->tex && mtex->tex->nodetree) + ntreeTexBeginExecTree(mtex->tex->nodetree, 1); /* has internal flag to detect it only does it once */ + } + +} + +static PaintOperation * texture_paint_init(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + ToolSettings *settings = scene->toolsettings; + Brush *brush = paint_brush(&settings->imapaint.paint); + PaintOperation *pop = MEM_callocN(sizeof(PaintOperation), "PaintOperation"); /* caller frees */ + + pop->first = 1; + + view3d_set_viewcontext(C, &pop->vc); + + /* initialize from context */ + if (CTX_wm_region_view3d(C)) { + pop->mode = PAINT_MODE_3D_PROJECT; + } + else { + pop->s.sima = CTX_wm_space_image(C); + pop->s.v2d = &CTX_wm_region(C)->v2d; + } + + pop->s.scene = scene; + pop->s.screen = CTX_wm_screen(C); + + pop->s.brush = brush; + pop->s.tool = brush->imagepaint_tool; + pop->s.blend = brush->blend; + pop->orig_brush_size = BKE_brush_size_get(scene, brush); + + if (pop->mode != PAINT_MODE_2D) { + Object *ob = OBACT; + Mesh *me = BKE_mesh_from_object(ob); + + if (!me) { + return 0; + } + + pop->s.ob = ob; + pop->s.do_facesel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + + /* for non prohect paint we need */ + /* fill in derived mesh */ + if (ob->derivedFinal && CustomData_has_layer(&ob->derivedFinal->faceData, CD_MTFACE)) { + pop->s.dm = ob->derivedFinal; + pop->s.dm_release = FALSE; + } + else { + pop->s.dm = mesh_get_derived_final(pop->s.scene, ob, pop->s.scene->customdata_mask | CD_MASK_MTFACE); + pop->s.dm_release = TRUE; + } + + if (!CustomData_has_layer(&pop->s.dm->faceData, CD_MTFACE)) { + + if (pop->s.dm_release) + pop->s.dm->release(pop->s.dm); + + pop->s.dm = NULL; + MEM_freeN(pop); + return NULL; + } + + pop->s.dm_mface = pop->s.dm->getTessFaceArray(pop->s.dm); + pop->s.dm_mtface = pop->s.dm->getTessFaceDataArray(pop->s.dm, CD_MTFACE); + pop->s.dm_totface = pop->s.dm->getNumTessFaces(pop->s.dm); + } + else { + pop->s.image = pop->s.sima->image; + + if (!imapaint_canvas_set(&pop->s, pop->s.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"); + + MEM_freeN(pop); + return NULL; + } + } + + /* note, if we have no UVs on the derived mesh, then we must return here */ + if (pop->mode == PAINT_MODE_3D_PROJECT) { + + /* initialize all data from the context */ + project_state_init(C, OBACT, &pop->ps); + + /* needed so multiple threads don't try to initialize the brush at once (can leak memory) */ + curvemapping_initialize(pop->ps.brush->curve); + + paint_brush_init_tex(pop->ps.brush); + + pop->ps.source = PROJ_SRC_VIEW; + + if (pop->ps.ob == NULL || !(pop->ps.ob->lay & pop->ps.v3d->lay)) { + MEM_freeN(pop); + return NULL; + } + + /* Don't allow brush size below 2 */ + if (BKE_brush_size_get(scene, brush) < 2) + BKE_brush_size_set(scene, brush, 2); + + /* allocate and initialize spatial data structures */ + project_paint_begin(&pop->ps); + + if (pop->ps.dm == NULL) { + MEM_freeN(pop); + return NULL; + } + } + else { + paint_brush_init_tex(pop->s.brush); + } + + settings->imapaint.flag |= IMAGEPAINT_DRAWING; + undo_paint_push_begin(UNDO_PAINT_IMAGE, op->type->name, + image_undo_restore, image_undo_free); + + /* create painter */ + pop->painter = BKE_brush_painter_new(scene, pop->s.brush); + + { + UnifiedPaintSettings *ups = &settings->unified_paint_settings; + ups->draw_pressure = true; + } + + return pop; +} + +static void paint_stroke_update_step(bContext *C, struct PaintStroke *stroke, PointerRNA *itemptr) +{ + PaintOperation *pop = paint_stroke_mode_data(stroke); + float mousef[2]; + float pressure; + int mouse[2], redraw; + + RNA_float_get_array(itemptr, "mouse", mousef); + mouse[0] = (int)(mousef[0]); + mouse[1] = (int)(mousef[1]); + pressure = RNA_float_get(itemptr, "pressure"); + + if (pop->first) + project_paint_begin_clone(&pop->ps, mouse); + + if (pop->mode == PAINT_MODE_3D_PROJECT) { + redraw = project_paint_stroke(&pop->ps, pop->prevmouse, mouse, pressure); + pop->prevmouse[0] = mouse[0]; + pop->prevmouse[1] = mouse[1]; + + } + else { + redraw = imapaint_paint_stroke(&pop->s, pop->painter, mouse, 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_brush_exit_tex(Brush *brush) +{ + if (brush) { + MTex *mtex = &brush->mtex; + if (mtex->tex && mtex->tex->nodetree) + ntreeTexEndExecTree(mtex->tex->nodetree->execdata, 1); + } +} + +static void paint_stroke_done(const bContext *C, struct PaintStroke *stroke) +{ + Scene *scene = CTX_data_scene(C); + ToolSettings *settings = scene->toolsettings; + PaintOperation *pop = paint_stroke_mode_data(stroke); + + if (pop->timer) + WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), pop->timer); + + settings->imapaint.flag &= ~IMAGEPAINT_DRAWING; + imapaint_canvas_free(&pop->s); + BKE_brush_painter_free(pop->painter); + + if (pop->mode == PAINT_MODE_3D_PROJECT) { + BKE_brush_size_set(scene, pop->ps.brush, pop->orig_brush_size); + paint_brush_exit_tex(pop->ps.brush); + + project_paint_end(&pop->ps); + } + else { + paint_brush_exit_tex(pop->s.brush); + + /* non projection 3d paint, could move into own function of more needs adding */ + if (pop->s.dm_release) + pop->s.dm->release(pop->s.dm); + } + + paint_redraw(C, &pop->s, 1); + undo_paint_push_end(UNDO_PAINT_IMAGE); + + /* duplicate warning, see texpaint_init + 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); + + { + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + ups->draw_pressure = false; + } +} + +static int paint_stroke_test_start(bContext * UNUSED(C), wmOperator * UNUSED(op), const float UNUSED(mouse[2])) { + return true; +} + + +static int paint_invoke_proj(bContext *C, wmOperator *op, wmEvent *event) +{ + PaintOperation *pop; + struct PaintStroke *stroke; + int retval; + + if (!(pop = texture_paint_init(C, op))) { + return OPERATOR_CANCELLED; + } + + stroke = op->customdata = paint_stroke_new(C, NULL, paint_stroke_test_start, + paint_stroke_update_step, + paint_stroke_done, event->type); + paint_stroke_set_mode_data(stroke, pop); + /* add modal handler */ + WM_event_add_modal_handler(C, op); + + retval = op->type->modal(C, op, event); + OPERATOR_RETVAL_CHECK(retval); + BLI_assert(retval == OPERATOR_RUNNING_MODAL); + + return OPERATOR_RUNNING_MODAL; +} + + +void PAINT_OT_image_paint_proj(wmOperatorType *ot) +{ + + /* identifiers */ + ot->name = "Image Paint"; + ot->idname = "PAINT_OT_image_paint_proj"; + ot->description = "Paint a stroke into the image"; + + /* api callbacks */ + ot->invoke = paint_invoke_proj; + ot->modal = paint_stroke_modal; + /* ot->exec = paint_exec; <-- needs stroke property */ + ot->poll = image_paint_poll; + ot->cancel = paint_stroke_cancel; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; + + RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", ""); +} + diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index a75d98f9923..36b1e56557f 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -113,6 +113,9 @@ void PAINT_OT_texture_paint_toggle(struct wmOperatorType *ot); void PAINT_OT_project_image(struct wmOperatorType *ot); void PAINT_OT_image_from_view(struct wmOperatorType *ot); +/* new texture painting */ +void PAINT_OT_image_paint_proj(struct wmOperatorType *ot); + /* uv sculpting */ int uv_sculpt_poll(struct bContext *C); diff --git a/source/blender/editors/sculpt_paint/paint_ops.c b/source/blender/editors/sculpt_paint/paint_ops.c index 7385c2f0cf1..4d350e2fe3a 100644 --- a/source/blender/editors/sculpt_paint/paint_ops.c +++ b/source/blender/editors/sculpt_paint/paint_ops.c @@ -465,6 +465,7 @@ 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_image_paint_proj); WM_operatortype_append(PAINT_OT_sample_color); WM_operatortype_append(PAINT_OT_grab_clone); WM_operatortype_append(PAINT_OT_clone_cursor_set); @@ -760,6 +761,7 @@ void ED_keymap_paint(wmKeyConfig *keyconf) keymap->poll = image_texture_paint_poll; WM_keymap_add_item(keymap, "PAINT_OT_image_paint", LEFTMOUSE, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "PAINT_OT_image_paint_proj", LEFTMOUSE, KM_PRESS, KM_SHIFT, 0); WM_keymap_add_item(keymap, "PAINT_OT_grab_clone", RIGHTMOUSE, 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_clone_cursor_set", LEFTMOUSE, KM_PRESS, KM_CTRL, 0); diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c index df44335ad78..a44702bd125 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.c +++ b/source/blender/editors/sculpt_paint/paint_stroke.c @@ -286,7 +286,8 @@ PaintStroke *paint_stroke_new(bContext *C, stroke->brush = paint_brush(paint_get_active_from_context(C)); view3d_set_viewcontext(C, &stroke->vc); - view3d_get_transformation(stroke->vc.ar, stroke->vc.rv3d, stroke->vc.obact, &stroke->mats); + if(stroke->vc.v3d) + view3d_get_transformation(stroke->vc.ar, stroke->vc.rv3d, stroke->vc.obact, &stroke->mats); stroke->get_location = get_location; stroke->test_start = test_start; diff --git a/source/blender/editors/space_view3d/view3d_draw.c b/source/blender/editors/space_view3d/view3d_draw.c index 323ee6642df..de6b6607619 100644 --- a/source/blender/editors/space_view3d/view3d_draw.c +++ b/source/blender/editors/space_view3d/view3d_draw.c @@ -1313,11 +1313,6 @@ static void backdrawview3d(Scene *scene, ARegion *ar, View3D *v3d) { /* do nothing */ } - else if ((base && (base->object->mode & OB_MODE_TEXTURE_PAINT)) && - scene->toolsettings && (scene->toolsettings->imapaint.flag & IMAGEPAINT_PROJECT_DISABLE)) - { - /* do nothing */ - } else if ((base && (base->object->mode & OB_MODE_PARTICLE_EDIT)) && v3d->drawtype > OB_WIRE && (v3d->flag & V3D_ZBUF_SELECT)) { diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index a6c838eb485..fa6fd41f568 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -1512,7 +1512,6 @@ typedef enum SculptFlags { // #define IMAGEPAINT_DRAW_TOOL_DRAWING 4 // deprecated /* projection painting only */ -#define IMAGEPAINT_PROJECT_DISABLE 8 /* Non projection 3D painting */ #define IMAGEPAINT_PROJECT_XRAY 16 #define IMAGEPAINT_PROJECT_BACKFACE 32 #define IMAGEPAINT_PROJECT_FLAT 64 diff --git a/source/blender/makesrna/intern/rna_sculpt_paint.c b/source/blender/makesrna/intern/rna_sculpt_paint.c index e0687295c70..b8b4ad5bb76 100644 --- a/source/blender/makesrna/intern/rna_sculpt_paint.c +++ b/source/blender/makesrna/intern/rna_sculpt_paint.c @@ -451,12 +451,7 @@ static void rna_def_image_paint(BlenderRNA *brna) RNA_def_struct_path_func(srna, "rna_ImagePaintSettings_path"); RNA_def_struct_ui_text(srna, "Image Paint", "Properties of image and texture painting mode"); - /* booleans */ - prop = RNA_def_property(srna, "use_projection", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", IMAGEPAINT_PROJECT_DISABLE); - RNA_def_property_ui_text(prop, "Project Paint", - "Use projection painting for improved consistency in the brush strokes"); - + /* booleans */ prop = RNA_def_property(srna, "use_occlude", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", IMAGEPAINT_PROJECT_XRAY); RNA_def_property_ui_text(prop, "Occlude", "Only paint onto the faces directly under the brush (slower)"); |