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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCampbell Barton <ideasman42@gmail.com>2008-12-12 21:59:17 +0300
committerCampbell Barton <ideasman42@gmail.com>2008-12-12 21:59:17 +0300
commitaa409532107e58ac74ac18f354e97637b797653d (patch)
treed1417a0dbe62d05c159da9c48ec06a15dbe2fb16
parente94dae4c039438aa6d970996d68280c9f4933e52 (diff)
parent696ef1afd29ef31b7202a9f5589076fe1a05a00b (diff)
Projection Painting, merge from branch
Additions/Features... - Painting onto multiple images at once. - Clone with an offset similar to the gimp, also an option to clone between UV layers, (not both offset and between layers atm) - Stencil layer, similar to The Gimp's quickmask. - Face mask mode - (painting onto only selected faces). - Texture brushes (so image and texture-node brushes too) - Seam bleed so you don't see gaps when painting over UV seams (new option set in the panit panel) - Occlusion (only point onto the first face under the paintbrush, can also be used for masking out parts of the model to paint onto) - (new option set in the paint panel) - Muti-threaded, each thread operates on its own bucket. - Support partial updates to speedup OpenGL texture refreshing. - Support tile based multiple undo's - Support float image buffers. - Backface culling (new option set in the paint panel) - All color blending modes - Add/Multiply.. etc. - Airbrush rate. (used to be broken) - Improve the way paint is mixed when painting, so painting with 0.2 alpha will not accumulate above 0.2 alpha on the image as you paint. - Option to use normals to fade out strokes on the edge of a surface to avoid ugly edges. (same as Vertex Paints "Normal" option) Known Problems - Not directly related to painting but changes in selection flags are not updated on the final derived mesh. - Smear tool has some distortion when in perspective mode. - While in perspective view any faces with 1 vert behind the viewpoint, will be completely ignored. - Painting can be initially slow while buckets are being initialized, a number of solutions exist. - Size 1 brushes don't work yet. - When painting onto large faces its still possible to get artifacts. - Spatial optimizations are needed to skip occluded faces and test pixel occlusion. (will look into using blenders BVH )
-rw-r--r--source/blender/blenkernel/BKE_blender.h2
-rw-r--r--source/blender/blenkernel/BKE_brush.h1
-rw-r--r--source/blender/blenkernel/BKE_customdata.h8
-rw-r--r--source/blender/blenkernel/intern/brush.c17
-rw-r--r--source/blender/blenkernel/intern/customdata.c90
-rw-r--r--source/blender/blenloader/intern/readfile.c11
-rw-r--r--source/blender/include/BSE_view.h1
-rw-r--r--source/blender/include/butspace.h2
-rw-r--r--source/blender/makesdna/DNA_customdata_types.h2
-rw-r--r--source/blender/makesdna/DNA_scene_types.h13
-rw-r--r--source/blender/src/buttons_editing.c146
-rw-r--r--source/blender/src/drawview.c7
-rw-r--r--source/blender/src/imagepaint.c3830
-rw-r--r--source/blender/src/space.c8
-rw-r--r--source/blender/src/view.c26
15 files changed, 4058 insertions, 106 deletions
diff --git a/source/blender/blenkernel/BKE_blender.h b/source/blender/blenkernel/BKE_blender.h
index f392d57521c..a6334e665d1 100644
--- a/source/blender/blenkernel/BKE_blender.h
+++ b/source/blender/blenkernel/BKE_blender.h
@@ -41,7 +41,7 @@ struct ListBase;
struct MemFile;
#define BLENDER_VERSION 248
-#define BLENDER_SUBVERSION 1
+#define BLENDER_SUBVERSION 2
#define BLENDER_MINVERSION 245
#define BLENDER_MINSUBVERSION 15
diff --git a/source/blender/blenkernel/BKE_brush.h b/source/blender/blenkernel/BKE_brush.h
index f1f4653f092..398d203709f 100644
--- a/source/blender/blenkernel/BKE_brush.h
+++ b/source/blender/blenkernel/BKE_brush.h
@@ -53,6 +53,7 @@ int brush_clone_image_delete(struct Brush *brush);
/* sampling */
float brush_sample_falloff(struct Brush *brush, float dist);
+float brush_sample_falloff_noalpha(struct Brush *brush, float dist);
void brush_sample_tex(struct Brush *brush, float *xy, float *rgba);
void brush_imbuf_new(struct Brush *brush, short flt, short texfalloff, int size,
struct ImBuf **imbuf);
diff --git a/source/blender/blenkernel/BKE_customdata.h b/source/blender/blenkernel/BKE_customdata.h
index c84b690bc49..10791968f79 100644
--- a/source/blender/blenkernel/BKE_customdata.h
+++ b/source/blender/blenkernel/BKE_customdata.h
@@ -198,8 +198,12 @@ int CustomData_get_layer_index(const struct CustomData *data, int type);
int CustomData_get_named_layer_index(const struct CustomData *data, int type, char *name);
int CustomData_get_active_layer_index(const struct CustomData *data, int type);
int CustomData_get_render_layer_index(const struct CustomData *data, int type);
+int CustomData_get_clone_layer_index(const struct CustomData *data, int type);
+int CustomData_get_mask_layer_index(const struct CustomData *data, int type);
int CustomData_get_active_layer(const struct CustomData *data, int type);
int CustomData_get_render_layer(const struct CustomData *data, int type);
+int CustomData_get_clone_layer(const struct CustomData *data, int type);
+int CustomData_get_mask_layer(const struct CustomData *data, int type);
/* copies the data from source to the data element at index in the first
* layer of type
@@ -227,10 +231,14 @@ void *CustomData_set_layer_n(const struct CustomData *data, int type, int n, voi
/* sets the nth layer of type as active */
void CustomData_set_layer_active(struct CustomData *data, int type, int n);
void CustomData_set_layer_render(struct CustomData *data, int type, int n);
+void CustomData_set_layer_clone(struct CustomData *data, int type, int n);
+void CustomData_set_layer_mask(struct CustomData *data, int type, int n);
/* same as above but works with an index from CustomData_get_layer_index */
void CustomData_set_layer_active_index(struct CustomData *data, int type, int n);
void CustomData_set_layer_render_index(struct CustomData *data, int type, int n);
+void CustomData_set_layer_clone_index(struct CustomData *data, int type, int n);
+void CustomData_set_layer_mask_index(struct CustomData *data, int type, int n);
/* adds flag to the layer flags */
void CustomData_set_layer_flag(struct CustomData *data, int type, int flag);
diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c
index 1c53af97dbb..021f76fd2f1 100644
--- a/source/blender/blenkernel/intern/brush.c
+++ b/source/blender/blenkernel/intern/brush.c
@@ -328,6 +328,23 @@ float brush_sample_falloff(Brush *brush, float dist)
return 0.0f;
}
+float brush_sample_falloff_noalpha(Brush *brush, float dist)
+{
+ float outer, inner;
+
+ outer = brush->size >> 1;
+ inner = outer*brush->innerradius;
+
+ if (dist <= inner) {
+ return 1.0f;
+ }
+ else if ((dist < outer) && (inner < outer)) {
+ return 1.0f - sqrt((dist - inner)/(outer - inner));
+ }
+ else
+ return 0.0f;
+}
+
void brush_sample_tex(Brush *brush, float *xy, float *rgba)
{
MTex *mtex= brush->mtex[brush->texact];
diff --git a/source/blender/blenkernel/intern/customdata.c b/source/blender/blenkernel/intern/customdata.c
index e93266c85f3..05271aa59a7 100644
--- a/source/blender/blenkernel/intern/customdata.c
+++ b/source/blender/blenkernel/intern/customdata.c
@@ -606,7 +606,7 @@ void CustomData_merge(const struct CustomData *source, struct CustomData *dest,
{
const LayerTypeInfo *typeInfo;
CustomDataLayer *layer, *newlayer;
- int i, type, number = 0, lasttype = -1, lastactive = 0, lastrender = 0;
+ int i, type, number = 0, lasttype = -1, lastactive = 0, lastrender = 0, lastclone = 0, lastmask = 0;
for(i = 0; i < source->totlayer; ++i) {
layer = &source->layers[i];
@@ -618,6 +618,8 @@ void CustomData_merge(const struct CustomData *source, struct CustomData *dest,
number = 0;
lastactive = layer->active;
lastrender = layer->active_rnd;
+ lastclone = layer->active_clone;
+ lastmask = layer->active_mask;
lasttype = type;
}
else
@@ -637,6 +639,8 @@ void CustomData_merge(const struct CustomData *source, struct CustomData *dest,
if(newlayer) {
newlayer->active = lastactive;
newlayer->active_rnd = lastrender;
+ newlayer->active_clone = lastclone;
+ newlayer->active_mask = lastmask;
}
}
}
@@ -736,6 +740,28 @@ int CustomData_get_render_layer_index(const CustomData *data, int type)
return -1;
}
+int CustomData_get_clone_layer_index(const CustomData *data, int type)
+{
+ int i;
+
+ for(i=0; i < data->totlayer; ++i)
+ if(data->layers[i].type == type)
+ return i + data->layers[i].active_clone;
+
+ return -1;
+}
+
+int CustomData_get_mask_layer_index(const CustomData *data, int type)
+{
+ int i;
+
+ for(i=0; i < data->totlayer; ++i)
+ if(data->layers[i].type == type)
+ return i + data->layers[i].active_mask;
+
+ return -1;
+}
+
int CustomData_get_active_layer(const CustomData *data, int type)
{
int i;
@@ -758,6 +784,27 @@ int CustomData_get_render_layer(const CustomData *data, int type)
return -1;
}
+int CustomData_get_clone_layer(const CustomData *data, int type)
+{
+ int i;
+
+ for(i=0; i < data->totlayer; ++i)
+ if(data->layers[i].type == type)
+ return data->layers[i].active_clone;
+
+ return -1;
+}
+
+int CustomData_get_mask_layer(const CustomData *data, int type)
+{
+ int i;
+
+ for(i=0; i < data->totlayer; ++i)
+ if(data->layers[i].type == type)
+ return data->layers[i].active_mask;
+
+ return -1;
+}
void CustomData_set_layer_active(CustomData *data, int type, int n)
{
@@ -777,6 +824,24 @@ void CustomData_set_layer_render(CustomData *data, int type, int n)
data->layers[i].active_rnd = n;
}
+void CustomData_set_layer_clone(CustomData *data, int type, int n)
+{
+ int i;
+
+ for(i=0; i < data->totlayer; ++i)
+ if(data->layers[i].type == type)
+ data->layers[i].active_clone = n;
+}
+
+void CustomData_set_layer_mask(CustomData *data, int type, int n)
+{
+ int i;
+
+ for(i=0; i < data->totlayer; ++i)
+ if(data->layers[i].type == type)
+ data->layers[i].active_mask = n;
+}
+
/* for using with an index from CustomData_get_active_layer_index and CustomData_get_render_layer_index */
void CustomData_set_layer_active_index(CustomData *data, int type, int n)
{
@@ -796,6 +861,23 @@ void CustomData_set_layer_render_index(CustomData *data, int type, int n)
data->layers[i].active_rnd = n-i;
}
+void CustomData_set_layer_clone_index(CustomData *data, int type, int n)
+{
+ int i;
+
+ for(i=0; i < data->totlayer; ++i)
+ if(data->layers[i].type == type)
+ data->layers[i].active_clone = n-i;
+}
+
+void CustomData_set_layer_mask_index(CustomData *data, int type, int n)
+{
+ int i;
+
+ for(i=0; i < data->totlayer; ++i)
+ if(data->layers[i].type == type)
+ data->layers[i].active_mask = n-i;
+}
void CustomData_set_layer_flag(struct CustomData *data, int type, int flag)
{
@@ -882,9 +964,13 @@ static CustomDataLayer *customData_add_layer__internal(CustomData *data,
if(index > 0 && data->layers[index-1].type == type) {
data->layers[index].active = data->layers[index-1].active;
data->layers[index].active_rnd = data->layers[index-1].active_rnd;
+ data->layers[index].active_clone = data->layers[index-1].active_clone;
+ data->layers[index].active_mask = data->layers[index-1].active_mask;
} else {
data->layers[index].active = 0;
data->layers[index].active_rnd = 0;
+ data->layers[index].active_clone = 0;
+ data->layers[index].active_mask = 0;
}
customData_update_offsets(data);
@@ -944,6 +1030,8 @@ int CustomData_free_layer(CustomData *data, int type, int totelem, int index)
for (; i < data->totlayer && data->layers[i].type == type; i++) {
data->layers[i].active--;
data->layers[i].active_rnd--;
+ data->layers[i].active_clone--;
+ data->layers[i].active_mask--;
}
}
diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c
index 423d050c862..91170563c11 100644
--- a/source/blender/blenloader/intern/readfile.c
+++ b/source/blender/blenloader/intern/readfile.c
@@ -8007,6 +8007,17 @@ static void do_versions(FileData *fd, Library *lib, Main *main)
}
}
+ if (main->versionfile < 248 || (main->versionfile == 248 && main->subversionfile < 2)) {
+ Scene *sce;
+
+ /* Note, these will need to be added for painting */
+ for (sce= main->scene.first; sce; sce= sce->id.next) {
+ sce->toolsettings->imapaint.seam_bleed = 2;
+ sce->toolsettings->imapaint.normal_angle = 80;
+ }
+ }
+
+
/* WATCH IT!!!: pointers from libdata have not been converted yet here! */
/* WATCH IT 2!: Userdef struct init has to be in src/usiblender.c! */
diff --git a/source/blender/include/BSE_view.h b/source/blender/include/BSE_view.h
index f7afce958f5..55363bcf1f4 100644
--- a/source/blender/include/BSE_view.h
+++ b/source/blender/include/BSE_view.h
@@ -80,6 +80,7 @@ void viewmove(int mode);
void viewmoveNDOFfly(int mode);
void viewmoveNDOF(int mode);
void view_zoom_mouseloc(float dfac, short *mouseloc);
+int view_mouse_depth( float mouse_worldloc[3], short mval[2], int dist);
int get_view3d_viewplane(int winxi, int winyi, rctf *viewplane, float *clipsta, float *clipend, float *pixsize);
void setwinmatrixview3d(int winx, int winy, struct rctf *rect);
diff --git a/source/blender/include/butspace.h b/source/blender/include/butspace.h
index 7019a3f6959..868d0c3e6c0 100644
--- a/source/blender/include/butspace.h
+++ b/source/blender/include/butspace.h
@@ -453,6 +453,8 @@ void curvemap_buttons(struct uiBlock *block, struct CurveMapping *cumap, char la
#define B_GEN_SKELETON 2085
#define B_RETARGET_SKELETON 2086
+#define B_SETTFACE_CLONE 2087
+#define B_SETTFACE_MASK 2088
/* *********************** */
#define B_VGROUPBUTS 2100
diff --git a/source/blender/makesdna/DNA_customdata_types.h b/source/blender/makesdna/DNA_customdata_types.h
index 6c098e220bb..e6b18641d2a 100644
--- a/source/blender/makesdna/DNA_customdata_types.h
+++ b/source/blender/makesdna/DNA_customdata_types.h
@@ -37,6 +37,8 @@ typedef struct CustomDataLayer {
int flag; /* general purpose flag */
int active; /* number of the active layer of this type */
int active_rnd; /* number of the layer to render*/
+ int active_clone; /* number of the layer to render*/
+ int active_mask; /* number of the layer to render*/
char pad[4];
char name[32]; /* layer name */
void *data; /* layer data */
diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h
index c1c164f136f..f92311312fa 100644
--- a/source/blender/makesdna/DNA_scene_types.h
+++ b/source/blender/makesdna/DNA_scene_types.h
@@ -345,7 +345,9 @@ typedef struct TimeMarker {
typedef struct ImagePaintSettings {
struct Brush *brush;
short flag, tool;
- int pad3;
+
+ /* for projection painting only */
+ short seam_bleed,normal_angle;
} ImagePaintSettings;
typedef struct ParticleBrushData {
@@ -799,6 +801,15 @@ typedef struct Scene {
#define IMAGEPAINT_DRAW_TOOL 2
#define IMAGEPAINT_DRAW_TOOL_DRAWING 4
+/* 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
+#define IMAGEPAINT_PROJECT_LAYER_CLONE 128
+#define IMAGEPAINT_PROJECT_LAYER_MASK 256
+#define IMAGEPAINT_PROJECT_LAYER_MASK_INV 512
+
/* toolsettings->uvcalc_flag */
#define UVCALC_FILLHOLES 1
#define UVCALC_NO_ASPECT_CORRECT 2 /* would call this UVCALC_ASPECT_CORRECT, except it should be default with old file */
diff --git a/source/blender/src/buttons_editing.c b/source/blender/src/buttons_editing.c
index edf4660b71e..c1539a46c86 100644
--- a/source/blender/src/buttons_editing.c
+++ b/source/blender/src/buttons_editing.c
@@ -184,7 +184,7 @@
static float editbutweight= 1.0;
float editbutvweight= 1;
-static int actmcol= 0, acttface= 0, acttface_rnd = 0, actmcol_rnd = 0;
+static int actmcol= 0, acttface= 0, acttface_rnd = 0, acttface_clone = 0, acttface_mask = 0, actmcol_rnd = 0;
extern ListBase editNurb;
@@ -722,10 +722,10 @@ static void delete_customdata_layer(void *data1, void *data2)
Mesh *me= (Mesh*)data1;
CustomData *data= (G.obedit)? &G.editMesh->fdata: &me->fdata;
CustomDataLayer *layer= (CustomDataLayer*)data2;
- void *actlayerdata, *rndlayerdata, *layerdata=layer->data;
+ void *actlayerdata, *rndlayerdata, *clonelayerdata, *masklayerdata, *layerdata=layer->data;
int type= layer->type;
int index= CustomData_get_layer_index(data, type);
- int i, actindex, rndindex;
+ int i, actindex, rndindex, cloneindex, maskindex;
/*ok, deleting a non-active layer needs to preserve the active layer indices.
to do this, we store a pointer to the .data member of both layer and the active layer,
@@ -736,6 +736,8 @@ static void delete_customdata_layer(void *data1, void *data2)
layer. */
actlayerdata = data->layers[CustomData_get_active_layer_index(data, type)].data;
rndlayerdata = data->layers[CustomData_get_render_layer_index(data, type)].data;
+ clonelayerdata = data->layers[CustomData_get_clone_layer_index(data, type)].data;
+ masklayerdata = data->layers[CustomData_get_mask_layer_index(data, type)].data;
CustomData_set_layer_active(data, type, layer - &data->layers[index]);
/* Multires is handled seperately because the display data is separate
@@ -787,6 +789,33 @@ static void delete_customdata_layer(void *data1, void *data2)
CustomData_set_layer_render(data, type, rndindex);
}
+ if (clonelayerdata != layerdata) {
+ /*find index. . .*/
+ cloneindex = CustomData_get_layer_index(data, type);
+ for (i=cloneindex; i<data->totlayer; i++) {
+ if (data->layers[i].data == clonelayerdata) {
+ cloneindex = i - cloneindex;
+ break;
+ }
+ }
+
+ /*set index. . .*/
+ CustomData_set_layer_clone(data, type, cloneindex);
+ }
+
+ if (masklayerdata != layerdata) {
+ /*find index. . .*/
+ maskindex = CustomData_get_layer_index(data, type);
+ for (i=maskindex; i<data->totlayer; i++) {
+ if (data->layers[i].data == masklayerdata) {
+ maskindex = i - maskindex;
+ break;
+ }
+ }
+
+ /*set index. . .*/
+ CustomData_set_layer_mask(data, type, maskindex);
+ }
DAG_object_flush_update(G.scene, OBACT, OB_RECALC_DATA);
@@ -802,9 +831,9 @@ static void delete_customdata_layer(void *data1, void *data2)
static int customdata_buttons(
uiBlock *block, Mesh *me, CustomData *data,
- int type, int *activep, int *renderp,
- int setevt, int setevt_rnd, int newevt,
- char *label, char *shortlabel, char *browsetip, char *browsetip_rnd,
+ int type, int *activep, int *renderp, int *clonep, int *maskp,
+ int setevt, int setevt_rnd, int setevt_clone, int setevt_mask, int newevt,
+ char *label, char *shortlabel, char *browsetip, char *browsetip_rnd, char *browsetip_clone, char *browsetip_mask,
char *newtip, char *deltip, int x, int y)
{
CustomDataLayer *layer;
@@ -828,12 +857,27 @@ static int customdata_buttons(
layer= &data->layers[i];
if(layer->type == type) {
+ int xi = 0;
*activep= layer->active + 1;
*renderp= layer->active_rnd + 1;
+ if (clonep) *clonep= layer->active_clone + 1;
+ if (maskp) *maskp= layer->active_mask + 1;
+
uiDefIconButI(block, ROW, setevt, ICON_VIEW3D, x,y,25,19, activep, 1.0, count, 0, 0, browsetip);
uiDefIconButI(block, ROW, setevt_rnd, ICON_SCENE, x+25,y,25,19, renderp, 1.0, count, 0, 0, browsetip_rnd);
- but=uiDefBut(block, TEX, setevt, "", x+50,y,145,19, layer->name, 0.0, 31.0, 0, 0, label);
+
+ if (clonep) {
+ uiDefIconButI(block, ROW, setevt_clone, ICON_TEXTURE, x+50,y,25,19, clonep, 1.0, count, 0, 0, browsetip_clone);
+ xi += 25;
+ }
+
+ if (maskp) {
+ uiDefIconButI(block, ROW, setevt_mask, ICON_PAINT, x+50+xi,y,25,19, maskp, 1.0, count, 0, 0, browsetip_mask);
+ xi += 25;
+ }
+
+ but=uiDefBut(block, TEX, setevt, "", x+50+xi,y,145-xi,19, layer->name, 0.0, 31.0, 0, 0, label);
uiButSetFunc(but, verify_customdata_name_func, data, layer);
but= uiDefIconBut(block, BUT, B_NOP, VICON_X, x+195,y,25,19, NULL, 0.0, 0.0, 0.0, 0.0, deltip);
uiButSetFunc(but, delete_customdata_layer, me, layer);
@@ -902,14 +946,14 @@ static void editing_panel_mesh_type(Object *ob, Mesh *me)
uiBlockEndAlign(block);
fdata= (G.obedit)? &G.editMesh->fdata: &me->fdata;
- yco= customdata_buttons(block, me, fdata, CD_MTFACE, &acttface, &acttface_rnd,
- B_SETTFACE, B_SETTFACE_RND, B_NEWTFACE, "UV Texture", "UV Texture:",
- "Set active UV texture", "Set rendering UV texture", "Creates a new UV texture layer",
+ yco= customdata_buttons(block, me, fdata, CD_MTFACE, &acttface, &acttface_rnd, (G.f & G_TEXTUREPAINT ? &acttface_clone : NULL), (G.f & G_TEXTUREPAINT ? &acttface_mask : NULL),
+ B_SETTFACE, B_SETTFACE_RND, B_SETTFACE_CLONE, B_SETTFACE_MASK, B_NEWTFACE, "UV Texture", "UV Texture:",
+ "Set active UV texture", "Set rendering UV texture", "Set the layer used for texturepaint cloning", "Set the texture paint stencil layer", "Creates a new UV texture layer",
"Removes the current UV texture layer", 190, 130);
- yco= customdata_buttons(block, me, fdata, CD_MCOL, &actmcol, &actmcol_rnd,
- B_SETMCOL, B_SETMCOL_RND, B_NEWMCOL, "Vertex Color", "Vertex Color:",
- "Sets active vertex color layer", "Sets rendering vertex color layer", "Creates a new vertex color layer",
+ yco= customdata_buttons(block, me, fdata, CD_MCOL, &actmcol, &actmcol_rnd, NULL, NULL,
+ B_SETMCOL, B_SETMCOL_RND, B_NOP, B_NOP, B_NEWMCOL, "Vertex Color", "Vertex Color:",
+ "Sets active vertex color layer", "Sets rendering vertex color layer", "", "", "Creates a new vertex color layer",
"Removes the current vertex color layer", 190, yco-5);
if(yco < 0)
@@ -4964,7 +5008,22 @@ void do_meshbuts(unsigned short event)
allqueue(REDRAWBUTSEDIT, 0);
}
break;
-
+ case B_SETTFACE_CLONE:
+ if (G.obedit || me) {
+ CustomData *fdata= (G.obedit)? &em->fdata: &me->fdata;
+ CustomData_set_layer_clone(fdata, CD_MTFACE, acttface_clone-1);
+ BIF_undo_push("Set Clone UV Texture");
+ allqueue(REDRAWBUTSEDIT, 0);
+ }
+ break;
+ case B_SETTFACE_MASK:
+ if (G.obedit || me) {
+ CustomData *fdata= (G.obedit)? &em->fdata: &me->fdata;
+ CustomData_set_layer_mask(fdata, CD_MTFACE, acttface_mask-1);
+ BIF_undo_push("Set Stencil UV Texture");
+ allqueue(REDRAWBUTSEDIT, 0);
+ }
+ break;
case B_FLIPNORM:
if(G.obedit) {
flip_editnormals();
@@ -6279,20 +6338,23 @@ void brush_buttons(uiBlock *block, short sima,
ToolSettings *settings= G.scene->toolsettings;
Brush *brush= settings->imapaint.brush;
ID *id;
- int yco, xco, butw;
+ int yco, xco, butw, but_idx;
short *menupoin = sima ? &(G.sima->menunr) : &(G.buts->menunr);
+ short do_project = settings->imapaint.flag & IMAGEPAINT_PROJECT_DISABLE ? 0:1;
yco= 160;
butw = sima ? 80 : 106;
uiBlockBeginAlign(block);
- uiDefButS(block, ROW, evt_change, "Draw", 0, yco,butw,19, &settings->imapaint.tool, 7.0, PAINT_TOOL_DRAW, 0, 0, "Draw brush");
- uiDefButS(block, ROW, evt_change, "Soften", butw, yco,butw,19, &settings->imapaint.tool, 7.0, PAINT_TOOL_SOFTEN, 0, 0, "Soften brush");
- uiDefButS(block, ROW, evt_change, "Smear", butw*2, yco,butw,19, &settings->imapaint.tool, 7.0, PAINT_TOOL_SMEAR, 0, 0, "Smear brush");
- if (sima)
- uiDefButS(block, ROW, evt_change, "Clone", butw*3, yco,butw,19, &settings->imapaint.tool, 7.0, PAINT_TOOL_CLONE, 0, 0, "Clone brush, use RMB to drag source image");
+ but_idx = 0;
+ uiDefButS(block, ROW, evt_change, "Draw", butw*(but_idx++),yco,butw,19, &settings->imapaint.tool, 7.0, PAINT_TOOL_DRAW, 0, 0, "Draw brush");
+ if (sima || do_project==0)
+ uiDefButS(block, ROW, evt_change, "Soften", butw*(but_idx++), yco,butw,19, &settings->imapaint.tool, 7.0, PAINT_TOOL_SOFTEN, 0, 0, "Soften brush");
+ uiDefButS(block, ROW, evt_change, "Smear", butw*(but_idx++), yco,butw,19, &settings->imapaint.tool, 7.0, PAINT_TOOL_SMEAR, 0, 0, "Smear brush");
+ if (sima || do_project)
+ uiDefButS(block, ROW, evt_change, "Clone", butw*(but_idx++), yco,butw,19, &settings->imapaint.tool, 7.0, PAINT_TOOL_CLONE, 0, 0, "Clone brush, use RMB to drag source image");
uiBlockEndAlign(block);
yco -= 30;
@@ -6307,16 +6369,44 @@ void brush_buttons(uiBlock *block, short sima,
butw= 320-(xco+10);
uiDefButS(block, MENU, evt_nop, "Mix %x0|Add %x1|Subtract %x2|Multiply %x3|Lighten %x4|Darken %x5|Erase Alpha %x6|Add Alpha %x7", xco+10,yco,butw,19, &brush->blend, 0, 0, 0, 0, "Blending method for applying brushes");
-
- uiDefButBitS(block, TOG|BIT, BRUSH_TORUS, evt_change, "Wrap", xco+10,yco-25,butw,19, &brush->flag, 0, 0, 0, 0, "Enables torus wrapping");
-
+
uiBlockBeginAlign(block);
- uiDefButBitS(block, TOG|BIT, BRUSH_AIRBRUSH, evt_change, "Airbrush", xco+10,yco-50,butw,19, &brush->flag, 0, 0, 0, 0, "Keep applying paint effect while holding mouse (spray)");
- uiDefButF(block, NUM, evt_nop, "Rate ", xco+10,yco-70,butw,19, &brush->rate, 0.01, 1.0, 0, 0, "Number of paints per second for Airbrush");
+ uiDefButBitS(block, TOG|BIT, BRUSH_AIRBRUSH, evt_change, "Airbrush", xco+10,yco-25,butw/2,19, &brush->flag, 0, 0, 0, 0, "Keep applying paint effect while holding mouse (spray)");
+ uiDefButF(block, NUM, evt_nop, "", xco+10 + butw/2,yco-25,butw/2,19, &brush->rate, 0.01, 1.0, 0, 0, "Number of paints per second for Airbrush");
uiBlockEndAlign(block);
-
- yco -= 25;
-
+
+ if (sima) {
+ uiDefButBitS(block, TOG|BIT, BRUSH_TORUS, evt_change, "Wrap", xco+10,yco-45,butw,19, &brush->flag, 0, 0, 0, 0, "Enables torus wrapping");
+ yco -= 25;
+ }
+ else {
+ yco -= 25;
+ uiBlockBeginAlign(block);
+ uiDefButBitS(block, TOGN|BIT, IMAGEPAINT_PROJECT_DISABLE, B_REDR, "Project Paint", xco+10,yco-25,butw,19, &settings->imapaint.flag, 0, 0, 0, 0, "Use projection painting for improved consistency in the brush strokes");
+
+ if ((settings->imapaint.flag & IMAGEPAINT_PROJECT_DISABLE)==0) {
+
+
+ /* Projection Painting */
+
+ uiDefButBitS(block, TOGN|BIT, IMAGEPAINT_PROJECT_XRAY, B_NOP, "Occlude", xco+10,yco-45,butw/2,19, &settings->imapaint.flag, 0, 0, 0, 0, "Only paint onto the faces directly under the brush (slower)");
+ uiDefButBitS(block, TOGN|BIT, IMAGEPAINT_PROJECT_BACKFACE, B_NOP, "Cull", xco+10+butw/2,yco-45,butw/2,19, &settings->imapaint.flag, 0, 0, 0, 0, "Ignore faces pointing away from the view (faster)");
+
+ uiDefButBitS(block, TOGN|BIT, IMAGEPAINT_PROJECT_FLAT, B_NOP, "Normal", xco+10,yco-65,butw/2,19, &settings->imapaint.flag, 0, 0, 0, 0, "Paint most on faces pointing towards the view");
+ uiDefButS(block, NUM, B_NOP, "", xco+10 +(butw/2),yco-65,butw/2,19, &settings->imapaint.normal_angle, 10.0, 90.0, 0, 0, "Paint most on faces pointing towards the view acording to this angle");
+
+ uiDefButS(block, NUM, B_NOP, "Bleed: ", xco+10,yco-85,butw,19, &settings->imapaint.seam_bleed, 0.0, 8.0, 0, 0, "Extend paint beyond the faces UVs to reduce seams (in pixels, slower)");
+ uiBlockEndAlign(block);
+
+ uiBlockBeginAlign(block);
+ uiDefButBitS(block, TOG|BIT, IMAGEPAINT_PROJECT_LAYER_MASK, B_NOP, "Stencil Layer", xco+10,yco-110,butw-30,19, &settings->imapaint.flag, 0, 0, 0, 0, "Set the mask layer from the UV layer buttons");
+ uiDefButBitS(block, TOG|BIT, IMAGEPAINT_PROJECT_LAYER_MASK_INV, B_NOP, "Inv", xco+10 + butw-30,yco-110,30,19, &settings->imapaint.flag, 0, 0, 0, 0, "Invert the mask");
+ uiBlockEndAlign(block);
+
+ }
+ uiBlockEndAlign(block);
+ }
+
uiBlockBeginAlign(block);
uiDefButF(block, COL, B_VPCOLSLI, "", 0,yco,200,19, brush->rgb, 0, 0, 0, 0, "");
uiDefButF(block, NUMSLI, evt_nop, "Opacity ", 0,yco-20,180,19, &brush->alpha, 0.0, 1.0, 0, 0, "The amount of pressure on the brush");
diff --git a/source/blender/src/drawview.c b/source/blender/src/drawview.c
index de3e464060d..0942d5d1cdb 100644
--- a/source/blender/src/drawview.c
+++ b/source/blender/src/drawview.c
@@ -3309,7 +3309,12 @@ void drawview3dspace(ScrArea *sa, void *spacedata)
fdrawXORcirc((float)c[0], (float)c[1], (float)pset->brush[pset->brushtype].size);
}
}
-
+ if(!G.obedit && OBACT && G.f&G_TEXTUREPAINT && area_is_active_area(v3d->area) && G.scene->toolsettings->imapaint.brush){
+ short c[2];
+ getmouseco_areawin(c);
+ fdrawXORcirc((float)c[0], (float)c[1], (float)G.scene->toolsettings->imapaint.brush->size/2);
+ }
+
if(v3d->persp>1) drawviewborder();
if(v3d->flag2 & V3D_FLYMODE) drawviewborder_flymode();
diff --git a/source/blender/src/imagepaint.c b/source/blender/src/imagepaint.c
index 149dbf1d026..96f10dbf933 100644
--- a/source/blender/src/imagepaint.c
+++ b/source/blender/src/imagepaint.c
@@ -23,9 +23,9 @@
* The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
* All rights reserved.
*
- * The Original Code is: all of this file.
+ * The Original Code is: some of this file.
*
- * Contributor(s): Jens Ole Wund (bjornmose)
+ * Contributor(s): Jens Ole Wund (bjornmose), Campbell Barton (ideasman42)
*
* ***** END GPL LICENSE BLOCK *****
*/
@@ -46,7 +46,10 @@
#include "BLI_arithb.h"
#include "BLI_blenlib.h"
#include "BLI_dynstr.h"
+#include "BLI_linklist.h"
+#include "BLI_memarena.h"
#include "PIL_time.h"
+#include "BLI_threads.h"
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
@@ -70,6 +73,7 @@
#include "BKE_mesh.h"
#include "BKE_node.h"
#include "BKE_utildefines.h"
+#include "BKE_DerivedMesh.h"
#include "BIF_interface.h"
#include "BIF_mywindow.h"
@@ -77,14 +81,16 @@
#include "BIF_space.h"
#include "BIF_toolbox.h"
+#include "BIF_editview.h" /* only for mouse_cursor - could remove this later */
+
#include "BSE_drawipo.h"
#include "BSE_node.h"
#include "BSE_trans_types.h"
#include "BSE_view.h"
+#include "BSE_drawview.h" /* view3d_test_clipping */
#include "BDR_imagepaint.h"
#include "BDR_vpaint.h"
-
#include "GPU_draw.h"
#include "GHOST_Types.h"
@@ -95,12 +101,13 @@
/* Defines and Structs */
-#define IMAPAINT_CHAR_TO_FLOAT(c) (c/255.0f)
+#define IMAPAINT_CHAR_TO_FLOAT(c) ((c)/255.0f)
+
+#define IMAPAINT_FLOAT_RGB_TO_CHAR(c, f) { (c)[0]=FTOCHAR((f)[0]); (c)[1]=FTOCHAR((f)[1]); (c)[2]=FTOCHAR((f)[2]); }
+#define IMAPAINT_FLOAT_RGBA_TO_CHAR(c, f) { (c)[0]=FTOCHAR((f)[0]); (c)[1]=FTOCHAR((f)[1]); (c)[2]=FTOCHAR((f)[2]); (c)[3]=FTOCHAR((f)[3]); }
-#define IMAPAINT_FLOAT_RGB_TO_CHAR(c, f) { c[0]=FTOCHAR(f[0]); \
- c[1]=FTOCHAR(f[1]); c[2]=FTOCHAR(f[2]); }
-#define IMAPAINT_CHAR_RGB_TO_FLOAT(f, c) { f[0]=IMAPAINT_CHAR_TO_FLOAT(c[0]); \
- f[1]=IMAPAINT_CHAR_TO_FLOAT(c[1]); f[2]=IMAPAINT_CHAR_TO_FLOAT(c[2]); }
+#define IMAPAINT_CHAR_RGB_TO_FLOAT(f, c) { (f)[0]=IMAPAINT_CHAR_TO_FLOAT((c)[0]); (f)[1]=IMAPAINT_CHAR_TO_FLOAT((c)[1]); (f)[2]=IMAPAINT_CHAR_TO_FLOAT((c)[2]); }
+#define IMAPAINT_CHAR_RGBA_TO_FLOAT(f, c) { (f)[0]=IMAPAINT_CHAR_TO_FLOAT((c)[0]); (f)[1]=IMAPAINT_CHAR_TO_FLOAT((c)[1]); (f)[2]=IMAPAINT_CHAR_TO_FLOAT((c)[2]); (f)[3]=IMAPAINT_CHAR_TO_FLOAT((c)[3]); }
#define IMAPAINT_FLOAT_RGB_COPY(a, b) VECCOPY(a, b)
#define IMAPAINT_TILE_BITS 6
@@ -109,6 +116,9 @@
#define MAXUNDONAME 64
+static void imapaint_image_update(Image *image, ImBuf *ibuf, short texpaint);
+
+
typedef struct ImagePaintState {
Brush *brush;
short tool, blend;
@@ -126,6 +136,194 @@ typedef struct ImagePaintState {
float uv[2];
} ImagePaintState;
+typedef struct ImagePaintPartialRedraw {
+ int x1, y1, x2, y2;
+ int enabled;
+} ImagePaintPartialRedraw;
+
+
+/* ProjectionPaint defines */
+
+/* approx the number of buckets to have under the brush,
+ * used with the brush size to set the ps->buckets_x and ps->buckets_y value.
+ *
+ * When 3 - a brush should have ~9 buckets under it at once
+ * ...this helps for threading while painting as well as
+ * avoiding initializing pixels that wont touch the brush */
+#define PROJ_BUCKET_BRUSH_DIV 4
+
+#define PROJ_BUCKET_RECT_MIN 4
+#define PROJ_BUCKET_RECT_MAX 256
+
+#define PROJ_BOUNDBOX_DIV 8
+#define PROJ_BOUNDBOX_SQUARED (PROJ_BOUNDBOX_DIV * PROJ_BOUNDBOX_DIV)
+
+//#define PROJ_DEBUG_PAINT 1
+//#define PROJ_DEBUG_NOSEAMBLEED 1
+//#define PROJ_DEBUG_PRINT_CLIP 1
+#define PROJ_DEBUG_WINCLIP 1
+
+/* projectFaceSeamFlags options */
+//#define PROJ_FACE_IGNORE 1<<0 /* When the face is hidden, backfacing or occluded */
+//#define PROJ_FACE_INIT 1<<1 /* When we have initialized the faces data */
+#define PROJ_FACE_SEAM1 1<<0 /* If this face has a seam on any of its edges */
+#define PROJ_FACE_SEAM2 1<<1
+#define PROJ_FACE_SEAM3 1<<2
+#define PROJ_FACE_SEAM4 1<<3
+
+#define PROJ_FACE_NOSEAM1 1<<4
+#define PROJ_FACE_NOSEAM2 1<<5
+#define PROJ_FACE_NOSEAM3 1<<6
+#define PROJ_FACE_NOSEAM4 1<<7
+
+/* a slightly scaled down face is used to get fake 3D location for edge pixels in the seams
+ * as this number approaches 1.0f the likelihood increases of float precision errors where
+ * it is occluded by an adjacent face */
+#define PROJ_FACE_SCALE_SEAM 0.99f
+
+#define PROJ_BUCKET_NULL 0
+#define PROJ_BUCKET_INIT 1<<0
+// #define PROJ_BUCKET_CLONE_INIT 1<<1
+
+/* used for testing doubles, if a point is on a line etc */
+#define PROJ_GEOM_TOLERANCE 0.0002f
+
+/* vert flags */
+#define PROJ_VERT_CULL 1
+
+#define PI_80_DEG ((M_PI_2 / 9) * 8)
+
+/* This is mainly a convenience struct used so we can keep an array of images we use
+ * Thir imbufs, etc, in 1 array, When using threads this array is copied for each thread
+ * because 'partRedrawRect' and 'touch' values would not be thread safe */
+typedef struct ProjPaintImage {
+ Image *ima;
+ ImBuf *ibuf;
+ ImagePaintPartialRedraw *partRedrawRect;
+ struct UndoTile **undoRect; /* only used to build undo tiles after painting */
+ int touch;
+} ProjPaintImage;
+
+/* Main projection painting struct passed to all projection painting functions */
+typedef struct ProjPaintState {
+ Brush *brush;
+ short tool, blend;
+ Object *ob;
+ /* end similarities with ImagePaintState */
+
+ DerivedMesh *dm;
+ int dm_totface;
+ int dm_totvert;
+
+ MVert *dm_mvert;
+ MFace *dm_mface;
+ MTFace *dm_mtface;
+ MTFace *dm_mtface_clone; /* other UV layer, use for cloning between layers */
+ MTFace *dm_mtface_mask;
+
+ /* projection painting only */
+ MemArena *arena_mt[BLENDER_MAX_THREADS];/* for multithreading, the first item is sometimes used for non threaded cases too */
+ LinkNode **bucketRect; /* screen sized 2D array, each pixel has a linked list of ProjPixel's */
+ LinkNode **bucketFaces; /* bucketRect aligned array linkList of faces overlapping each bucket */
+ unsigned char *bucketFlags; /* store if the bucks have been initialized */
+#ifndef PROJ_DEBUG_NOSEAMBLEED
+ char *faceSeamFlags; /* store info about faces, if they are initialized etc*/
+ float (*faceSeamUVs)[4][2]; /* expanded UVs for faces to use as seams */
+ LinkNode **vertFaces; /* Only needed for when seam_bleed_px is enabled, use to find UV seams */
+ char *vertFlags; /* store options per vert, now only store if the vert is pointing away from the view */
+#endif
+ int buckets_x; /* The size of the bucket grid, the grid span's screenMin/screenMax so you can paint outsize the screen or with 2 brushes at once */
+ int buckets_y;
+
+ ProjPaintImage *projImages;
+
+ int image_tot; /* size of projectImages array */
+
+ float (*screenCoords)[4]; /* verts projected into floating point screen space */
+
+ float screenMin[2]; /* 2D bounds for mesh verts on the screen's plane (screenspace) */
+ float screenMax[2];
+ float screen_width; /* Calculated from screenMin & screenMax */
+ float screen_height;
+
+ /* options for projection painting */
+ int do_layer_clone;
+ int do_layer_mask;
+ int do_layer_mask_inv;
+
+ short do_occlude; /* Use raytraced occlusion? - ortherwise will paint right through to the back*/
+ short do_backfacecull; /* ignore faces with normals pointing away, skips a lot of raycasts if your normals are correctly flipped */
+ short do_mask_normal; /* mask out pixels based on their normals */
+ float normal_angle; /* what angle to mask at*/
+ float normal_angle_inner;
+ float normal_angle_range; /* difference between normal_angle and normal_angle_inner, for easy access */
+
+ short is_ortho;
+ short is_airbrush; /* only to avoid using (ps.brush->flag & BRUSH_AIRBRUSH) */
+ short is_texbrush; /* only to avoid running */
+#ifndef PROJ_DEBUG_NOSEAMBLEED
+ float seam_bleed_px;
+#endif
+ /* clone vars */
+ float cloneOffset[2];
+
+ float projectMat[4][4]; /* Projection matrix, use for getting screen coords */
+ float viewMat[4][4];
+ float viewDir[3]; /* View vector, use for do_backfacecull and for ray casting with an ortho viewport */
+ float viewPos[3]; /* View location in object relative 3D space, so can compare to verts */
+ float clipsta, clipend;
+
+ /* threads */
+ int thread_tot;
+ int bucketMin[2];
+ int bucketMax[2];
+ int context_bucket_x, context_bucket_y; /* must lock threads while accessing these */
+} ProjPaintState;
+
+typedef union pixelPointer
+{
+ float *f_pt; /* float buffer */
+ unsigned int *uint_pt; /* 2 ways to access a char buffer */
+ unsigned char *ch_pt;
+} PixelPointer;
+
+typedef union pixelStore
+{
+ unsigned char ch[4];
+ unsigned int uint;
+ float f[4];
+} PixelStore;
+
+typedef struct ProjPixel {
+ float projCoSS[2]; /* the floating point screen projection of this pixel */
+
+ /* Only used when the airbrush is disabled.
+ * Store the max mask value to avoid painting over an area with a lower opacity
+ * with an advantage that we can avoid touching the pixel at all, if the
+ * new mask value is lower then mask_max */
+ unsigned short mask_max;
+
+ /* for various reasons we may want to mask out painting onto this pixel */
+ unsigned short mask;
+
+ short x_px, y_px;
+
+ PixelStore origColor;
+ PixelStore newColor;
+ PixelPointer pixel;
+
+ short image_index; /* if anyone wants to paint onto more then 32768 images they can bite me */
+ unsigned char bb_cell_index;
+} ProjPixel;
+
+typedef struct ProjPixelClone {
+ struct ProjPixel __pp;
+ PixelStore clonepx;
+} ProjPixelClone;
+
+/* Finish projection painting structs */
+
+
typedef struct UndoTile {
struct UndoTile *next, *prev;
ID id;
@@ -142,11 +340,6 @@ typedef struct UndoElem {
ListBase tiles;
} UndoElem;
-typedef struct ImagePaintPartialRedraw {
- int x1, y1, x2, y2;
- int enabled;
-} ImagePaintPartialRedraw;
-
static ListBase undobase = {NULL, NULL};
static UndoElem *curundo = NULL;
static ImagePaintPartialRedraw imapaintpartial = {0, 0, 0, 0, 0};
@@ -161,14 +354,42 @@ static void undo_copy_tile(UndoTile *tile, ImBuf *tmpibuf, ImBuf *ibuf, int rest
IMB_rectcpy(tmpibuf, ibuf, 0, 0, tile->x*IMAPAINT_TILE_SIZE,
tile->y*IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE);
- if(ibuf->rect_float) SWAP(void*, tmpibuf->rect_float, tile->rect)
- else SWAP(void*, tmpibuf->rect, tile->rect)
+ if(ibuf->rect_float) {
+ SWAP(void*, tmpibuf->rect_float, tile->rect);
+ } else {
+ SWAP(void*, tmpibuf->rect, tile->rect);
+ }
if(restore)
IMB_rectcpy(ibuf, tmpibuf, tile->x*IMAPAINT_TILE_SIZE,
tile->y*IMAPAINT_TILE_SIZE, 0, 0, IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE);
}
+static UndoTile *undo_init_tile(ID *id, ImBuf *ibuf, ImBuf **tmpibuf, int x_tile, int y_tile)
+{
+ UndoTile *tile;
+ int allocsize;
+
+ if (*tmpibuf==NULL)
+ *tmpibuf = IMB_allocImBuf(IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE, 32, IB_rectfloat|IB_rect, 0);
+
+ tile= MEM_callocN(sizeof(UndoTile), "ImaUndoTile");
+ tile->id= *id;
+ tile->x= x_tile;
+ tile->y= y_tile;
+
+ allocsize= IMAPAINT_TILE_SIZE*IMAPAINT_TILE_SIZE*4;
+ allocsize *= (ibuf->rect_float)? sizeof(float): sizeof(char);
+ tile->rect= MEM_mapallocN(allocsize, "ImaUndoRect");
+
+ undo_copy_tile(tile, *tmpibuf, ibuf, 0);
+ curundo->undosize += allocsize;
+
+ BLI_addtail(&curundo->tiles, tile);
+
+ return tile;
+}
+
static void undo_restore(UndoElem *undo)
{
Image *ima = NULL;
@@ -281,9 +502,3399 @@ static void undo_imagepaint_push_end()
}
}
-/* external functions */
+/* fast projection bucket array lookup, use the safe version for bound checking */
+static int project_bucket_offset(const ProjPaintState *ps, const float projCoSS[2])
+{
+ /* If we were not dealing with screenspace 2D coords we could simple do...
+ * ps->bucketRect[x + (y*ps->buckets_y)] */
+
+ /* please explain?
+ * projCoSS[0] - ps->screenMin[0] : zero origin
+ * ... / ps->screen_width : range from 0.0 to 1.0
+ * ... * ps->buckets_x : use as a bucket index
+ *
+ * Second multiplication does similar but for vertical offset
+ */
+ return ( (int)(((projCoSS[0] - ps->screenMin[0]) / ps->screen_width) * ps->buckets_x)) +
+ ( ( (int)(((projCoSS[1] - ps->screenMin[1]) / ps->screen_height) * ps->buckets_y)) * ps->buckets_x);
+}
+
+static int project_bucket_offset_safe(const ProjPaintState *ps, const float projCoSS[2])
+{
+ int bucket_index = project_bucket_offset(ps, projCoSS);
+
+ if (bucket_index < 0 || bucket_index >= ps->buckets_x*ps->buckets_y) {
+ return -1;
+ }
+ else {
+ return bucket_index;
+ }
+}
+
+#define SIDE_OF_LINE(pa, pb, pp) ((pa[0]-pp[0])*(pb[1]-pp[1]))-((pb[0]-pp[0])*(pa[1]-pp[1]))
+
+static float AreaSignedF2Dfl(float *v1, float *v2, float *v3)
+{
+ return (float)(0.5f*((v1[0]-v2[0])*(v2[1]-v3[1]) +
+(v1[1]-v2[1])*(v3[0]-v2[0])));
+}
+
+static void BarycentricWeights2f(float v1[2], float v2[2], float v3[2], float pt[2], float w[3])
+{
+ float wtot_inv, wtot, wsign[3];
+
+ wsign[0] = AreaSignedF2Dfl(v2, v3, pt);
+ wsign[1] = AreaSignedF2Dfl(v3, v1, pt);
+ wsign[2] = AreaSignedF2Dfl(v1, v2, pt);
+ wtot = wsign[0]+wsign[1]+wsign[2];
+
+ if (fabs(wtot) > 0.0f) {
+ wtot_inv = 1.0f/wtot;
+
+ w[0] = wsign[0]*wtot_inv;
+ w[1] = wsign[1]*wtot_inv;
+ w[2] = wsign[2]*wtot_inv;
+ }
+ else /* dummy values for zero area face */
+ w[0] = w[1] = w[2] = 1.0f/3.0f;
+}
+
+/* still use 2D X,Y space but this works for verts transformed by a perspective matrix, using their 4th component as a weight */
+
+static void BarycentricWeightsPersp2f(float v1[4], float v2[4], float v3[4], float pt[2], float w[3])
+{
+ float persp_tot, persp_tot_inv;
+ BarycentricWeights2f(v1, v2, v3, pt, w);
+
+ w[0] /= v1[3];
+ w[1] /= v2[3];
+ w[2] /= v3[3];
+
+ persp_tot = w[0]+w[1]+w[2];
+ if (persp_tot > 0.0f) {
+ persp_tot_inv = 1.0f / persp_tot;
+ w[0] *= persp_tot_inv;
+ w[1] *= persp_tot_inv;
+ w[2] *= persp_tot_inv;
+ }
+ else {
+ w[0] = w[1] = w[2] = 1.0f/3.0f; /* dummy values for zero area face */
+ }
+}
+
+static void VecWeightf(float p[3], const float v1[3], const float v2[3], const float v3[3], const float w[3])
+{
+ p[0] = v1[0]*w[0] + v2[0]*w[1] + v3[0]*w[2];
+ p[1] = v1[1]*w[0] + v2[1]*w[1] + v3[1]*w[2];
+ p[2] = v1[2]*w[0] + v2[2]*w[1] + v3[2]*w[2];
+}
+
+static void Vec2Weightf(float p[2], const float v1[2], const float v2[2], const float v3[2], const float w[3])
+{
+ p[0] = v1[0]*w[0] + v2[0]*w[1] + v3[0]*w[2];
+ p[1] = v1[1]*w[0] + v2[1]*w[1] + v3[1]*w[2];
+}
+
+static float tri_depth_2d(float v1[3], float v2[3], float v3[3], float pt[2], float w[3])
+{
+ BarycentricWeights2f(v1, v2, v3, pt, w);
+ return (v1[2]*w[0]) + (v2[2]*w[1]) + (v3[2]*w[2]);
+}
+
+
+/* Return the top-most face index that the screen space coord 'pt' touches (or -1) */
+static int project_paint_PickFace(const ProjPaintState *ps, float pt[2], float w[3], int *side)
+{
+ LinkNode *node;
+ float w_tmp[3];
+ float *v1, *v2, *v3, *v4;
+ int bucket_index;
+ int face_index;
+ int best_side = -1;
+ int best_face_index = -1;
+ float z_depth_best = MAXFLOAT, z_depth;
+ MFace *mf;
+
+ bucket_index = project_bucket_offset_safe(ps, pt);
+ if (bucket_index==-1)
+ return -1;
+
+
+
+ /* we could return 0 for 1 face buckets, as long as this function assumes
+ * that the point its testing is only every originated from an existing face */
+
+ for (node= ps->bucketFaces[bucket_index]; node; node= node->next) {
+ face_index = GET_INT_FROM_POINTER(node->link);
+ mf= ps->dm_mface + face_index;
+
+ v1= ps->screenCoords[mf->v1];
+ v2= ps->screenCoords[mf->v2];
+ v3= ps->screenCoords[mf->v3];
+
+ if (IsectPT2Df(pt, v1, v2, v3)) {
+ z_depth= tri_depth_2d(v1, v2, v3, pt, w_tmp);
+ if (z_depth < z_depth_best) {
+ best_face_index = face_index;
+ best_side = 0;
+ z_depth_best = z_depth;
+ VECCOPY(w, w_tmp);
+ }
+ }
+ else if (mf->v4) {
+ v4= ps->screenCoords[mf->v4];
+
+ if (IsectPT2Df(pt, v1, v3, v4)) {
+ z_depth= tri_depth_2d(v1, v3, v4, pt, w_tmp);
+ if (z_depth < z_depth_best) {
+ best_face_index = face_index;
+ best_side= 1;
+ z_depth_best = z_depth;
+ VECCOPY(w, w_tmp);
+ }
+ }
+ }
+ }
+
+ *side = best_side;
+ return best_face_index; /* will be -1 or a valid face */
+}
+
+/* Converts a uv coord into a pixel location wrapping if the uv is outside 0-1 range */
+static void uvco_to_wrapped_pxco(float uv[2], int ibuf_x, int ibuf_y, float *x, float *y)
+{
+ /* use */
+ *x = (float)fmod(uv[0], 1.0f);
+ *y = (float)fmod(uv[1], 1.0f);
+
+ if (*x < 0.0f) *x += 1.0f;
+ if (*y < 0.0f) *y += 1.0f;
+
+ *x = *x * ibuf_x - 0.5f;
+ *y = *y * ibuf_y - 0.5f;
+}
+
+/* Set the top-most face color that the screen space coord 'pt' touches (or return 0 if none touch) */
+static int project_paint_PickColor(const ProjPaintState *ps, float pt[2], float *rgba_fp, unsigned char *rgba, const int interp)
+{
+ float w[3], uv[2];
+ int side;
+ int face_index;
+ MTFace *tf;
+ ImBuf *ibuf;
+ int xi, yi;
+
+
+ face_index = project_paint_PickFace(ps, pt, w, &side);
+
+ if (face_index == -1)
+ return 0;
+
+ tf = ps->dm_mtface + face_index;
+
+ if (side == 0) {
+ Vec2Weightf(uv, tf->uv[0], tf->uv[1], tf->uv[2], w);
+ }
+ else { /* QUAD */
+ Vec2Weightf(uv, tf->uv[0], tf->uv[2], tf->uv[3], w);
+ }
+
+ ibuf = BKE_image_get_ibuf((Image *)tf->tpage, NULL); /* TODO - this may be slow, the only way around it is to have an ibuf index per face */
+
+
+
+ if (interp) {
+ float x, y;
+ uvco_to_wrapped_pxco(uv, ibuf->x, ibuf->y, &x, &y);
+
+ if (ibuf->rect_float) {
+ if (rgba_fp) {
+ bilinear_interpolation_color(ibuf, NULL, rgba_fp, x, y);
+ }
+ else {
+ float rgba_tmp_f[4];
+ bilinear_interpolation_color(ibuf, NULL, rgba_tmp_f, x, y);
+ IMAPAINT_FLOAT_RGBA_TO_CHAR(rgba, rgba_tmp_f);
+ }
+ }
+ else {
+ if (rgba) {
+ bilinear_interpolation_color(ibuf, rgba, NULL, x, y);
+ }
+ else {
+ unsigned char rgba_tmp[4];
+ bilinear_interpolation_color(ibuf, rgba_tmp, NULL, x, y);
+ IMAPAINT_CHAR_RGBA_TO_FLOAT(rgba_fp, rgba_tmp);
+ }
+ }
+ }
+ else {
+ xi = (uv[0]*ibuf->x) + 0.5f;
+ yi = (uv[1]*ibuf->y) + 0.5f;
+
+ //if (xi<0 || xi>=ibuf->x || yi<0 || yi>=ibuf->y) return 0;
+
+ /* wrap */
+ xi = ((int)(uv[0]*ibuf->x)) % ibuf->x;
+ if (xi<0) xi += ibuf->x;
+ yi = ((int)(uv[1]*ibuf->y)) % ibuf->y;
+ if (yi<0) yi += ibuf->y;
+
+
+ if (rgba) {
+ if (ibuf->rect_float) {
+ float *rgba_tmp_fp = ibuf->rect_float + (xi + yi * ibuf->x * 4);
+ IMAPAINT_FLOAT_RGBA_TO_CHAR(rgba, rgba_tmp_fp);
+ }
+ else {
+ *((unsigned int *)rgba) = *(unsigned int *)(((char *)ibuf->rect) + ((xi + yi * ibuf->x) * 4));
+ }
+ }
+
+ if (rgba_fp) {
+ if (ibuf->rect_float) {
+ QUATCOPY(rgba_fp, ((float *)ibuf->rect_float + ((xi + yi * ibuf->x) * 4)));
+ }
+ else {
+ char *tmp_ch= ((char *)ibuf->rect) + ((xi + yi * ibuf->x) * 4);
+ IMAPAINT_CHAR_RGBA_TO_FLOAT(rgba_fp, tmp_ch);
+ }
+ }
+ }
+ return 1;
+}
+
+/* Check if 'pt' is infront of the 3 verts on the Z axis (used for screenspace occlusuion test)
+ * return...
+ * 0 : no occlusion
+ * -1 : no occlusion but 2D intersection is true (avoid testing the other half of a quad)
+ * 1 : occluded */
+
+static int project_paint_occlude_ptv(float pt[3], float v1[3], float v2[3], float v3[3])
+{
+ /* if all are behind us, return false */
+ if(v1[2] > pt[2] && v2[2] > pt[2] && v3[2] > pt[2])
+ return 0;
+
+ /* do a 2D point in try intersection */
+ if (!IsectPT2Df(pt, v1, v2, v3))
+ return 0; /* we know there is */
+
+
+ /* From here on we know there IS an intersection */
+ /* if ALL of the verts are infront of us then we know it intersects ? */
+ if(v1[2] < pt[2] && v2[2] < pt[2] && v3[2] < pt[2]) {
+ return 1;
+ }
+ else {
+ float w[3];
+ /* we intersect? - find the exact depth at the point of intersection */
+ if (tri_depth_2d(v1, v2, v3, pt, w) < pt[2]) {
+ return 1; /* This point is occluded by another face */
+ }
+ }
+ return -1;
+}
+
+
+static int project_paint_occlude_ptv_clip(
+ const ProjPaintState *ps, const MFace *mf,
+ float pt[3], float v1[3], float v2[3], float v3[3],
+ const int side )
+{
+ float w[3], wco[3];
+
+ /* if all are behind us, return false */
+ if(v1[2] > pt[2] && v2[2] > pt[2] && v3[2] > pt[2])
+ return 0;
+
+ /* do a 2D point in try intersection */
+ if (!IsectPT2Df(pt, v1, v2, v3))
+ return 0; /* we know there is */
+
+ /* we intersect? - find the exact depth at the point of intersection */
+ if (tri_depth_2d(v1, v2, v3, pt, w) > pt[2])
+ return -1;
+
+ if (side) VecWeightf(wco, ps->dm_mvert[mf->v1].co, ps->dm_mvert[mf->v3].co, ps->dm_mvert[mf->v4].co, w);
+ else VecWeightf(wco, ps->dm_mvert[mf->v1].co, ps->dm_mvert[mf->v2].co, ps->dm_mvert[mf->v3].co, w);
+
+ Mat4MulVecfl(ps->ob->obmat, wco);
+ if(!view3d_test_clipping(G.vd, wco)) {
+ return 1;
+ }
+
+ return -1;
+}
+
+
+/* Check if a screenspace location is occluded by any other faces
+ * check, pixelScreenCo must be in screenspace, its Z-Depth only needs to be used for comparison
+ * and dosn't need to be correct in relation to X and Y coords (this is the case in perspective view) */
+static int project_bucket_point_occluded(const ProjPaintState *ps, LinkNode *bucketFace, const int orig_face, float pixelScreenCo[4])
+{
+ MFace *mf;
+ int face_index;
+ int isect_ret;
+
+ /* 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 */
+
+ if(G.vd->flag & V3D_CLIPPING) {
+ for (; bucketFace; bucketFace = bucketFace->next) {
+ face_index = (int)bucketFace->link;
+
+ if (orig_face != face_index) {
+ mf = ps->dm_mface + face_index;
+ isect_ret = project_paint_occlude_ptv_clip(ps, mf, pixelScreenCo, ps->screenCoords[mf->v1], ps->screenCoords[mf->v2], ps->screenCoords[mf->v3], 0);
+
+ /* Note, if isect_ret==-1 then we dont want to test the other side of the quad */
+ if (isect_ret==0 && mf->v4) {
+ isect_ret = project_paint_occlude_ptv_clip(ps, mf, pixelScreenCo, ps->screenCoords[mf->v1], ps->screenCoords[mf->v3], ps->screenCoords[mf->v4], 1);
+ }
+ 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;
+ }
+ }
+ }
+ }
+ else {
+ for (; bucketFace; bucketFace = bucketFace->next) {
+ face_index = (int)bucketFace->link;
+
+ if (orig_face != face_index) {
+ mf = ps->dm_mface + face_index;
+ isect_ret = project_paint_occlude_ptv(pixelScreenCo, ps->screenCoords[mf->v1], ps->screenCoords[mf->v2], ps->screenCoords[mf->v3]);
+
+ /* Note, if isect_ret==-1 then we dont want to test the other side of the quad */
+ if (isect_ret==0 && mf->v4) {
+ isect_ret = project_paint_occlude_ptv(pixelScreenCo, ps->screenCoords[mf->v1], ps->screenCoords[mf->v3], ps->screenCoords[mf->v4]);
+ }
+ if (isect_ret==1) {
+ /* TODO - we may want to cache the first hit,
+ * it is not possible to swap the face order in the list anymore */
+ return 1;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+/* basic line intersection, could move to arithb.c, 2 points with a horiz line
+ * 1 for an intersection, 2 if the first point is aligned, 3 if the second point is aligned */
+#define ISECT_TRUE 1
+#define ISECT_TRUE_P1 2
+#define ISECT_TRUE_P2 3
+static int line_isect_y(const float p1[2], const float p2[2], const float y_level, float *x_isect)
+{
+ float y_diff;
+
+ if (y_level==p1[1]) { /* are we touching the first point? - no interpolation needed */
+ *x_isect = p1[0];
+ return ISECT_TRUE_P1;
+ }
+ if (y_level==p2[1]) { /* are we touching the second point? - no interpolation needed */
+ *x_isect = p2[0];
+ return ISECT_TRUE_P2;
+ }
+
+ y_diff= fabs(p1[1]-p2[1]); /* yuck, horizontal line, we cant do much here */
+
+ if (y_diff < 0.000001f) {
+ *x_isect = (p1[0]+p2[0]) * 0.5f;
+ return ISECT_TRUE;
+ }
+
+ if (p1[1] > y_level && p2[1] < y_level) {
+ *x_isect = (p2[0]*(p1[1]-y_level) + p1[0]*(y_level-p2[1])) / y_diff; /*(p1[1]-p2[1]);*/
+ return ISECT_TRUE;
+ }
+ else if (p1[1] < y_level && p2[1] > y_level) {
+ *x_isect = (p2[0]*(y_level-p1[1]) + p1[0]*(p2[1]-y_level)) / y_diff; /*(p2[1]-p1[1]);*/
+ return ISECT_TRUE;
+ }
+ else {
+ return 0;
+ }
+}
+
+static int line_isect_x(const float p1[2], const float p2[2], const float x_level, float *y_isect)
+{
+ float x_diff;
+
+ if (x_level==p1[0]) { /* are we touching the first point? - no interpolation needed */
+ *y_isect = p1[1];
+ return ISECT_TRUE_P1;
+ }
+ if (x_level==p2[0]) { /* are we touching the second point? - no interpolation needed */
+ *y_isect = p2[1];
+ return ISECT_TRUE_P2;
+ }
+
+ x_diff= fabs(p1[0]-p2[0]); /* yuck, horizontal line, we cant do much here */
+
+ if (x_diff < 0.000001) { /* yuck, vertical line, we cant do much here */
+ *y_isect = (p1[0]+p2[0]) * 0.5f;
+ return ISECT_TRUE;
+ }
+
+ if (p1[0] > x_level && p2[0] < x_level) {
+ *y_isect = (p2[1]*(p1[0]-x_level) + p1[1]*(x_level-p2[0])) / x_diff; /*(p1[0]-p2[0]);*/
+ return ISECT_TRUE;
+ }
+ else if (p1[0] < x_level && p2[0] > x_level) {
+ *y_isect = (p2[1]*(x_level-p1[0]) + p1[1]*(p2[0]-x_level)) / x_diff; /*(p2[0]-p1[0]);*/
+ return ISECT_TRUE;
+ }
+ else {
+ return 0;
+ }
+}
+
+/* simple func use for comparing UV locations to check if there are seams.
+ * Its possible this gives incorrect results, when the UVs for 1 face go into the next
+ * tile, but do not do this for the adjacent face, it could return a false positive.
+ * This is so unlikely that Id not worry about it. */
+static int cmp_uv(const float vec2a[2], const float vec2b[2])
+{
+ /* if the UV's are not between 0.0 and 1.0 */
+ float xa = (float)fmod(vec2a[0], 1.0f);
+ float ya = (float)fmod(vec2a[1], 1.0f);
+
+ float xb = (float)fmod(vec2b[0], 1.0f);
+ float yb = (float)fmod(vec2b[1], 1.0f);
+
+ if (xa < 0.0f) xa += 1.0f;
+ if (ya < 0.0f) ya += 1.0f;
+
+ if (xb < 0.0f) xb += 1.0f;
+ if (yb < 0.0f) yb += 1.0f;
+
+ return ((fabs(xa-xb) < PROJ_GEOM_TOLERANCE) && (fabs(ya-yb) < PROJ_GEOM_TOLERANCE)) ? 1:0;
+}
+
+
+/* set min_px and max_px to the image space bounds of the UV coords
+ * return zero if there is no area in the returned rectangle */
+static int pixel_bounds_uv(
+ const float uv1[2], const float uv2[2], const float uv3[2], const float uv4[2],
+ rcti *bounds_px,
+ const int ibuf_x, const int ibuf_y,
+ int is_quad
+) {
+ float min_uv[2], max_uv[2]; /* UV bounds */
+
+ INIT_MINMAX2(min_uv, max_uv);
+
+ DO_MINMAX2(uv1, min_uv, max_uv);
+ DO_MINMAX2(uv2, min_uv, max_uv);
+ DO_MINMAX2(uv3, min_uv, max_uv);
+ if (is_quad)
+ DO_MINMAX2(uv4, min_uv, max_uv);
+
+ bounds_px->xmin = (int)(ibuf_x * min_uv[0]);
+ bounds_px->ymin = (int)(ibuf_y * min_uv[1]);
+
+ bounds_px->xmax = (int)(ibuf_x * max_uv[0]) +1;
+ bounds_px->ymax = (int)(ibuf_y * max_uv[1]) +1;
+
+ /*printf("%d %d %d %d \n", min_px[0], min_px[1], max_px[0], max_px[1]);*/
+
+ /* face uses no UV area when quantized to pixels? */
+ return (bounds_px->xmin == bounds_px->xmax || bounds_px->ymin == bounds_px->ymax) ? 0 : 1;
+}
+
+static int pixel_bounds_array(float (* uv)[2], rcti *bounds_px, const int ibuf_x, const int ibuf_y, int tot)
+{
+ float min_uv[2], max_uv[2]; /* UV bounds */
+
+ if (tot==0) {
+ return 0;
+ }
+
+ INIT_MINMAX2(min_uv, max_uv);
+
+ while (tot--) {
+ DO_MINMAX2((*uv), min_uv, max_uv);
+ uv++;
+ }
+
+ bounds_px->xmin = (int)(ibuf_x * min_uv[0]);
+ bounds_px->ymin = (int)(ibuf_y * min_uv[1]);
+
+ bounds_px->xmax = (int)(ibuf_x * max_uv[0]) +1;
+ bounds_px->ymax = (int)(ibuf_y * max_uv[1]) +1;
+
+ /*printf("%d %d %d %d \n", min_px[0], min_px[1], max_px[0], max_px[1]);*/
+
+ /* face uses no UV area when quantized to pixels? */
+ return (bounds_px->xmin == bounds_px->xmax || bounds_px->ymin == bounds_px->ymax) ? 0 : 1;
+}
+
+#ifndef PROJ_DEBUG_NOSEAMBLEED
+
+/* This function returns 1 if this face has a seam along the 2 face-vert indicies
+ * 'orig_i1_fidx' and 'orig_i2_fidx' */
+static int check_seam(const ProjPaintState *ps, const int orig_face, const int orig_i1_fidx, const int orig_i2_fidx, int *other_face, int *orig_fidx)
+{
+ LinkNode *node;
+ int face_index;
+ int i1, i2;
+ int i1_fidx = -1, i2_fidx = -1; /* index in face */
+ MFace *mf;
+ MTFace *tf;
+ const MFace *orig_mf = ps->dm_mface + orig_face;
+ const MTFace *orig_tf = ps->dm_mtface + orig_face;
+
+ /* vert indicies from face vert order indicies */
+ i1 = (*(&orig_mf->v1 + orig_i1_fidx));
+ i2 = (*(&orig_mf->v1 + orig_i2_fidx));
+
+ for (node = ps->vertFaces[i1]; node; node = node->next) {
+ face_index = (int)node->link;
+ if (face_index != orig_face) {
+ mf = ps->dm_mface + face_index;
+ /* could check if the 2 faces images match here,
+ * but then there wouldn't be a way to return the opposite face's info */
+
+
+ /* We need to know the order of the verts in the adjacent face
+ * set the i1_fidx and i2_fidx to (0,1,2,3) */
+ if (mf->v1==i1) i1_fidx = 0;
+ else if (mf->v2==i1) i1_fidx = 1;
+ else if (mf->v3==i1) i1_fidx = 2;
+ else if (mf->v4 && mf->v4==i1) i1_fidx = 3;
+
+ if (mf->v1==i2) i2_fidx = 0;
+ else if (mf->v2==i2) i2_fidx = 1;
+ else if (mf->v3==i2) i2_fidx = 2;
+ else if (mf->v4 && mf->v4==i2) i2_fidx = 3;
+
+ /* Only need to check if 'i2_fidx' is valid because we know i1_fidx is the same vert on both faces */
+ if (i2_fidx != -1) {
+ /* This IS an adjacent face!, now lets check if the UVs are ok */
+ tf = ps->dm_mtface + face_index;
+
+ /* set up the other face */
+ *other_face = face_index;
+ *orig_fidx = (i1_fidx < i2_fidx) ? i1_fidx : i2_fidx;
+
+ /* first test if they have the same image */
+ if ( (orig_tf->tpage == tf->tpage) &&
+ cmp_uv(orig_tf->uv[orig_i1_fidx], tf->uv[i1_fidx]) &&
+ cmp_uv(orig_tf->uv[orig_i2_fidx], tf->uv[i2_fidx]) )
+ {
+ // printf("SEAM (NONE)\n");
+ return 0;
+
+ }
+ else {
+ // printf("SEAM (UV GAP)\n");
+ return 1;
+ }
+ }
+ }
+ }
+ // printf("SEAM (NO FACE)\n");
+ *other_face = -1;
+ return 1;
+}
+
+/* TODO - move to arithb.c */
+/* Converts an angle to a length that can be used for maintaining an even margin around UV's */
+static float angleToLength(float angle)
+{
+ // already accounted for
+ if (angle < 0.000001f) {
+ return 1.0f;
+ }
+ else {
+ return fabs(1.0f / cos(angle * (M_PI/180.0f)));
+ }
+}
+
+/* Calculate outset UV's, this is not the same as simply scaling the UVs,
+ * since the outset coords are a margin that keep an even distance from the original UV's,
+ * note that the image aspect is taken into account */
+static void uv_image_outset(float (*orig_uv)[2], float (*outset_uv)[2], const float scaler, const int ibuf_x, const int ibuf_y, const int is_quad)
+{
+ float a1, a2, a3, a4=0.0f;
+ float puv[4][2]; /* pixelspace uv's */
+ float no1[2], no2[2], no3[2], no4[2]; /* normals */
+ float dir1[2], dir2[2], dir3[2], dir4[2];
+ float ibuf_x_inv = 1.0f / (float)ibuf_x;
+ float ibuf_y_inv = 1.0f / (float)ibuf_y;
+
+ /* make UV's in pixel space so we can */
+ puv[0][0] = orig_uv[0][0] * ibuf_x;
+ puv[0][1] = orig_uv[0][1] * ibuf_y;
+
+ puv[1][0] = orig_uv[1][0] * ibuf_x;
+ puv[1][1] = orig_uv[1][1] * ibuf_y;
+
+ puv[2][0] = orig_uv[2][0] * ibuf_x;
+ puv[2][1] = orig_uv[2][1] * ibuf_y;
+
+ if (is_quad) {
+ puv[3][0] = orig_uv[3][0] * ibuf_x;
+ puv[3][1] = orig_uv[3][1] * ibuf_y;
+ }
+
+ /* face edge directions */
+ Vec2Subf(dir1, puv[1], puv[0]);
+ Vec2Subf(dir2, puv[2], puv[1]);
+ Normalize2(dir1);
+ Normalize2(dir2);
+
+ if (is_quad) {
+ Vec2Subf(dir3, puv[3], puv[2]);
+ Vec2Subf(dir4, puv[0], puv[3]);
+ Normalize2(dir3);
+ Normalize2(dir4);
+ }
+ else {
+ Vec2Subf(dir3, puv[0], puv[2]);
+ Normalize2(dir3);
+ }
+
+ if (is_quad) {
+ a1 = angleToLength(NormalizedVecAngle2_2D(dir4, dir1));
+ a2 = angleToLength(NormalizedVecAngle2_2D(dir1, dir2));
+ a3 = angleToLength(NormalizedVecAngle2_2D(dir2, dir3));
+ a4 = angleToLength(NormalizedVecAngle2_2D(dir3, dir4));
+ }
+ else {
+ a1 = angleToLength(NormalizedVecAngle2_2D(dir3, dir1));
+ a2 = angleToLength(NormalizedVecAngle2_2D(dir1, dir2));
+ a3 = angleToLength(NormalizedVecAngle2_2D(dir2, dir3));
+ }
+
+ if (is_quad) {
+ Vec2Subf(no1, dir4, dir1);
+ Vec2Subf(no2, dir1, dir2);
+ Vec2Subf(no3, dir2, dir3);
+ Vec2Subf(no4, dir3, dir4);
+ Normalize2(no1);
+ Normalize2(no2);
+ Normalize2(no3);
+ Normalize2(no4);
+ Vec2Mulf(no1, a1*scaler);
+ Vec2Mulf(no2, a2*scaler);
+ Vec2Mulf(no3, a3*scaler);
+ Vec2Mulf(no4, a4*scaler);
+ Vec2Addf(outset_uv[0], puv[0], no1);
+ Vec2Addf(outset_uv[1], puv[1], no2);
+ Vec2Addf(outset_uv[2], puv[2], no3);
+ Vec2Addf(outset_uv[3], puv[3], no4);
+ outset_uv[0][0] *= ibuf_x_inv;
+ outset_uv[0][1] *= ibuf_y_inv;
+
+ outset_uv[1][0] *= ibuf_x_inv;
+ outset_uv[1][1] *= ibuf_y_inv;
+
+ outset_uv[2][0] *= ibuf_x_inv;
+ outset_uv[2][1] *= ibuf_y_inv;
+
+ outset_uv[3][0] *= ibuf_x_inv;
+ outset_uv[3][1] *= ibuf_y_inv;
+ }
+ else {
+ Vec2Subf(no1, dir3, dir1);
+ Vec2Subf(no2, dir1, dir2);
+ Vec2Subf(no3, dir2, dir3);
+ Normalize2(no1);
+ Normalize2(no2);
+ Normalize2(no3);
+ Vec2Mulf(no1, a1*scaler);
+ Vec2Mulf(no2, a2*scaler);
+ Vec2Mulf(no3, a3*scaler);
+ Vec2Addf(outset_uv[0], puv[0], no1);
+ Vec2Addf(outset_uv[1], puv[1], no2);
+ Vec2Addf(outset_uv[2], puv[2], no3);
+ outset_uv[0][0] *= ibuf_x_inv;
+ outset_uv[0][1] *= ibuf_y_inv;
+
+ outset_uv[1][0] *= ibuf_x_inv;
+ outset_uv[1][1] *= ibuf_y_inv;
+
+ outset_uv[2][0] *= ibuf_x_inv;
+ outset_uv[2][1] *= ibuf_y_inv;
+ }
+}
+
+/*
+ * Be tricky with flags, first 4 bits are PROJ_FACE_SEAM1 to 4, last 4 bits are PROJ_FACE_NOSEAM1 to 4
+ * 1<<i - where i is (0-3)
+ *
+ * If we're multithreadng, make sure threads are locked when this is called
+ */
+static void project_face_seams_init(const ProjPaintState *ps, const int face_index, const int is_quad)
+{
+ int other_face, other_fidx; /* vars for the other face, we also set its flag */
+ int fidx1 = is_quad ? 3 : 2;
+ int fidx2 = 0; /* next fidx in the face (0,1,2,3) -> (1,2,3,0) or (0,1,2) -> (1,2,0) for a tri */
+
+ do {
+ if ((ps->faceSeamFlags[face_index] & (1<<fidx1|16<<fidx1)) == 0) {
+ if (check_seam(ps, face_index, fidx1, fidx2, &other_face, &other_fidx)) {
+ ps->faceSeamFlags[face_index] |= 1<<fidx1;
+ if (other_face != -1)
+ ps->faceSeamFlags[other_face] |= 1<<other_fidx;
+ }
+ else {
+ ps->faceSeamFlags[face_index] |= 16<<fidx1;
+ if (other_face != -1)
+ ps->faceSeamFlags[other_face] |= 16<<other_fidx; /* second 4 bits for disabled */
+ }
+ }
+
+ fidx2 = fidx1;
+ } while (fidx1--);
+}
+#endif // PROJ_DEBUG_NOSEAMBLEED
+
+
+/* TODO - move to arithb.c */
+
+/* little sister we only need to know lambda */
+static float lambda_cp_line2(const float p[2], const float l1[2], const float l2[2])
+{
+ float h[2], u[2];
+
+ u[0] = l2[0] - l1[0];
+ u[1] = l2[1] - l1[1];
+
+ h[0] = p[0] - l1[0];
+ h[1] = p[1] - l1[1];
+
+ return(Inp2f(u, h)/Inp2f(u, u));
+}
+
+
+/* Converts a UV location to a 3D screenspace location
+ * Takes a 'uv' and 3 UV coords, and sets the values of pixelScreenCo
+ *
+ * This is used for finding a pixels location in screenspace for painting */
+static void screen_px_from_ortho(
+ float uv[2],
+ float v1co[3], float v2co[3], float v3co[3], /* Screenspace coords */
+ float uv1co[2], float uv2co[2], float uv3co[2],
+ float pixelScreenCo[4],
+ float w[3])
+{
+ BarycentricWeights2f(uv1co, uv2co, uv3co, uv, w);
+ VecWeightf(pixelScreenCo, v1co, v2co, v3co, w);
+}
+
+/* same as screen_px_from_ortho except we need to take into account
+ * the perspective W coord for each vert */
+static void screen_px_from_persp(
+ float uv[2],
+ float v1co[3], float v2co[3], float v3co[3], /* screenspace coords */
+ float uv1co[2], float uv2co[2], float uv3co[2],
+ float pixelScreenCo[4],
+ float w[3])
+{
+
+ float wtot_inv, wtot;
+ BarycentricWeights2f(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.0/3.0; /* dummy values for zero area face */
+ }
+ /* done re-weighting */
+
+ VecWeightf(pixelScreenCo, v1co, v2co, v3co, w);
+}
+
+static void project_face_pixel(const MTFace *tf_other, ImBuf *ibuf_other, const float w[3], int side, unsigned char rgba_ub[4], float rgba_f[4])
+{
+ float *uvCo1, *uvCo2, *uvCo3;
+ float uv_other[2], x, y;
+
+ uvCo1 = (float *)tf_other->uv[0];
+ if (side==1) {
+ uvCo2 = (float *)tf_other->uv[2];
+ uvCo3 = (float *)tf_other->uv[3];
+ }
+ else {
+ uvCo2 = (float *)tf_other->uv[1];
+ uvCo3 = (float *)tf_other->uv[2];
+ }
+
+ Vec2Weightf(uv_other, uvCo1, uvCo2, uvCo3, w);
+
+ /* use */
+ uvco_to_wrapped_pxco(uv_other, ibuf_other->x, ibuf_other->y, &x, &y);
+
+
+ if (ibuf_other->rect_float) { /* from float to float */
+ bilinear_interpolation_color(ibuf_other, NULL, rgba_f, x, y);
+ }
+ else { /* from char to float */
+ bilinear_interpolation_color(ibuf_other, rgba_ub, NULL, x, y);
+ }
+
+}
+
+/* run this outside project_paint_uvpixel_init since pixels with mask 0 dont need init */
+float project_paint_uvpixel_mask(
+ const ProjPaintState *ps,
+ const int face_index,
+ const int side,
+ const float w[3])
+{
+ float mask, mask_angle;
+
+ /* Image Mask */
+ if (ps->do_layer_mask) {
+ /* another UV layers image is masking this one's */
+ ImBuf *ibuf_other;
+ const MTFace *tf_other = ps->dm_mtface_mask + face_index;
+
+ if (tf_other->tpage && (ibuf_other = BKE_image_get_ibuf((Image *)tf_other->tpage, NULL))) {
+ /* BKE_image_get_ibuf - TODO - this may be slow */
+ unsigned char rgba_ub[4];
+ float rgba_f[4];
+
+ project_face_pixel(tf_other, ibuf_other, w, side, rgba_ub, rgba_f);
+
+ if (ibuf_other->rect_float) { /* from float to float */
+ mask = ((rgba_f[0]+rgba_f[1]+rgba_f[2])/3.0f) * rgba_f[3];
+ }
+ else { /* from char to float */
+ mask = ((rgba_ub[0]+rgba_ub[1]+rgba_ub[2])/(256*3.0f)) * (rgba_ub[3]/256.0f);
+ }
+
+ if (!ps->do_layer_mask_inv) /* matching the gimps layer mask black/white rules, white==full opacity */
+ mask = (1.0f - mask);
+
+ if (mask == 0.0f) {
+ return 0.0f;
+ }
+ }
+ else {
+ return 0.0f;
+ }
+ } else {
+ mask = 1.0f;
+ }
+
+ /* calculate mask */
+ if (ps->do_mask_normal) {
+ MFace *mf = ps->dm_mface + face_index;
+ short *no1, *no2, *no3;
+ float no[3], angle;
+ no1 = ps->dm_mvert[mf->v1].no;
+ if (side==1) {
+ no2 = ps->dm_mvert[mf->v3].no;
+ no3 = ps->dm_mvert[mf->v4].no;
+ }
+ else {
+ no2 = ps->dm_mvert[mf->v2].no;
+ no3 = ps->dm_mvert[mf->v3].no;
+ }
+
+ no[0] = w[0]*no1[0] + w[1]*no2[0] + w[2]*no3[0];
+ no[1] = w[0]*no1[1] + w[1]*no2[1] + w[2]*no3[1];
+ no[2] = w[0]*no1[2] + w[1]*no2[2] + w[2]*no3[2];
+ Normalize(no);
+
+ /* now we can use the normal as a mask */
+ if (ps->is_ortho) {
+ angle = NormalizedVecAngle2((float *)ps->viewDir, no);
+ }
+ else {
+ /* Annoying but for the perspective view we need to get the pixels location in 3D space :/ */
+ float viewDirPersp[3];
+ float *co1, *co2, *co3;
+ co1 = ps->dm_mvert[mf->v1].co;
+ if (side==1) {
+ co2 = ps->dm_mvert[mf->v3].co;
+ co3 = ps->dm_mvert[mf->v4].co;
+ }
+ else {
+ co2 = ps->dm_mvert[mf->v2].co;
+ co3 = ps->dm_mvert[mf->v3].co;
+ }
+
+ /* Get the direction from the viewPoint to the pixel and normalize */
+ viewDirPersp[0] = (ps->viewPos[0] - (w[0]*co1[0] + w[1]*co2[0] + w[2]*co3[0]));
+ viewDirPersp[1] = (ps->viewPos[1] - (w[0]*co1[1] + w[1]*co2[1] + w[2]*co3[1]));
+ viewDirPersp[2] = (ps->viewPos[2] - (w[0]*co1[2] + w[1]*co2[2] + w[2]*co3[2]));
+ Normalize(viewDirPersp);
+
+ angle = NormalizedVecAngle2(viewDirPersp, no);
+ }
+
+ if (angle >= ps->normal_angle) {
+ return 0.0f; /* outsize the normal limit*/
+ }
+ else if (angle > ps->normal_angle_inner) {
+ mask *= (ps->normal_angle - angle) / ps->normal_angle_range;
+ } /* otherwise no mask normal is needed, were within the limit */
+ }
+
+ // This only works when the opacity dosnt change while painting, stylus pressure messes with this
+ // so dont use it.
+ // if (ps->is_airbrush==0) mask *= ps->brush->alpha;
+
+ return mask;
+}
+
+/* run this function when we know a bucket's, face's pixel can be initialized,
+ * return the ProjPixel which is added to 'ps->bucketRect[bucket_index]' */
+static ProjPixel *project_paint_uvpixel_init(
+ const ProjPaintState *ps,
+ MemArena *arena,
+ const ImBuf *ibuf,
+ short x_px, short y_px,
+ const float mask,
+ const int face_index,
+ const int image_index,
+ const float pixelScreenCo[4],
+ const int side,
+ const float w[3])
+{
+ ProjPixel *projPixel;
+ short size;
+
+ /* wrap pixel location */
+ x_px = x_px % ibuf->x;
+ if (x_px<0) x_px += ibuf->x;
+ y_px = y_px % ibuf->y;
+ if (y_px<0) y_px += ibuf->y;
+
+ if (ps->tool==PAINT_TOOL_CLONE) {
+ size = sizeof(ProjPixelClone);
+ }
+ else if (ps->tool==PAINT_TOOL_SMEAR) {
+ size = sizeof(ProjPixelClone);
+ }
+ else {
+ size = sizeof(ProjPixel);
+ }
+
+ projPixel = (ProjPixel *)BLI_memarena_alloc(arena, size);
+ //memset(projPixel, 0, size);
+
+ if (ibuf->rect_float) {
+ projPixel->pixel.f_pt = (float *)ibuf->rect_float + ((x_px + y_px * ibuf->x) * 4);
+ projPixel->origColor.f[0] = projPixel->newColor.f[0] = projPixel->pixel.f_pt[0];
+ projPixel->origColor.f[1] = projPixel->newColor.f[1] = projPixel->pixel.f_pt[1];
+ projPixel->origColor.f[2] = projPixel->newColor.f[2] = projPixel->pixel.f_pt[2];
+ projPixel->origColor.f[3] = projPixel->newColor.f[3] = projPixel->pixel.f_pt[3];
+ }
+ else {
+ projPixel->pixel.ch_pt = ((unsigned char *)ibuf->rect + ((x_px + y_px * ibuf->x) * 4));
+ projPixel->origColor.uint = projPixel->newColor.uint = *projPixel->pixel.uint_pt;
+ }
+
+ /* screenspace unclamped, we could keep its z and w values but dont need them at the moment */
+ VECCOPY2D(projPixel->projCoSS, pixelScreenCo);
+
+ projPixel->x_px = x_px;
+ projPixel->y_px = y_px;
+
+ projPixel->mask = (unsigned short)(mask * 65535);
+ projPixel->mask_max = 0;
+
+ /* which bounding box cell are we in?, needed for undo */
+ projPixel->bb_cell_index = ((int)(((float)x_px/(float)ibuf->x) * PROJ_BOUNDBOX_DIV)) + ((int)(((float)y_px/(float)ibuf->y) * PROJ_BOUNDBOX_DIV)) * PROJ_BOUNDBOX_DIV ;
+
+ /* done with view3d_project_float inline */
+ if (ps->tool==PAINT_TOOL_CLONE) {
+ if (ps->dm_mtface_clone) {
+ ImBuf *ibuf_other;
+ const MTFace *tf_other = ps->dm_mtface_clone + face_index;
+
+ if (tf_other->tpage && (ibuf_other = BKE_image_get_ibuf((Image *)tf_other->tpage, NULL))) {
+ /* BKE_image_get_ibuf - TODO - this may be slow */
+
+ if (ibuf->rect_float) {
+ if (ibuf_other->rect_float) { /* from float to float */
+ project_face_pixel(tf_other, ibuf_other, w, side, NULL, ((ProjPixelClone *)projPixel)->clonepx.f);
+ }
+ else { /* from char to float */
+ unsigned char rgba_ub[4];
+ project_face_pixel(tf_other, ibuf_other, w, side, rgba_ub, NULL);
+ IMAPAINT_CHAR_RGBA_TO_FLOAT(((ProjPixelClone *)projPixel)->clonepx.f, rgba_ub);
+ }
+ }
+ else {
+ if (ibuf_other->rect_float) { /* float to char */
+ float rgba[4];
+ project_face_pixel(tf_other, ibuf_other, w, side, NULL, rgba);
+ IMAPAINT_FLOAT_RGBA_TO_CHAR(((ProjPixelClone *)projPixel)->clonepx.ch, rgba)
+ }
+ else { /* char to char */
+ project_face_pixel(tf_other, ibuf_other, w, side, ((ProjPixelClone *)projPixel)->clonepx.ch, NULL);
+ }
+ }
+ }
+ else {
+ if (ibuf->rect_float) {
+ ((ProjPixelClone *)projPixel)->clonepx.f[3] = 0;
+ }
+ else {
+ ((ProjPixelClone *)projPixel)->clonepx.ch[3] = 0;
+ }
+ }
+
+ }
+ else {
+ float co[2];
+ Vec2Subf(co, projPixel->projCoSS, (float *)ps->cloneOffset);
+
+ /* no need to initialize the bucket, we're only checking buckets faces and for this
+ * the faces are alredy initialized in project_paint_delayed_face_init(...) */
+ if (ibuf->rect_float) {
+ if (!project_paint_PickColor(ps, co, ((ProjPixelClone *)projPixel)->clonepx.f, NULL, 1)) {
+ ((ProjPixelClone *)projPixel)->clonepx.f[3] = 0; /* zero alpha - ignore */
+ }
+ }
+ else {
+ if (!project_paint_PickColor(ps, co, NULL, ((ProjPixelClone *)projPixel)->clonepx.ch, 1)) {
+ ((ProjPixelClone *)projPixel)->clonepx.ch[3] = 0; /* zero alpha - ignore */
+ }
+ }
+ }
+ }
+
+#ifdef PROJ_DEBUG_PAINT
+ if (ibuf->rect_float) projPixel->pixel.f_pt[0] = 0;
+ else projPixel->pixel.ch_pt[0] = 0;
+#endif
+ projPixel->image_index = image_index;
+
+ return projPixel;
+}
+
+static int line_clip_rect2f(
+ rctf *rect,
+ const float l1[2], const float l2[2],
+ float l1_clip[2], float l2_clip[2])
+{
+ /* first account for horizontal, then vertical lines */
+ /* horiz */
+ if (fabs(l1[1]-l2[1]) < PROJ_GEOM_TOLERANCE) {
+ /* is the line out of range on its Y axis? */
+ if (l1[1] < rect->ymin || l1[1] > rect->ymax) {
+ return 0;
+ }
+ /* line is out of range on its X axis */
+ if ((l1[0] < rect->xmin && l2[0] < rect->xmin) || (l1[0] > rect->xmax && l2[0] > rect->xmax)) {
+ return 0;
+ }
+
+
+ if (fabs(l1[0]-l2[0]) < PROJ_GEOM_TOLERANCE) { /* this is a single point (or close to)*/
+ if (BLI_in_rctf(rect, l1[0], l1[1])) {
+ VECCOPY2D(l1_clip, l1);
+ VECCOPY2D(l2_clip, l2);
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ VECCOPY2D(l1_clip, l1);
+ VECCOPY2D(l2_clip, l2);
+ CLAMP(l1_clip[0], rect->xmin, rect->xmax);
+ CLAMP(l2_clip[0], rect->xmin, rect->xmax);
+ return 1;
+ }
+ else if (fabs(l1[0]-l2[0]) < PROJ_GEOM_TOLERANCE) {
+ /* is the line out of range on its X axis? */
+ if (l1[0] < rect->xmin || l1[0] > rect->xmax) {
+ return 0;
+ }
+
+ /* line is out of range on its Y axis */
+ if ((l1[1] < rect->ymin && l2[1] < rect->ymin) || (l1[1] > rect->ymax && l2[1] > rect->ymax)) {
+ return 0;
+ }
+
+ if (fabs(l1[1]-l2[1]) < PROJ_GEOM_TOLERANCE) { /* this is a single point (or close to)*/
+ if (BLI_in_rctf(rect, l1[0], l1[1])) {
+ VECCOPY2D(l1_clip, l1);
+ VECCOPY2D(l2_clip, l2);
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ VECCOPY2D(l1_clip, l1);
+ VECCOPY2D(l2_clip, l2);
+ CLAMP(l1_clip[1], rect->ymin, rect->ymax);
+ CLAMP(l2_clip[1], rect->ymin, rect->ymax);
+ return 1;
+ }
+ else {
+ float isect;
+ short ok1 = 0;
+ short ok2 = 0;
+
+ /* Done with vertical lines */
+
+ /* are either of the points inside the rectangle ? */
+ if (BLI_in_rctf(rect, l1[0], l1[1])) {
+ VECCOPY2D(l1_clip, l1);
+ ok1 = 1;
+ }
+
+ if (BLI_in_rctf(rect, l2[0], l2[1])) {
+ VECCOPY2D(l2_clip, l2);
+ ok2 = 1;
+ }
+
+ /* line inside rect */
+ if (ok1 && ok2) return 1;
+
+ /* top/bottom */
+ if (line_isect_y(l1, l2, rect->ymin, &isect) && (isect >= rect->xmin) && (isect <= rect->xmax)) {
+ if (l1[1] < l2[1]) { /* line 1 is outside */
+ l1_clip[0] = isect;
+ l1_clip[1] = rect->ymin;
+ ok1 = 1;
+ }
+ else {
+ l2_clip[0] = isect;
+ l2_clip[1] = rect->ymin;
+ ok2 = 2;
+ }
+ }
+
+ if (ok1 && ok2) return 1;
+
+ if (line_isect_y(l1, l2, rect->ymax, &isect) && (isect >= rect->xmin) && (isect <= rect->xmax)) {
+ if (l1[1] > l2[1]) { /* line 1 is outside */
+ l1_clip[0] = isect;
+ l1_clip[1] = rect->ymax;
+ ok1 = 1;
+ }
+ else {
+ l2_clip[0] = isect;
+ l2_clip[1] = rect->ymax;
+ ok2 = 2;
+ }
+ }
+
+ if (ok1 && ok2) return 1;
+
+ /* left/right */
+ if (line_isect_x(l1, l2, rect->xmin, &isect) && (isect >= rect->ymin) && (isect <= rect->ymax)) {
+ if (l1[0] < l2[0]) { /* line 1 is outside */
+ l1_clip[0] = rect->xmin;
+ l1_clip[1] = isect;
+ ok1 = 1;
+ }
+ else {
+ l2_clip[0] = rect->xmin;
+ l2_clip[1] = isect;
+ ok2 = 2;
+ }
+ }
+
+ if (ok1 && ok2) return 1;
+
+ if (line_isect_x(l1, l2, rect->xmax, &isect) && (isect >= rect->ymin) && (isect <= rect->ymax)) {
+ if (l1[0] > l2[0]) { /* line 1 is outside */
+ l1_clip[0] = rect->xmax;
+ l1_clip[1] = isect;
+ ok1 = 1;
+ }
+ else {
+ l2_clip[0] = rect->xmax;
+ l2_clip[1] = isect;
+ ok2 = 2;
+ }
+ }
+
+ if (ok1 && ok2) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }
+}
+
+
+
+/* scale the quad & tri about its center
+ * scaling by PROJ_FACE_SCALE_SEAM (0.99x) is used for getting fake UV pixel coords that are on the
+ * edge of the face but slightly inside it occlusion tests dont return hits on adjacent faces */
+static void scale_quad(float insetCos[4][3], float *origCos[4], const float inset)
+{
+ float cent[3];
+ cent[0] = (origCos[0][0] + origCos[1][0] + origCos[2][0] + origCos[3][0]) / 4.0f;
+ cent[1] = (origCos[0][1] + origCos[1][1] + origCos[2][1] + origCos[3][1]) / 4.0f;
+ cent[2] = (origCos[0][2] + origCos[1][2] + origCos[2][2] + origCos[3][2]) / 4.0f;
+
+ VecSubf(insetCos[0], origCos[0], cent);
+ VecSubf(insetCos[1], origCos[1], cent);
+ VecSubf(insetCos[2], origCos[2], cent);
+ VecSubf(insetCos[3], origCos[3], cent);
+
+ VecMulf(insetCos[0], inset);
+ VecMulf(insetCos[1], inset);
+ VecMulf(insetCos[2], inset);
+ VecMulf(insetCos[3], inset);
+
+ VecAddf(insetCos[0], insetCos[0], cent);
+ VecAddf(insetCos[1], insetCos[1], cent);
+ VecAddf(insetCos[2], insetCos[2], cent);
+ VecAddf(insetCos[3], insetCos[3], cent);
+}
+
+
+static void scale_tri(float insetCos[4][3], float *origCos[4], const float inset)
+{
+ float cent[3];
+ cent[0] = (origCos[0][0] + origCos[1][0] + origCos[2][0]) / 3.0f;
+ cent[1] = (origCos[0][1] + origCos[1][1] + origCos[2][1]) / 3.0f;
+ cent[2] = (origCos[0][2] + origCos[1][2] + origCos[2][2]) / 3.0f;
+
+ VecSubf(insetCos[0], origCos[0], cent);
+ VecSubf(insetCos[1], origCos[1], cent);
+ VecSubf(insetCos[2], origCos[2], cent);
+
+ VecMulf(insetCos[0], inset);
+ VecMulf(insetCos[1], inset);
+ VecMulf(insetCos[2], inset);
+
+ VecAddf(insetCos[0], insetCos[0], cent);
+ VecAddf(insetCos[1], insetCos[1], cent);
+ VecAddf(insetCos[2], insetCos[2], cent);
+}
+
+
+static float Vec2Lenf_nosqrt(const float *v1, const float *v2)
+{
+ float x, y;
+
+ x = v1[0]-v2[0];
+ y = v1[1]-v2[1];
+ return x*x+y*y;
+}
+
+static float Vec2Lenf_nosqrt_other(const float *v1, const float v2_1, const float v2_2)
+{
+ float x, y;
+
+ x = v1[0]-v2_1;
+ y = v1[1]-v2_2;
+ return x*x+y*y;
+}
+
+/* note, use a squared value so we can use Vec2Lenf_nosqrt
+ * be sure that you have done a bounds check first or this may fail */
+/* only give bucket_bounds as an arg because we need it elsewhere */
+static int project_bucket_isect_circle(const int bucket_x, const int bucket_y, const float cent[2], const float radius_squared, rctf *bucket_bounds)
+{
+
+ /* Would normally to a simple intersection test, however we know the bounds of these 2 alredy intersect
+ * so we only need to test if the center is inside the vertical or horizontal bounds on either axis,
+ * this is even less work then an intersection test
+ *
+ if (BLI_in_rctf(bucket_bounds, cent[0], cent[1]))
+ return 1;
+ */
+
+ if((bucket_bounds->xmin <= cent[0] && bucket_bounds->xmax >= cent[0]) || (bucket_bounds->ymin <= cent[1] && bucket_bounds->ymax >= cent[1]) ) {
+ return 1;
+ }
+
+ /* out of bounds left */
+ if (cent[0] < bucket_bounds->xmin) {
+ /* lower left out of radius test */
+ if (cent[1] < bucket_bounds->ymin) {
+ return (Vec2Lenf_nosqrt_other(cent, bucket_bounds->xmin, bucket_bounds->ymin) < radius_squared) ? 1 : 0;
+ }
+ /* top left test */
+ else if (cent[1] > bucket_bounds->ymax) {
+ return (Vec2Lenf_nosqrt_other(cent, bucket_bounds->xmin, bucket_bounds->ymax) < radius_squared) ? 1 : 0;
+ }
+ }
+ else if (cent[0] > bucket_bounds->xmax) {
+ /* lower right out of radius test */
+ if (cent[1] < bucket_bounds->ymin) {
+ return (Vec2Lenf_nosqrt_other(cent, bucket_bounds->xmax, bucket_bounds->ymin) < radius_squared) ? 1 : 0;
+ }
+ /* top right test */
+ else if (cent[1] > bucket_bounds->ymax) {
+ return (Vec2Lenf_nosqrt_other(cent, bucket_bounds->xmax, bucket_bounds->ymax) < radius_squared) ? 1 : 0;
+ }
+ }
+
+ return 0;
+}
+
+
+
+/* Note for rect_to_uvspace_ortho() and rect_to_uvspace_persp()
+ * in ortho view this function gives good results when bucket_bounds are outside the triangle
+ * however in some cases, perspective view will mess up with faces that have minimal screenspace area (viewed from the side)
+ *
+ * for this reason its not relyable in this case so we'll use the Simple Barycentric' funcs that only account for points inside the triangle.
+ * however switching back to this for ortho is always an option */
+
+static void rect_to_uvspace_ortho(
+ rctf *bucket_bounds,
+ float *v1coSS, float *v2coSS, float *v3coSS,
+ float *uv1co, float *uv2co, float *uv3co,
+ float bucket_bounds_uv[4][2],
+ const int flip)
+{
+ float uv[2];
+ float w[3];
+
+ /* get the UV space bounding box */
+ uv[0] = bucket_bounds->xmax;
+ uv[1] = bucket_bounds->ymin;
+ BarycentricWeights2f(v1coSS, v2coSS, v3coSS, uv, w);
+ Vec2Weightf(bucket_bounds_uv[flip?3:0], uv1co, uv2co, uv3co, w);
+
+ //uv[0] = bucket_bounds->xmax; // set above
+ uv[1] = bucket_bounds->ymax;
+ BarycentricWeights2f(v1coSS, v2coSS, v3coSS, uv, w);
+ Vec2Weightf(bucket_bounds_uv[flip?2:1], uv1co, uv2co, uv3co, w);
+
+ uv[0] = bucket_bounds->xmin;
+ //uv[1] = bucket_bounds->ymax; // set above
+ BarycentricWeights2f(v1coSS, v2coSS, v3coSS, uv, w);
+ Vec2Weightf(bucket_bounds_uv[flip?1:2], uv1co, uv2co, uv3co, w);
+
+ //uv[0] = bucket_bounds->xmin; // set above
+ uv[1] = bucket_bounds->ymin;
+ BarycentricWeights2f(v1coSS, v2coSS, v3coSS, uv, w);
+ Vec2Weightf(bucket_bounds_uv[flip?0:3], uv1co, uv2co, uv3co, w);
+}
+
+/* same as above but use BarycentricWeightsPersp2f */
+static void rect_to_uvspace_persp(
+ rctf *bucket_bounds,
+ float *v1coSS, float *v2coSS, float *v3coSS,
+ float *uv1co, float *uv2co, float *uv3co,
+ float bucket_bounds_uv[4][2],
+ const int flip
+ )
+{
+ float uv[2];
+ float w[3];
+
+ /* get the UV space bounding box */
+ uv[0] = bucket_bounds->xmax;
+ uv[1] = bucket_bounds->ymin;
+ BarycentricWeightsPersp2f(v1coSS, v2coSS, v3coSS, uv, w);
+ Vec2Weightf(bucket_bounds_uv[flip?3:0], uv1co, uv2co, uv3co, w);
+
+ //uv[0] = bucket_bounds->xmax; // set above
+ uv[1] = bucket_bounds->ymax;
+ BarycentricWeightsPersp2f(v1coSS, v2coSS, v3coSS, uv, w);
+ Vec2Weightf(bucket_bounds_uv[flip?2:1], uv1co, uv2co, uv3co, w);
+
+ uv[0] = bucket_bounds->xmin;
+ //uv[1] = bucket_bounds->ymax; // set above
+ BarycentricWeightsPersp2f(v1coSS, v2coSS, v3coSS, uv, w);
+ Vec2Weightf(bucket_bounds_uv[flip?1:2], uv1co, uv2co, uv3co, w);
+
+ //uv[0] = bucket_bounds->xmin; // set above
+ uv[1] = bucket_bounds->ymin;
+ BarycentricWeightsPersp2f(v1coSS, v2coSS, v3coSS, uv, w);
+ Vec2Weightf(bucket_bounds_uv[flip?0:3], uv1co, uv2co, uv3co, w);
+}
+
+/* This works as we need it to but we can save a few steps and not use it */
+
+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]);
+}
+
+#define ISECT_1 (1)
+#define ISECT_2 (1<<1)
+#define ISECT_3 (1<<2)
+#define ISECT_4 (1<<3)
+#define ISECT_ALL3 ((1<<3)-1)
+#define ISECT_ALL4 ((1<<4)-1)
+
+/* limit must be a fraction over 1.0f */
+static int IsectPT2Df_limit(float pt[2], float v1[2], float v2[2], float v3[2], float limit)
+{
+ return ((AreaF2Dfl(pt,v1,v2) + AreaF2Dfl(pt,v2,v3) + AreaF2Dfl(pt,v3,v1)) / (AreaF2Dfl(v1,v2,v3))) < limit;
+}
+
+/* Clip the face by a bucket and set the uv-space bucket_bounds_uv
+ * so we have the clipped UV's to do pixel intersection tests with
+ * */
+static int float_z_sort_flip(const void *p1, const void *p2) {
+ return (((float *)p1)[2] < ((float *)p2)[2] ? 1:-1);
+}
+
+static int float_z_sort(const void *p1, const void *p2) {
+ return (((float *)p1)[2] < ((float *)p2)[2] ?-1:1);
+}
+
+static void project_bucket_clip_face(
+ const int is_ortho,
+ rctf *bucket_bounds,
+ float *v1coSS, float *v2coSS, float *v3coSS,
+ float *uv1co, float *uv2co, float *uv3co,
+ float bucket_bounds_uv[8][2],
+ int *tot)
+{
+ int inside_bucket_flag = 0;
+ int inside_face_flag = 0;
+ const int flip = ((SIDE_OF_LINE(v1coSS, v2coSS, v3coSS) > 0.0f) != (SIDE_OF_LINE(uv1co, uv2co, uv3co) > 0.0f));
+
+ float bucket_bounds_ss[4][2];
+ float w[3];
+
+ /* get the UV space bounding box */
+ inside_bucket_flag |= BLI_in_rctf(bucket_bounds, v1coSS[0], v1coSS[1]);
+ inside_bucket_flag |= BLI_in_rctf(bucket_bounds, v2coSS[0], v2coSS[1]) << 1;
+ inside_bucket_flag |= BLI_in_rctf(bucket_bounds, v3coSS[0], v3coSS[1]) << 2;
+
+ if (inside_bucket_flag == ISECT_ALL3) {
+ /* all screenspace points are inside the bucket bounding box, this means we dont need to clip and can simply return the UVs */
+ if (flip) { /* facing the back? */
+ VECCOPY2D(bucket_bounds_uv[0], uv3co);
+ VECCOPY2D(bucket_bounds_uv[1], uv2co);
+ VECCOPY2D(bucket_bounds_uv[2], uv1co);
+ }
+ else {
+ VECCOPY2D(bucket_bounds_uv[0], uv1co);
+ VECCOPY2D(bucket_bounds_uv[1], uv2co);
+ VECCOPY2D(bucket_bounds_uv[2], uv3co);
+ }
+
+ *tot = 3;
+ return;
+ }
+
+ /* get the UV space bounding box */
+ /* use IsectPT2Df_limit here so we catch points are are touching the tri edge (or a small fraction over) */
+ bucket_bounds_ss[0][0] = bucket_bounds->xmax;
+ bucket_bounds_ss[0][1] = bucket_bounds->ymin;
+ inside_face_flag |= (IsectPT2Df_limit(bucket_bounds_ss[0], v1coSS, v2coSS, v3coSS, 1+PROJ_GEOM_TOLERANCE) ? ISECT_1 : 0);
+
+ bucket_bounds_ss[1][0] = bucket_bounds->xmax;
+ bucket_bounds_ss[1][1] = bucket_bounds->ymax;
+ inside_face_flag |= (IsectPT2Df_limit(bucket_bounds_ss[1], v1coSS, v2coSS, v3coSS, 1+PROJ_GEOM_TOLERANCE) ? ISECT_2 : 0);
+
+ bucket_bounds_ss[2][0] = bucket_bounds->xmin;
+ bucket_bounds_ss[2][1] = bucket_bounds->ymax;
+ inside_face_flag |= (IsectPT2Df_limit(bucket_bounds_ss[2], v1coSS, v2coSS, v3coSS, 1+PROJ_GEOM_TOLERANCE) ? ISECT_3 : 0);
+
+ bucket_bounds_ss[3][0] = bucket_bounds->xmin;
+ bucket_bounds_ss[3][1] = bucket_bounds->ymin;
+ inside_face_flag |= (IsectPT2Df_limit(bucket_bounds_ss[3], v1coSS, v2coSS, v3coSS, 1+PROJ_GEOM_TOLERANCE) ? ISECT_4 : 0);
+
+ if (inside_face_flag == ISECT_ALL4) {
+ /* bucket is totally inside the screenspace face, we can safely use weights */
+
+ if (is_ortho) rect_to_uvspace_ortho(bucket_bounds, v1coSS, v2coSS, v3coSS, uv1co, uv2co, uv3co, bucket_bounds_uv, flip);
+ else rect_to_uvspace_persp(bucket_bounds, v1coSS, v2coSS, v3coSS, uv1co, uv2co, uv3co, bucket_bounds_uv, flip);
+
+ *tot = 4;
+ return;
+ }
+ else {
+ /* The Complicated Case!
+ *
+ * The 2 cases above are where the face is inside the bucket or the bucket is inside the face.
+ *
+ * we need to make a convex polyline from the intersection between the screenspace face
+ * and the bucket bounds.
+ *
+ * There are a number of ways this could be done, currently it just collects all intersecting verts,
+ * and line intersections, then sorts them clockwise, this is a lot easier then evaluating the geometry to
+ * do a correct clipping on both shapes. */
+
+
+ /* add a bunch of points, we know must make up the convex hull which is the clipped rect and triangle */
+
+
+
+ /* Maximum possible 6 intersections when using a rectangle and triangle */
+ float isectVCosSS[8][3]; /* The 3rd float is used to store angle for qsort(), NOT as a Z location */
+ float v1_clipSS[2], v2_clipSS[2];
+
+ /* calc center*/
+ float cent[2] = {0.0f, 0.0f};
+ /*float up[2] = {0.0f, 1.0f};*/
+ int i;
+ short doubles;
+
+ (*tot) = 0;
+
+ if (inside_face_flag & ISECT_1) { VECCOPY2D(isectVCosSS[*tot], bucket_bounds_ss[0]); (*tot)++; }
+ if (inside_face_flag & ISECT_2) { VECCOPY2D(isectVCosSS[*tot], bucket_bounds_ss[1]); (*tot)++; }
+ if (inside_face_flag & ISECT_3) { VECCOPY2D(isectVCosSS[*tot], bucket_bounds_ss[2]); (*tot)++; }
+ if (inside_face_flag & ISECT_4) { VECCOPY2D(isectVCosSS[*tot], bucket_bounds_ss[3]); (*tot)++; }
+
+ if (inside_bucket_flag & ISECT_1) { VECCOPY2D(isectVCosSS[*tot], v1coSS); (*tot)++; }
+ if (inside_bucket_flag & ISECT_2) { VECCOPY2D(isectVCosSS[*tot], v2coSS); (*tot)++; }
+ if (inside_bucket_flag & ISECT_3) { VECCOPY2D(isectVCosSS[*tot], v3coSS); (*tot)++; }
+
+ if ((inside_bucket_flag & (ISECT_1|ISECT_2)) != (ISECT_1|ISECT_2)) {
+ if (line_clip_rect2f(bucket_bounds, v1coSS, v2coSS, v1_clipSS, v2_clipSS)) {
+ if ((inside_bucket_flag & ISECT_1)==0) { VECCOPY2D(isectVCosSS[*tot], v1_clipSS); (*tot)++; }
+ if ((inside_bucket_flag & ISECT_2)==0) { VECCOPY2D(isectVCosSS[*tot], v2_clipSS); (*tot)++; }
+ }
+ }
+
+ if ((inside_bucket_flag & (ISECT_2|ISECT_3)) != (ISECT_2|ISECT_3)) {
+ if (line_clip_rect2f(bucket_bounds, v2coSS, v3coSS, v1_clipSS, v2_clipSS)) {
+ if ((inside_bucket_flag & ISECT_2)==0) { VECCOPY2D(isectVCosSS[*tot], v1_clipSS); (*tot)++; }
+ if ((inside_bucket_flag & ISECT_3)==0) { VECCOPY2D(isectVCosSS[*tot], v2_clipSS); (*tot)++; }
+ }
+ }
+
+ if ((inside_bucket_flag & (ISECT_3|ISECT_1)) != (ISECT_3|ISECT_1)) {
+ if (line_clip_rect2f(bucket_bounds, v3coSS, v1coSS, v1_clipSS, v2_clipSS)) {
+ if ((inside_bucket_flag & ISECT_3)==0) { VECCOPY2D(isectVCosSS[*tot], v1_clipSS); (*tot)++; }
+ if ((inside_bucket_flag & ISECT_1)==0) { VECCOPY2D(isectVCosSS[*tot], v2_clipSS); (*tot)++; }
+ }
+ }
+
+
+ if ((*tot) < 3) { /* no intersections to speak of */
+ *tot = 0;
+ }
+
+ /* now we have all points we need, collect their angles and sort them clockwise */
+
+ for(i=0; i<(*tot); i++) {
+ cent[0] += isectVCosSS[i][0];
+ cent[1] += isectVCosSS[i][1];
+ }
+ cent[0] = cent[0] / (float)(*tot);
+ cent[1] = cent[1] / (float)(*tot);
+
+
+
+ /* Collect angles for every point around the center point */
+
+
+#if 0 /* uses a few more cycles then the above loop */
+ for(i=0; i<(*tot); i++) {
+ isectVCosSS[i][2] = angle_2d_clockwise(up, cent, isectVCosSS[i]);
+ }
+#endif
+
+ v1_clipSS[0] = cent[0]; /* Abuse this var for the loop below */
+ v1_clipSS[1] = cent[1] + 1.0f;
+
+ for(i=0; i<(*tot); i++) {
+ v2_clipSS[0] = isectVCosSS[i][0] - cent[0];
+ v2_clipSS[1] = isectVCosSS[i][1] - cent[1];
+ isectVCosSS[i][2] = atan2(v1_clipSS[0]*v2_clipSS[1] - v1_clipSS[1]*v2_clipSS[0], v1_clipSS[0]*v2_clipSS[0]+v1_clipSS[1]*v2_clipSS[1]);
+ }
+
+ if (flip) qsort(isectVCosSS, *tot, sizeof(float)*3, float_z_sort_flip);
+ else qsort(isectVCosSS, *tot, sizeof(float)*3, float_z_sort);
+
+
+ /* remove doubles */
+ /* first/last check */
+ if (fabs(isectVCosSS[0][0]-isectVCosSS[(*tot)-1][0]) < PROJ_GEOM_TOLERANCE && fabs(isectVCosSS[0][1]-isectVCosSS[(*tot)-1][1]) < PROJ_GEOM_TOLERANCE) {
+ (*tot)--;
+ }
+
+ /* its possible there is only a few left after remove doubles */
+ if ((*tot) < 3) {
+ // printf("removed too many doubles A\n");
+ *tot = 0;
+ return;
+ }
+
+ doubles = TRUE;
+ while (doubles==TRUE) {
+ doubles = FALSE;
+ for(i=1; i<(*tot); i++) {
+ if (fabs(isectVCosSS[i-1][0]-isectVCosSS[i][0]) < PROJ_GEOM_TOLERANCE &&
+ fabs(isectVCosSS[i-1][1]-isectVCosSS[i][1]) < PROJ_GEOM_TOLERANCE)
+ {
+ int j;
+ for(j=i+1; j<(*tot); j++) {
+ isectVCosSS[j-1][0] = isectVCosSS[j][0];
+ isectVCosSS[j-1][1] = isectVCosSS[j][1];
+ }
+ doubles = TRUE; /* keep looking for more doubles */
+ (*tot)--;
+ }
+ }
+ }
+
+ /* its possible there is only a few left after remove doubles */
+ if ((*tot) < 3) {
+ // printf("removed too many doubles B\n");
+ *tot = 0;
+ return;
+ }
+
+
+ if (is_ortho) {
+ for(i=0; i<(*tot); i++) {
+ BarycentricWeights2f(v1coSS, v2coSS, v3coSS, isectVCosSS[i], w);
+ Vec2Weightf(bucket_bounds_uv[i], uv1co, uv2co, uv3co, w);
+ }
+ }
+ else {
+ for(i=0; i<(*tot); i++) {
+ BarycentricWeightsPersp2f(v1coSS, v2coSS, v3coSS, isectVCosSS[i], w);
+ Vec2Weightf(bucket_bounds_uv[i], uv1co, uv2co, uv3co, w);
+ }
+ }
+ }
+
+#ifdef PROJ_DEBUG_PRINT_CLIP
+ /* include this at the bottom of the above function to debug the output */
+
+ {
+ /* If there are ever any problems, */
+ float test_uv[4][2];
+ int i;
+ if (is_ortho) rect_to_uvspace_ortho(bucket_bounds, v1coSS, v2coSS, v3coSS, uv1co, uv2co, uv3co, test_uv, flip);
+ else rect_to_uvspace_persp(bucket_bounds, v1coSS, v2coSS, v3coSS, uv1co, uv2co, uv3co, test_uv, flip);
+ printf("( [(%f,%f), (%f,%f), (%f,%f), (%f,%f)], ", test_uv[0][0], test_uv[0][1], test_uv[1][0], test_uv[1][1], test_uv[2][0], test_uv[2][1], test_uv[3][0], test_uv[3][1]);
+
+ printf(" [(%f,%f), (%f,%f), (%f,%f)], ", uv1co[0], uv1co[1], uv2co[0], uv2co[1], uv3co[0], uv3co[1]);
+
+ printf("[");
+ for (i=0; i < (*tot); i++) {
+ printf("(%f, %f),", bucket_bounds_uv[i][0], bucket_bounds_uv[i][1]);
+ }
+ printf("]),\\\n");
+ }
+#endif
+}
+
+ /*
+# This script creates faces in a blender scene from printed data above.
+
+project_ls = [
+...(output from above block)...
+]
+
+from Blender import Scene, Mesh, Window, sys, Mathutils
+
+import bpy
+
+V = Mathutils.Vector
+
+def main():
+ sce = bpy.data.scenes.active
+
+ for item in project_ls:
+ bb = item[0]
+ uv = item[1]
+ poly = item[2]
+
+ me = bpy.data.meshes.new()
+ ob = sce.objects.new(me)
+
+ me.verts.extend([V(bb[0]).resize3D(), V(bb[1]).resize3D(), V(bb[2]).resize3D(), V(bb[3]).resize3D()])
+ me.faces.extend([(0,1,2,3),])
+ me.verts.extend([V(uv[0]).resize3D(), V(uv[1]).resize3D(), V(uv[2]).resize3D()])
+ me.faces.extend([(4,5,6),])
+
+ vs = [V(p).resize3D() for p in poly]
+ print len(vs)
+ l = len(me.verts)
+ me.verts.extend(vs)
+
+ i = l
+ while i < len(me.verts):
+ ii = i+1
+ if ii==len(me.verts):
+ ii = l
+ me.edges.extend([i, ii])
+ i+=1
+
+if __name__ == '__main__':
+ main()
+ */
+
+
+#undef ISECT_1
+#undef ISECT_2
+#undef ISECT_3
+#undef ISECT_4
+#undef ISECT_ALL3
+#undef ISECT_ALL4
+
+
+/* checks if pt is inside a convex 2D polyline, the polyline must be ordered rotating clockwise
+ * otherwise it would have to test for mixed (SIDE_OF_LINE > 0.0f) cases */
+int IsectPoly2Df(const float pt[2], float uv[][2], const int tot)
+{
+ int i;
+ if (SIDE_OF_LINE(uv[tot-1], uv[0], pt) < 0.0f)
+ return 0;
+
+ for (i=1; i<tot; i++) {
+ if (SIDE_OF_LINE(uv[i-1], uv[i], pt) < 0.0f)
+ return 0;
+
+ }
+
+ return 1;
+}
+
+/* One of the most important function for projectiopn painting, since it selects the pixels to be added into each bucket.
+ * initialize pixels from this face where it intersects with the bucket_index, optionally initialize pixels for removing seams */
+static void project_paint_face_init(const ProjPaintState *ps, const int thread_index, const int bucket_index, const int face_index, const int image_index, rctf *bucket_bounds, const ImBuf *ibuf)
+{
+ /* Projection vars, to get the 3D locations into screen space */
+ MemArena *arena = ps->arena_mt[thread_index];
+ LinkNode **bucketPixelNodes = ps->bucketRect + bucket_index;
+ LinkNode *bucketFaceNodes = ps->bucketFaces[bucket_index];
+
+ const MFace *mf = ps->dm_mface + face_index;
+ const MTFace *tf = ps->dm_mtface + face_index;
+
+ /* UV/pixel seeking data */
+ int x; /* Image X-Pixel */
+ int y;/* Image Y-Pixel */
+ float mask;
+ float uv[2]; /* Image floating point UV - same as x, y but from 0.0-1.0 */
+
+ int side;
+ float *v1coSS, *v2coSS, *v3coSS; /* vert co screen-space, these will be assigned to mf->v1,2,3 or mf->v1,3,4 */
+
+ float *vCo[4]; /* vertex screenspace coords */
+
+ float w[3], wco[3];
+
+ float *uv1co, *uv2co, *uv3co; /* for convenience only, these will be assigned to tf->uv[0],1,2 or tf->uv[0],2,3 */
+ float pixelScreenCo[4];
+
+ rcti bounds_px; /* ispace bounds */
+ /* vars for getting uvspace bounds */
+
+ float tf_uv_pxoffset[4][2]; /* bucket bounds in UV space so we can init pixels only for this face, */
+ float xhalfpx, yhalfpx;
+ const float ibuf_xf = ibuf->x, ibuf_yf = ibuf->y;
+
+ int has_x_isect = 0, has_isect = 0; /* for early loop exit */
+
+ int i1, i2, i3;
+
+ float uv_clip[8][2];
+ int uv_clip_tot;
+ const short is_ortho = ps->is_ortho;
+
+ vCo[0] = ps->dm_mvert[mf->v1].co;
+ vCo[1] = ps->dm_mvert[mf->v2].co;
+ vCo[2] = ps->dm_mvert[mf->v3].co;
+
+
+ /* Use tf_uv_pxoffset instead of tf->uv so we can offset the UV half a pixel
+ * this is done so we can avoid offseting all the pixels by 0.5 which causes
+ * problems when wrapping negative coords */
+ xhalfpx = 0.5f / ibuf_xf;
+ yhalfpx = 0.5f / ibuf_yf;
+
+ tf_uv_pxoffset[0][0] = tf->uv[0][0] - xhalfpx;
+ tf_uv_pxoffset[0][1] = tf->uv[0][1] - yhalfpx;
+
+ tf_uv_pxoffset[1][0] = tf->uv[1][0] - xhalfpx;
+ tf_uv_pxoffset[1][1] = tf->uv[1][1] - yhalfpx;
+
+ tf_uv_pxoffset[2][0] = tf->uv[2][0] - xhalfpx;
+ tf_uv_pxoffset[2][1] = tf->uv[2][1] - yhalfpx;
+
+ if (mf->v4) {
+ vCo[3] = ps->dm_mvert[ mf->v4 ].co;
+
+ tf_uv_pxoffset[3][0] = tf->uv[3][0] - xhalfpx;
+ tf_uv_pxoffset[3][1] = tf->uv[3][1] - yhalfpx;
+ side = 1;
+ }
+ else {
+ side = 0;
+ }
+
+ do {
+ if (side==1) {
+ i1=0; i2=2; i3=3;
+ }
+ else {
+ i1=0; i2=1; i3=2;
+ }
+
+ uv1co = tf_uv_pxoffset[i1]; // was tf->uv[i1];
+ uv2co = tf_uv_pxoffset[i2]; // was tf->uv[i2];
+ uv3co = tf_uv_pxoffset[i3]; // was tf->uv[i3];
+
+ v1coSS = ps->screenCoords[ (*(&mf->v1 + i1)) ];
+ v2coSS = ps->screenCoords[ (*(&mf->v1 + i2)) ];
+ v3coSS = ps->screenCoords[ (*(&mf->v1 + i3)) ];
+
+
+ /* This funtion gives is a concave polyline in UV space from the clipped quad and tri*/
+ project_bucket_clip_face(
+ is_ortho, bucket_bounds,
+ v1coSS, v2coSS, v3coSS,
+ uv1co, uv2co, uv3co,
+ uv_clip, &uv_clip_tot
+ );
+
+
+ /* sometimes this happens, better just allow for 8 intersectiosn even though there should be max 6 */
+ /*
+ if (uv_clip_tot>6) {
+ printf("this should never happen! %d\n", uv_clip_tot);
+ }*/
+
+
+ if (pixel_bounds_array(uv_clip, &bounds_px, ibuf->x, ibuf->y, uv_clip_tot)) {
+
+ /* clip face and */
+
+ has_isect = 0;
+ for (y = bounds_px.ymin; y < bounds_px.ymax; y++) {
+ //uv[1] = (((float)y) + 0.5f) / (float)ibuf->y;
+ uv[1] = (float)y / ibuf_yf; /* use pixel offset UV coords instead */
+
+ has_x_isect = 0;
+ for (x = bounds_px.xmin; x < bounds_px.xmax; x++) {
+ //uv[0] = (((float)x) + 0.5f) / ibuf->x;
+ uv[0] = (float)x / ibuf_xf; /* use pixel offset UV coords instead */
+
+ if (IsectPoly2Df(uv, uv_clip, uv_clip_tot)) {
+
+ has_x_isect = has_isect = 1;
+
+ if (is_ortho) screen_px_from_ortho(uv, v1coSS, v2coSS, v3coSS, uv1co, uv2co, uv3co, pixelScreenCo, w);
+ else screen_px_from_persp(uv, v1coSS, v2coSS, v3coSS, uv1co, uv2co, uv3co, pixelScreenCo, w);
+
+ /* a pitty we need to get the worldspace pixel location here */
+ if(G.vd->flag & V3D_CLIPPING) {
+ VecWeightf(wco, ps->dm_mvert[ (*(&mf->v1 + i1)) ].co, ps->dm_mvert[ (*(&mf->v1 + i2)) ].co, ps->dm_mvert[ (*(&mf->v1 + i3)) ].co, w);
+ Mat4MulVecfl(ps->ob->obmat, wco);
+ if(view3d_test_clipping(G.vd, wco)) {
+ continue; /* Watch out that no code below this needs to run */
+ }
+ }
+
+ /* Is this UV visible from the view? - raytrace */
+ /* project_paint_PickFace is less complex, use for testing */
+ //if (project_paint_PickFace(ps, pixelScreenCo, w, &side) == face_index) {
+ if (ps->do_occlude==0 || !project_bucket_point_occluded(ps, bucketFaceNodes, face_index, pixelScreenCo)) {
+
+ mask = project_paint_uvpixel_mask(ps, face_index, side, w);
+
+ if (mask > 0.0f) {
+ BLI_linklist_prepend_arena(
+ bucketPixelNodes,
+ project_paint_uvpixel_init(ps, arena, ibuf, x, y, mask, face_index, image_index, pixelScreenCo, side, w),
+ arena
+ );
+ }
+ }
+
+ }
+//#if 0
+ else if (has_x_isect) {
+ /* assuming the face is not a bow-tie - we know we cant intersect again on the X */
+ break;
+ }
+//#endif
+ }
+
+
+#if 0 /* TODO - investigate why this dosnt work sometimes! it should! */
+ /* no intersection for this entire row, after some intersection above means we can quit now */
+ if (has_x_isect==0 && has_isect) {
+ break;
+ }
+#endif
+ }
+ }
+ } while(side--);
+
+
+
+#ifndef PROJ_DEBUG_NOSEAMBLEED
+ if (ps->seam_bleed_px > 0.0f) {
+ int face_seam_flag;
+
+ if (ps->thread_tot > 1)
+ BLI_lock_thread(LOCK_CUSTOM1); /* Other threads could be modifying these vars */
+
+ face_seam_flag = ps->faceSeamFlags[face_index];
+
+ /* are any of our edges un-initialized? */
+ if ((face_seam_flag & (PROJ_FACE_SEAM1|PROJ_FACE_NOSEAM1))==0 ||
+ (face_seam_flag & (PROJ_FACE_SEAM2|PROJ_FACE_NOSEAM2))==0 ||
+ (face_seam_flag & (PROJ_FACE_SEAM3|PROJ_FACE_NOSEAM3))==0 ||
+ (face_seam_flag & (PROJ_FACE_SEAM4|PROJ_FACE_NOSEAM4))==0
+ ) {
+ project_face_seams_init(ps, face_index, mf->v4);
+ face_seam_flag = ps->faceSeamFlags[face_index];
+ //printf("seams - %d %d %d %d\n", flag&PROJ_FACE_SEAM1, flag&PROJ_FACE_SEAM2, flag&PROJ_FACE_SEAM3, flag&PROJ_FACE_SEAM4);
+ }
+
+ if ((face_seam_flag & (PROJ_FACE_SEAM1|PROJ_FACE_SEAM2|PROJ_FACE_SEAM3|PROJ_FACE_SEAM4))==0) {
+
+ if (ps->thread_tot > 1)
+ BLI_unlock_thread(LOCK_CUSTOM1); /* Other threads could be modifying these vars */
+
+ }
+ else {
+ /* we have a seam - deal with it! */
+
+ /* Now create new UV's for the seam face */
+ float (*outset_uv)[2] = ps->faceSeamUVs[face_index];
+ float insetCos[4][3]; /* inset face coords. NOTE!!! ScreenSace for ortho, Worldspace in prespective view */
+
+ float *uv_seam_quad[4];
+ float fac;
+ float *vCoSS[4]; /* vertex screenspace coords */
+
+ float bucket_clip_edges[2][2]; /* store the screenspace coords of the face, clipped by the bucket's screen aligned rectangle */
+ float edge_verts_inset_clip[2][3];
+ int fidx1, fidx2; /* face edge pairs - loop throuh these ((0,1), (1,2), (2,3), (3,0)) or ((0,1), (1,2), (2,0)) for a tri */
+
+ float seam_subsection[4][2];
+ float fac1, fac2, ftot;
+
+
+ if (outset_uv[0][0]==MAXFLOAT) /* first time initialize */
+ uv_image_outset(tf_uv_pxoffset, outset_uv, ps->seam_bleed_px, ibuf->x, ibuf->y, mf->v4);
+
+ /* ps->faceSeamUVs cant be modified when threading, now this is done we can unlock */
+ if (ps->thread_tot > 1)
+ BLI_unlock_thread(LOCK_CUSTOM1); /* Other threads could be modifying these vars */
+
+ vCoSS[0] = ps->screenCoords[mf->v1];
+ vCoSS[1] = ps->screenCoords[mf->v2];
+ vCoSS[2] = ps->screenCoords[mf->v3];
+ if (mf->v4)
+ vCoSS[3] = ps->screenCoords[ mf->v4 ];
+
+ /* PROJ_FACE_SCALE_SEAM must be slightly less then 1.0f */
+ if (is_ortho) {
+ if (mf->v4) scale_quad(insetCos, vCoSS, PROJ_FACE_SCALE_SEAM);
+ else scale_tri(insetCos, vCoSS, PROJ_FACE_SCALE_SEAM);
+ }
+ else {
+ if (mf->v4) scale_quad(insetCos, vCo, PROJ_FACE_SCALE_SEAM);
+ else scale_tri(insetCos, vCo, PROJ_FACE_SCALE_SEAM);
+ }
+
+ side = 0; /* for triangles this wont need to change */
+
+ for (fidx1 = 0; fidx1 < (mf->v4 ? 4 : 3); fidx1++) {
+ if (mf->v4) fidx2 = (fidx1==3) ? 0 : fidx1+1; /* next fidx in the face (0,1,2,3) -> (1,2,3,0) */
+ else fidx2 = (fidx1==2) ? 0 : fidx1+1; /* next fidx in the face (0,1,2) -> (1,2,0) */
+
+ if ( (face_seam_flag & (1<<fidx1)) && /* 1<<fidx1 -> PROJ_FACE_SEAM# */
+ line_clip_rect2f(bucket_bounds, vCoSS[fidx1], vCoSS[fidx2], bucket_clip_edges[0], bucket_clip_edges[1])
+ ) {
+
+ ftot = Vec2Lenf(vCoSS[fidx1], vCoSS[fidx2]); /* screenspace edge length */
+
+ if (ftot > 0.0f) { /* avoid div by zero */
+ if (mf->v4) {
+ if (fidx1==2 || fidx2==2) side= 1;
+ else side= 0;
+ }
+
+ fac1 = Vec2Lenf(vCoSS[fidx1], bucket_clip_edges[0]) / ftot;
+ fac2 = Vec2Lenf(vCoSS[fidx1], bucket_clip_edges[1]) / ftot;
+
+ uv_seam_quad[0] = tf_uv_pxoffset[fidx1];
+ uv_seam_quad[1] = tf_uv_pxoffset[fidx2];
+ uv_seam_quad[2] = outset_uv[fidx2];
+ uv_seam_quad[3] = outset_uv[fidx1];
+
+ Vec2Lerpf(seam_subsection[0], uv_seam_quad[0], uv_seam_quad[1], fac1);
+ Vec2Lerpf(seam_subsection[1], uv_seam_quad[0], uv_seam_quad[1], fac2);
+
+ Vec2Lerpf(seam_subsection[2], uv_seam_quad[3], uv_seam_quad[2], fac2);
+ Vec2Lerpf(seam_subsection[3], uv_seam_quad[3], uv_seam_quad[2], fac1);
+
+ /* if the bucket_clip_edges values Z values was kept we could avoid this
+ * Inset needs to be added so occlusion tests wont hit adjacent faces */
+ VecLerpf(edge_verts_inset_clip[0], insetCos[fidx1], insetCos[fidx2], fac1);
+ VecLerpf(edge_verts_inset_clip[1], insetCos[fidx1], insetCos[fidx2], fac2);
+
+
+ if (pixel_bounds_uv(seam_subsection[0], seam_subsection[1], seam_subsection[2], seam_subsection[3], &bounds_px, ibuf->x, ibuf->y, 1)) {
+ /* bounds between the seam rect and the uvspace bucket pixels */
+
+ has_isect = 0;
+ for (y = bounds_px.ymin; y < bounds_px.ymax; y++) {
+ // uv[1] = (((float)y) + 0.5f) / (float)ibuf->y;
+ uv[1] = (float)y / ibuf_yf; /* use offset uvs instead */
+
+ has_x_isect = 0;
+ for (x = bounds_px.xmin; x < bounds_px.xmax; x++) {
+ //uv[0] = (((float)x) + 0.5f) / (float)ibuf->x;
+ uv[0] = (float)x / ibuf_xf; /* use offset uvs instead */
+
+ /* test we're inside uvspace bucket and triangle bounds */
+ if (IsectPQ2Df(uv, seam_subsection[0], seam_subsection[1], seam_subsection[2], seam_subsection[3])) {
+
+ /* We need to find the closest point along the face edge,
+ * getting the screen_px_from_*** wont work because our actual location
+ * is not relevent, since we are outside the face, Use VecLerpf to find
+ * our location on the side of the face's UV */
+ /*
+ if (is_ortho) screen_px_from_ortho(ps, uv, v1co, v2co, v3co, uv1co, uv2co, uv3co, pixelScreenCo);
+ else screen_px_from_persp(ps, uv, v1co, v2co, v3co, uv1co, uv2co, uv3co, pixelScreenCo);
+ */
+
+ /* Since this is a seam we need to work out where on the line this pixel is */
+ //fac = lambda_cp_line2(uv, uv_seam_quad[0], uv_seam_quad[1]);
+
+ fac = lambda_cp_line2(uv, seam_subsection[0], seam_subsection[1]);
+ if (fac < 0.0f) { VECCOPY(pixelScreenCo, edge_verts_inset_clip[0]); }
+ else if (fac > 1.0f) { VECCOPY(pixelScreenCo, edge_verts_inset_clip[1]); }
+ else { VecLerpf(pixelScreenCo, edge_verts_inset_clip[0], edge_verts_inset_clip[1], fac); }
+
+ if (!is_ortho) {
+ pixelScreenCo[3] = 1.0f;
+ Mat4MulVec4fl(ps->projectMat, pixelScreenCo);
+ pixelScreenCo[0] = (float)(curarea->winx/2.0f)+(curarea->winx/2.0f)*pixelScreenCo[0]/pixelScreenCo[3];
+ pixelScreenCo[1] = (float)(curarea->winy/2.0f)+(curarea->winy/2.0f)*pixelScreenCo[1]/pixelScreenCo[3];
+ pixelScreenCo[2] = pixelScreenCo[2]/pixelScreenCo[3]; /* Use the depth for bucket point occlusion */
+ }
+
+ if (ps->do_occlude==0 || !project_bucket_point_occluded(ps, bucketFaceNodes, face_index, pixelScreenCo)) {
+
+ /* Only bother calculating the weights if we intersect */
+ if (ps->do_mask_normal || ps->dm_mtface_clone) {
+ /* TODO, this is not QUITE correct since UV is not inside the UV's but good enough for seams */
+ if (side) {
+ BarycentricWeights2f(tf_uv_pxoffset[0], tf_uv_pxoffset[2], tf_uv_pxoffset[3], uv, w);
+ }
+ else {
+ BarycentricWeights2f(tf_uv_pxoffset[0], tf_uv_pxoffset[1], tf_uv_pxoffset[2], uv, w);
+ }
+
+ }
+
+ /* a pitty we need to get the worldspace pixel location here */
+ if(G.vd->flag & V3D_CLIPPING) {
+ if (side) VecWeightf(wco, ps->dm_mvert[mf->v1].co, ps->dm_mvert[mf->v3].co, ps->dm_mvert[mf->v4].co, w);
+ else VecWeightf(wco, ps->dm_mvert[mf->v1].co, ps->dm_mvert[mf->v2].co, ps->dm_mvert[mf->v3].co, w);
+
+ Mat4MulVecfl(ps->ob->obmat, wco);
+ if(view3d_test_clipping(G.vd, wco)) {
+ continue; /* Watch out that no code below this needs to run */
+ }
+ }
+
+ mask = project_paint_uvpixel_mask(ps, face_index, side, w);
+
+ if (mask > 0.0f) {
+ BLI_linklist_prepend_arena(
+ bucketPixelNodes,
+ project_paint_uvpixel_init(ps, arena, ibuf, x, y, mask, face_index, image_index, pixelScreenCo, side, w),
+ arena
+ );
+ }
+
+ }
+ }
+ else if (has_x_isect) {
+ /* assuming the face is not a bow-tie - we know we cant intersect again on the X */
+ break;
+ }
+ }
+
+#if 0 /* TODO - investigate why this dosnt work sometimes! it should! */
+ /* no intersection for this entire row, after some intersection above means we can quit now */
+ if (has_x_isect==0 && has_isect) {
+ break;
+ }
+#endif
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+#endif // PROJ_DEBUG_NOSEAMBLEED
+}
+
+
+/* takes floating point screenspace min/max and returns int min/max to be used as indicies for ps->bucketRect, ps->bucketFlags */
+static void project_paint_bucket_bounds(const ProjPaintState *ps, const float min[2], const float max[2], int bucketMin[2], int bucketMax[2])
+{
+ /* divide by bucketWidth & bucketHeight so the bounds are offset in bucket grid units */
+ bucketMin[0] = (int)(((float)(min[0] - ps->screenMin[0]) / ps->screen_width) * ps->buckets_x) + 0.5f; /* these offsets of 0.5 and 1.5 seem odd but they are correct */
+ bucketMin[1] = (int)(((float)(min[1] - ps->screenMin[1]) / ps->screen_height) * ps->buckets_y) + 0.5f;
+
+ bucketMax[0] = (int)(((float)(max[0] - ps->screenMin[0]) / ps->screen_width) * ps->buckets_x) + 1.5f;
+ bucketMax[1] = (int)(((float)(max[1] - ps->screenMin[1]) / ps->screen_height) * ps->buckets_y) + 1.5f;
+
+ /* incase the rect is outside the mesh 2d bounds */
+ CLAMP(bucketMin[0], 0, ps->buckets_x);
+ CLAMP(bucketMin[1], 0, ps->buckets_y);
+
+ CLAMP(bucketMax[0], 0, ps->buckets_x);
+ CLAMP(bucketMax[1], 0, ps->buckets_y);
+}
+
+/* set bucket_bounds to a screen space-aligned floating point bound-box */
+static void project_bucket_bounds(const ProjPaintState *ps, const int bucket_x, const int bucket_y, rctf *bucket_bounds)
+{
+ bucket_bounds->xmin = ps->screenMin[0]+((bucket_x)*(ps->screen_width / ps->buckets_x)); /* left */
+ bucket_bounds->xmax = ps->screenMin[0]+((bucket_x+1)*(ps->screen_width / ps->buckets_x)); /* right */
+
+ bucket_bounds->ymin = ps->screenMin[1]+((bucket_y)*(ps->screen_height / ps->buckets_y)); /* bottom */
+ bucket_bounds->ymax = ps->screenMin[1]+((bucket_y+1)*(ps->screen_height / ps->buckets_y)); /* top */
+}
+
+/* Fill this bucket with pixels from the faces that intersect it.
+ *
+ * have bucket_bounds as an argument so we don;t need to give bucket_x/y the rect function needs */
+static void project_bucket_init(const ProjPaintState *ps, const int thread_index, const int bucket_index, rctf *bucket_bounds)
+{
+ LinkNode *node;
+ int face_index, image_index;
+ ImBuf *ibuf = NULL;
+ MTFace *tf;
+
+ Image *tpage_last = NULL;
+
+
+ if (ps->image_tot==1) {
+ /* Simple loop, no context switching */
+ ibuf = ps->projImages[0].ibuf;
+
+ for (node = ps->bucketFaces[bucket_index]; node; node= node->next) {
+ project_paint_face_init(ps, thread_index, bucket_index, (int)node->link, 0, bucket_bounds, ibuf);
+ }
+ }
+ else {
+
+ /* More complicated loop, switch between images */
+ for (node = ps->bucketFaces[bucket_index]; node; node= node->next) {
+ face_index = (int)node->link;
+
+ /* Image context switching */
+ tf = ps->dm_mtface+face_index;
+ if (tpage_last != tf->tpage) {
+ tpage_last = tf->tpage;
+
+ image_index = -1; /* sanity check */
+
+ for (image_index=0; image_index < ps->image_tot; image_index++) {
+ if (ps->projImages[image_index].ima == tpage_last) {
+ ibuf = ps->projImages[image_index].ibuf;
+ break;
+ }
+ }
+ }
+ /* context switching done */
+
+ project_paint_face_init(ps, thread_index, bucket_index, face_index, image_index, bucket_bounds, ibuf);
+
+ }
+ }
+
+ ps->bucketFlags[bucket_index] |= PROJ_BUCKET_INIT;
+}
+
+
+/* We want to know if a bucket and a face overlap in screen-space
+ *
+ * Note, if this ever returns false positives its not that bad, since a face in the bounding area will have its pixels
+ * calculated when it might not be needed later, (at the moment at least)
+ * obviously it shouldn't have bugs though */
+
+static int project_bucket_face_isect(ProjPaintState *ps, float min[2], float max[2], int bucket_x, int bucket_y, int bucket_index, const MFace *mf)
+{
+ /* TODO - replace this with a tricker method that uses sideofline for all screenCoords's edges against the closest bucket corner */
+ rctf bucket_bounds;
+ float p1[2], p2[2], p3[2], p4[2];
+ float *v, *v1,*v2,*v3,*v4;
+ int fidx;
+
+ project_bucket_bounds(ps, bucket_x, bucket_y, &bucket_bounds);
+
+ /* Is one of the faces verts in the bucket bounds? */
+
+ fidx = mf->v4 ? 3:2;
+ do {
+ v = ps->screenCoords[ (*(&mf->v1 + fidx)) ];
+ if (BLI_in_rctf(&bucket_bounds, v[0], v[1])) {
+ return 1;
+ }
+ } while (fidx--);
+
+ v1 = ps->screenCoords[mf->v1];
+ v2 = ps->screenCoords[mf->v2];
+ v3 = ps->screenCoords[mf->v3];
+ if (mf->v4) {
+ v4 = ps->screenCoords[mf->v4];
+ }
+
+ p1[0] = bucket_bounds.xmin; p1[1] = bucket_bounds.ymin;
+ p2[0] = bucket_bounds.xmin; p2[1] = bucket_bounds.ymax;
+ p3[0] = bucket_bounds.xmax; p3[1] = bucket_bounds.ymax;
+ p4[0] = bucket_bounds.xmax; p4[1] = bucket_bounds.ymin;
+
+ if (mf->v4) {
+ if( IsectPQ2Df(p1, v1, v2, v3, v4) || IsectPQ2Df(p2, v1, v2, v3, v4) || IsectPQ2Df(p3, v1, v2, v3, v4) || IsectPQ2Df(p4, v1, v2, v3, v4) ||
+ /* we can avoid testing v3,v1 because another intersection MUST exist if this intersects */
+ (IsectLL2Df(p1, p2, v1, v2) || IsectLL2Df(p1, p2, v2, v3) || IsectLL2Df(p1, p2, v3, v4)) ||
+ (IsectLL2Df(p2, p3, v1, v2) || IsectLL2Df(p2, p3, v2, v3) || IsectLL2Df(p2, p3, v3, v4)) ||
+ (IsectLL2Df(p3, p4, v1, v2) || IsectLL2Df(p3, p4, v2, v3) || IsectLL2Df(p3, p4, v3, v4)) ||
+ (IsectLL2Df(p4, p1, v1, v2) || IsectLL2Df(p4, p1, v2, v3) || IsectLL2Df(p4, p1, v3, v4))
+ ) {
+ return 1;
+ }
+ }
+ else {
+ if( IsectPT2Df(p1, v1, v2, v3) || IsectPT2Df(p2, v1, v2, v3) || IsectPT2Df(p3, v1, v2, v3) || IsectPT2Df(p4, v1, v2, v3) ||
+ /* we can avoid testing v3,v1 because another intersection MUST exist if this intersects */
+ (IsectLL2Df(p1, p2, v1, v2) || IsectLL2Df(p1, p2, v2, v3)) ||
+ (IsectLL2Df(p2, p3, v1, v2) || IsectLL2Df(p2, p3, v2, v3)) ||
+ (IsectLL2Df(p3, p4, v1, v2) || IsectLL2Df(p3, p4, v2, v3)) ||
+ (IsectLL2Df(p4, p1, v1, v2) || IsectLL2Df(p4, p1, v2, v3))
+ ) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* Add faces to the bucket but dont initialize its pixels
+ * TODO - when painting occluded, sort the faces on their min-Z and only add faces that faces that are not occluded */
+static void project_paint_delayed_face_init(ProjPaintState *ps, const MFace *mf, const MTFace *tf, const int face_index)
+{
+ float min[2], max[2], *vCoSS;
+ int bucketMin[2], bucketMax[2]; /* for ps->bucketRect indexing */
+ int fidx, bucket_x, bucket_y, bucket_index;
+ int has_x_isect = -1, has_isect = 0; /* for early loop exit */
+ MemArena *arena = ps->arena_mt[0]; /* just use the first thread arena since threading has not started yet */
+
+ INIT_MINMAX2(min, max);
+
+ fidx = mf->v4 ? 3:2;
+ do {
+ vCoSS = ps->screenCoords[ *(&mf->v1 + fidx) ];
+ DO_MINMAX2(vCoSS, min, max);
+ } while (fidx--);
+
+ project_paint_bucket_bounds(ps, min, max, bucketMin, bucketMax);
+
+ for (bucket_y = bucketMin[1]; bucket_y < bucketMax[1]; bucket_y++) {
+ has_x_isect = 0;
+ for (bucket_x = bucketMin[0]; bucket_x < bucketMax[0]; bucket_x++) {
+
+ bucket_index = bucket_x + (bucket_y * ps->buckets_x);
+
+ if (project_bucket_face_isect(ps, min, max, bucket_x, bucket_y, bucket_index, mf)) {
+ BLI_linklist_prepend_arena(
+ &ps->bucketFaces[ bucket_index ],
+ SET_INT_IN_POINTER(face_index), /* cast to a pointer to shut up the compiler */
+ arena
+ );
+
+ has_x_isect = has_isect = 1;
+ }
+ else if (has_x_isect) {
+ /* assuming the face is not a bow-tie - we know we cant intersect again on the X */
+ break;
+ }
+ }
+
+ /* no intersection for this entire row, after some intersection above means we can quit now */
+ if (has_x_isect==0 && has_isect) {
+ break;
+ }
+ }
+
+#ifndef PROJ_DEBUG_NOSEAMBLEED
+ if (ps->seam_bleed_px > 0.0f) {
+ if (!mf->v4) {
+ ps->faceSeamFlags[face_index] |= PROJ_FACE_NOSEAM4; /* so this wont show up as an untagged edge */
+ }
+ **ps->faceSeamUVs[face_index] = MAXFLOAT; /* set as uninitialized */
+ }
+#endif
+}
+
+/* run once per stroke before projection painting */
+static void project_paint_begin(ProjPaintState *ps, short mval[2])
+{
+ /* Viewport vars */
+ float mat[3][3];
+
+ float no[3];
+
+ float (*projScreenCo)[4]; /* Note, we could have 4D vectors are only needed for */
+ float projMargin;
+ /* Image Vars - keep track of images we have used */
+ LinkNode *image_LinkList = NULL;
+ LinkNode *node;
+
+ ProjPaintImage *projIma;
+ Image *tpage_last = NULL;
+
+ /* Face vars */
+ MFace *mf;
+ MTFace *tf;
+
+ int a, i; /* generic looping vars */
+ int image_index = -1, face_index;
+
+ MemArena *arena; /* at the moment this is just ps->arena_mt[0], but use this to show were not multithreading */
+
+ /* ---- end defines ---- */
+
+ /* paint onto the derived mesh */
+ ps->dm = mesh_get_derived_final(ps->ob, get_viewedit_datamask());
+
+ if ( !CustomData_has_layer( &ps->dm->faceData, CD_MTFACE) ) {
+ ps->dm = NULL;
+ return;
+ }
+ ps->dm_mvert = ps->dm->getVertArray(ps->dm);
+ ps->dm_mface = ps->dm->getFaceArray(ps->dm);
+ ps->dm_mtface= ps->dm->getFaceDataArray(ps->dm, CD_MTFACE);
+
+ ps->dm_totvert = ps->dm->getNumVerts(ps->dm);
+ ps->dm_totface = ps->dm->getNumFaces(ps->dm);
+
+ /* use clone mtface? */
+
+
+ /* Note, use the original mesh for getting the clone and mask layer index
+ * this avoids re-generating the derived mesh just to get the new index */
+ if (ps->do_layer_clone) {
+ //int layer_num = CustomData_get_clone_layer(&ps->dm->faceData, CD_MTFACE);
+ int layer_num = CustomData_get_clone_layer(&((Mesh *)ps->ob->data)->fdata, CD_MTFACE);
+ if (layer_num != -1)
+ ps->dm_mtface_clone = CustomData_get_layer_n(&ps->dm->faceData, CD_MTFACE, layer_num);
+
+ if (ps->dm_mtface_clone==NULL || ps->dm_mtface_clone==ps->dm_mtface) {
+ ps->do_layer_clone = 0;
+ ps->dm_mtface_clone= NULL;
+ }
+ }
+
+ if (ps->do_layer_mask) {
+ //int layer_num = CustomData_get_mask_layer(&ps->dm->faceData, CD_MTFACE);
+ int layer_num = CustomData_get_mask_layer(&((Mesh *)ps->ob->data)->fdata, CD_MTFACE);
+ if (layer_num != -1)
+ ps->dm_mtface_mask = CustomData_get_layer_n(&ps->dm->faceData, CD_MTFACE, layer_num);
+
+ if (ps->dm_mtface_mask==NULL || ps->dm_mtface_mask==ps->dm_mtface) {
+ ps->do_layer_mask = 0;
+ ps->dm_mtface_mask = NULL;
+ }
+ }
+
+
+
+ ps->viewDir[0] = 0.0f;
+ ps->viewDir[1] = 0.0f;
+ ps->viewDir[2] = 1.0f;
+
+ view3d_get_object_project_mat(curarea, ps->ob, ps->projectMat, ps->viewMat);
+
+ /* viewDir - object relative */
+ Mat4Invert(ps->ob->imat, ps->ob->obmat);
+ Mat3CpyMat4(mat, G.vd->viewinv);
+ Mat3MulVecfl(mat, ps->viewDir);
+ Mat3CpyMat4(mat, ps->ob->imat);
+ Mat3MulVecfl(mat, ps->viewDir);
+ Normalize(ps->viewDir);
+
+ /* viewPos - object relative */
+ VECCOPY(ps->viewPos, G.vd->viewinv[3]);
+ Mat3CpyMat4(mat, ps->ob->imat);
+ Mat3MulVecfl(mat, ps->viewPos);
+ VecAddf(ps->viewPos, ps->viewPos, ps->ob->imat[3]);
+
+ { /* only use these for running 'get_view3d_viewplane' */
+ rctf viewplane;
+
+ ps->is_ortho = get_view3d_viewplane(curarea->winx, curarea->winy, &viewplane, &ps->clipsta, &ps->clipend, NULL);
+
+ //printf("%f %f\n", ps->clipsta, ps->clipend);
+ if (ps->is_ortho) { /* only needed for ortho */
+ float fac = 2.0f / (ps->clipend - ps->clipsta);
+ ps->clipsta *= fac;
+ ps->clipend *= fac;
+ }
+ else {
+ /* TODO - can we even adjust for clip start/end? */
+ }
+
+ }
+
+ ps->is_airbrush = (ps->brush->flag & BRUSH_AIRBRUSH) ? 1 : 0;
+
+ ps->is_texbrush = (ps->brush->mtex[ps->brush->texact] && ps->brush->mtex[ps->brush->texact]->tex) ? 1 : 0;
+
+
+ /* calculate vert screen coords
+ * run this early so we can calculate the x/y resolution of our bucket rect */
+ INIT_MINMAX2(ps->screenMin, ps->screenMax);
+
+ ps->screenCoords = MEM_mallocN(sizeof(float) * ps->dm_totvert * 4, "ProjectPaint ScreenVerts");
+ projScreenCo = ps->screenCoords;
+
+ if (ps->is_ortho) {
+ for(a=0; a < ps->dm_totvert; a++, projScreenCo++) {
+ VECCOPY((*projScreenCo), ps->dm_mvert[a].co);
+ Mat4MulVecfl(ps->projectMat, (*projScreenCo));
+
+ /* screen space, not clamped */
+ (*projScreenCo)[0] = (float)(curarea->winx/2.0f)+(curarea->winx/2.0f)*(*projScreenCo)[0];
+ (*projScreenCo)[1] = (float)(curarea->winy/2.0f)+(curarea->winy/2.0f)*(*projScreenCo)[1];
+ DO_MINMAX2((*projScreenCo), ps->screenMin, ps->screenMax);
+ }
+ }
+ else {
+ for(a=0; a < ps->dm_totvert; a++, projScreenCo++) {
+ VECCOPY((*projScreenCo), ps->dm_mvert[a].co);
+ (*projScreenCo)[3] = 1.0f;
+
+ Mat4MulVec4fl(ps->projectMat, (*projScreenCo));
+
+
+ if ((*projScreenCo)[3] > ps->clipsta) {
+ /* screen space, not clamped */
+ (*projScreenCo)[0] = (float)(curarea->winx/2.0f)+(curarea->winx/2.0f)*(*projScreenCo)[0]/(*projScreenCo)[3];
+ (*projScreenCo)[1] = (float)(curarea->winy/2.0f)+(curarea->winy/2.0f)*(*projScreenCo)[1]/(*projScreenCo)[3];
+ (*projScreenCo)[2] = (*projScreenCo)[2]/(*projScreenCo)[3]; /* Use the depth for bucket point occlusion */
+ DO_MINMAX2((*projScreenCo), ps->screenMin, ps->screenMax);
+ }
+ else {
+ /* TODO - deal with cases where 1 side of a face goes behind the view ?
+ *
+ * After some research this is actually very tricky, only option is to
+ * clip the derived mesh before painting, which is a Pain */
+ (*projScreenCo)[0] = MAXFLOAT;
+ }
+ }
+ }
+
+ /* If this border is not added we get artifacts for faces that
+ * have a parallel edge and at the bounds of the the 2D projected verts eg
+ * - a single screen aligned quad */
+ projMargin = (ps->screenMax[0] - ps->screenMin[0]) * 0.000001f;
+ ps->screenMax[0] += projMargin;
+ ps->screenMin[0] -= projMargin;
+ projMargin = (ps->screenMax[1] - ps->screenMin[1]) * 0.000001f;
+ ps->screenMax[1] += projMargin;
+ ps->screenMin[1] -= projMargin;
+
+#ifdef PROJ_DEBUG_WINCLIP
+ CLAMP(ps->screenMin[0], -ps->brush->size, curarea->winx + ps->brush->size);
+ CLAMP(ps->screenMax[0], -ps->brush->size, curarea->winx + ps->brush->size);
+
+ CLAMP(ps->screenMin[1], -ps->brush->size, curarea->winy + ps->brush->size);
+ CLAMP(ps->screenMax[1], -ps->brush->size, curarea->winy + ps->brush->size);
+#endif
+
+ /* only for convenience */
+ ps->screen_width = ps->screenMax[0] - ps->screenMin[0];
+ ps->screen_height = ps->screenMax[1] - ps->screenMin[1];
+
+ ps->buckets_x = (int)(ps->screen_width / (((float)ps->brush->size) / PROJ_BUCKET_BRUSH_DIV));
+ ps->buckets_y = (int)(ps->screen_height / (((float)ps->brush->size) / PROJ_BUCKET_BRUSH_DIV));
+
+ printf("\tscreenspace bucket division x:%d y:%d\n", ps->buckets_x, ps->buckets_y);
+
+ /* really high values could cause problems since it has to allocate a few
+ * (ps->buckets_x*ps->buckets_y) sized arrays */
+ CLAMP(ps->buckets_x, PROJ_BUCKET_RECT_MIN, PROJ_BUCKET_RECT_MAX);
+ CLAMP(ps->buckets_y, PROJ_BUCKET_RECT_MIN, PROJ_BUCKET_RECT_MAX);
+
+ ps->bucketRect = (LinkNode **)MEM_callocN(sizeof(LinkNode *) * ps->buckets_x * ps->buckets_y, "paint-bucketRect");
+ ps->bucketFaces= (LinkNode **)MEM_callocN(sizeof(LinkNode *) * ps->buckets_x * ps->buckets_y, "paint-bucketFaces");
+
+ ps->bucketFlags= (unsigned char *)MEM_callocN(sizeof(char) * ps->buckets_x * ps->buckets_y, "paint-bucketFaces");
+#ifndef PROJ_DEBUG_NOSEAMBLEED
+ if (ps->seam_bleed_px > 0.0f) {
+ ps->vertFaces= (LinkNode **)MEM_callocN(sizeof(LinkNode *) * ps->dm_totvert, "paint-vertFaces");
+ ps->faceSeamFlags = (char *)MEM_callocN(sizeof(char) * ps->dm_totface, "paint-faceSeamFlags");
+ ps->faceSeamUVs= MEM_mallocN(sizeof(float) * ps->dm_totface * 8, "paint-faceSeamUVs");
+ }
+#endif
+
+ /* Thread stuff
+ *
+ * very small brushes run a lot slower multithreaded since the advantage with
+ * threads is being able to fill in multiple buckets at once.
+ * Only use threads for bigger brushes. */
+
+ if (G.scene->r.mode & R_FIXED_THREADS) {
+ ps->thread_tot = G.scene->r.threads;
+ }
+ else {
+ ps->thread_tot = BLI_system_thread_count();
+ }
+ for (a=0; a<ps->thread_tot; a++) {
+ ps->arena_mt[a] = BLI_memarena_new(1<<16);
+ }
+
+ arena = ps->arena_mt[0];
+
+ if (ps->do_backfacecull && ps->do_mask_normal) {
+ MVert *v = ps->dm_mvert;
+ float viewDirPersp[3];
+
+ ps->vertFlags = MEM_callocN(sizeof(char) * ps->dm_totvert, "paint-vertFlags");
+
+ for(a=0; a < ps->dm_totvert; a++, v++) {
+ no[0] = (float)(v->no[0] / 32767.0f);
+ no[1] = (float)(v->no[1] / 32767.0f);
+ no[2] = (float)(v->no[2] / 32767.0f);
+
+ if (ps->is_ortho) {
+ if (NormalizedVecAngle2(ps->viewDir, no) >= ps->normal_angle) { /* 1 vert of this face is towards us */
+ ps->vertFlags[a] |= PROJ_VERT_CULL;
+ }
+ }
+ else {
+ VecSubf(viewDirPersp, ps->viewPos, v->co);
+ Normalize(viewDirPersp);
+ if (NormalizedVecAngle2(viewDirPersp, no) >= ps->normal_angle) { /* 1 vert of this face is towards us */
+ ps->vertFlags[a] |= PROJ_VERT_CULL;
+ }
+ }
+ }
+ }
+
+ /* setup clone offset */
+ if (ps->tool == PAINT_TOOL_CLONE) {
+ float projCo[4];
+ float *curs= give_cursor();
+ VECCOPY(projCo, curs);
+ Mat4MulVecfl(ps->ob->imat, projCo);
+
+ projCo[3] = 1.0f;
+ Mat4MulVec4fl(ps->projectMat, projCo);
+ ps->cloneOffset[0] = mval[0] - ((float)(curarea->winx/2.0f)+(curarea->winx/2.0f)*projCo[0]/projCo[3]);
+ ps->cloneOffset[1] = mval[1] - ((float)(curarea->winy/2.0f)+(curarea->winy/2.0f)*projCo[1]/projCo[3]);
+
+ // printf("%f %f %f %f %f\n", ps->cloneOffset[0], ps->cloneOffset[1], curs[0], curs[1], curs[2]);
+
+ }
+
+
+
+ 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], (void *)face_index, arena);
+ BLI_linklist_prepend_arena(&ps->vertFaces[mf->v2], (void *)face_index, arena);
+ BLI_linklist_prepend_arena(&ps->vertFaces[mf->v3], (void *)face_index, arena);
+ if (mf->v4) {
+ BLI_linklist_prepend_arena(&ps->vertFaces[ mf->v4 ], (void *)face_index, arena);
+ }
+ }
+#endif
+
+ if (tf->tpage && ((G.f & G_FACESELECT)==0 || mf->flag & ME_FACE_SEL)) {
+
+ float *v1coSS, *v2coSS, *v3coSS, *v4coSS;
+
+ v1coSS = ps->screenCoords[mf->v1];
+ v2coSS = ps->screenCoords[mf->v2];
+ v3coSS = ps->screenCoords[mf->v3];
+ if (mf->v4) {
+ v4coSS = ps->screenCoords[mf->v4];
+ }
+
+
+ if (!ps->is_ortho) {
+ if ( v1coSS[0]==MAXFLOAT ||
+ v2coSS[0]==MAXFLOAT ||
+ v3coSS[0]==MAXFLOAT ||
+ (mf->v4 && v4coSS[0]==MAXFLOAT)
+ ) {
+ continue;
+ }
+ }
+
+#ifdef PROJ_DEBUG_WINCLIP
+ /* ignore faces outside the view */
+ if (
+ (v1coSS[0] < ps->screenMin[0] &&
+ v2coSS[0] < ps->screenMin[0] &&
+ v3coSS[0] < ps->screenMin[0] &&
+ (mf->v4 && v4coSS[0] < ps->screenMin[0])) ||
+
+ (v1coSS[0] > ps->screenMax[0] &&
+ v2coSS[0] > ps->screenMax[0] &&
+ v3coSS[0] > ps->screenMax[0] &&
+ (mf->v4 && v4coSS[0] > ps->screenMax[0])) ||
+
+ (v1coSS[1] < ps->screenMin[1] &&
+ v2coSS[1] < ps->screenMin[1] &&
+ v3coSS[1] < ps->screenMin[1] &&
+ (mf->v4 && v4coSS[1] < ps->screenMin[1])) ||
+
+ (v1coSS[1] > ps->screenMax[1] &&
+ v2coSS[1] > ps->screenMax[1] &&
+ v3coSS[1] > ps->screenMax[1] &&
+ (mf->v4 && v4coSS[1] > ps->screenMax[1]))
+ ) {
+ continue;
+ }
+
+#endif //PROJ_DEBUG_WINCLIP
+
+
+ if (ps->do_backfacecull) {
+ if (ps->do_mask_normal) {
+ /* Since we are interpolating the normals of faces, we want to make
+ * sure all the verts are pointing away from the view,
+ * not just the face */
+ if ( (ps->vertFlags[mf->v1] & PROJ_VERT_CULL) &&
+ (ps->vertFlags[mf->v2] & PROJ_VERT_CULL) &&
+ (ps->vertFlags[mf->v3] & PROJ_VERT_CULL) &&
+ (mf->v4==0 || ps->vertFlags[mf->v4] & PROJ_VERT_CULL)
+
+ ) {
+ continue;
+ }
+ }
+ else {
+ if (SIDE_OF_LINE(v1coSS, v2coSS, v3coSS) < 0.0f) {
+ continue;
+ }
+
+ }
+ }
+
+ if (tpage_last != tf->tpage) {
+
+ image_index = BLI_linklist_index(image_LinkList, tf->tpage);
+
+ if (image_index==-1 && BKE_image_get_ibuf((Image *)tf->tpage, NULL)) { /* MemArena dosnt have an append func */
+ BLI_linklist_append(&image_LinkList, tf->tpage);
+ image_index = ps->image_tot;
+ ps->image_tot++;
+ }
+
+ tpage_last = tf->tpage;
+ }
+
+ if (image_index != -1) {
+ /* Initialize the faces screen pixels */
+ /* Add this to a list to initialize later */
+ project_paint_delayed_face_init(ps, mf, tf, face_index);
+ }
+ }
+ }
+
+ /* build an array of images we use*/
+ projIma = ps->projImages = (ProjPaintImage *)BLI_memarena_alloc(arena, sizeof(ProjPaintImage) * ps->image_tot);
+
+ for (node= image_LinkList, i=0; node; node= node->next, i++, projIma++) {
+ projIma->ima = node->link;
+ // calloced - projIma->touch = 0;
+ projIma->ibuf = BKE_image_get_ibuf(projIma->ima, NULL);
+ projIma->partRedrawRect = BLI_memarena_alloc(arena, sizeof(ImagePaintPartialRedraw) * PROJ_BOUNDBOX_SQUARED);
+ // calloced - memset(projIma->partRedrawRect, 0, sizeof(ImagePaintPartialRedraw) * PROJ_BOUNDBOX_SQUARED);
+ }
+
+ /* we have built the array, discard the linked list */
+ BLI_linklist_free(image_LinkList, NULL);
+}
+
+static void project_paint_end(ProjPaintState *ps)
+{
+ int a;
+
+ /* build undo data from original pixel colors */
+ if(U.uiflag & USER_GLOBALUNDO) {
+ ProjPixel *projPixel;
+ ImBuf *tmpibuf = NULL, *tmpibuf_float = NULL;
+ LinkNode *pixel_node;
+ UndoTile *tile;
+ MemArena *arena = ps->arena_mt[0]; /* threaded arena re-used for non threaded case */
+
+ int bucket_tot = (ps->buckets_x * ps->buckets_y); /* we could get an X/Y but easier to loop through all possible buckets */
+ int bucket_index;
+ int tile_index;
+ int x_round, y_round;
+ int x_tile, y_tile;
+ int is_float = -1;
+
+ /* context */
+ ProjPaintImage *last_projIma;
+ int last_image_index = -1;
+ int last_tile_width;
+
+ for(a=0, last_projIma=ps->projImages; a < ps->image_tot; a++, last_projIma++) {
+ int size = sizeof(UndoTile **) * IMAPAINT_TILE_NUMBER(last_projIma->ibuf->x) * IMAPAINT_TILE_NUMBER(last_projIma->ibuf->y);
+ last_projIma->undoRect = (UndoTile **) BLI_memarena_alloc(arena, size);
+ memset(last_projIma->undoRect, 0, size);
+ }
+
+ for (bucket_index = 0; bucket_index < bucket_tot; bucket_index++) {
+ /* loop through all pixels */
+ for(pixel_node= ps->bucketRect[bucket_index]; pixel_node; pixel_node= pixel_node->next) {
+
+ /* ok we have a pixel, was it modified? */
+ projPixel = (ProjPixel *)pixel_node->link;
+
+ if (last_image_index != projPixel->image_index) {
+ /* set the context */
+ last_image_index = projPixel->image_index;
+ last_projIma = ps->projImages + last_image_index;
+ last_tile_width = IMAPAINT_TILE_NUMBER(last_projIma->ibuf->x);
+ is_float = last_projIma->ibuf->rect_float ? 1 : 0;
+ }
+
+
+ if ( (is_float == 0 && projPixel->origColor.uint != *projPixel->pixel.uint_pt) ||
+
+ (is_float == 1 &&
+ ( projPixel->origColor.f[0] != projPixel->pixel.f_pt[0] ||
+ projPixel->origColor.f[1] != projPixel->pixel.f_pt[1] ||
+ projPixel->origColor.f[2] != projPixel->pixel.f_pt[2] ||
+ projPixel->origColor.f[3] != projPixel->pixel.f_pt[3] ))
+ ) {
+
+ x_tile = projPixel->x_px >> IMAPAINT_TILE_BITS;
+ y_tile = projPixel->y_px >> IMAPAINT_TILE_BITS;
+
+ x_round = x_tile * IMAPAINT_TILE_SIZE;
+ y_round = y_tile * IMAPAINT_TILE_SIZE;
+
+ tile_index = x_tile + y_tile * last_tile_width;
+
+ if (last_projIma->undoRect[tile_index]==NULL) {
+ /* add the undo tile from the modified image, then write the original colors back into it */
+ tile = last_projIma->undoRect[tile_index] = undo_init_tile(&last_projIma->ima->id, last_projIma->ibuf, is_float ? (&tmpibuf_float):(&tmpibuf) , x_tile, y_tile);
+ }
+ else {
+ tile = last_projIma->undoRect[tile_index];
+ }
+
+ /* This is a BIT ODD, but overwrite the undo tiles image info with this pixels original color
+ * because allocating the tiles allong the way slows down painting */
+
+ if (is_float) {
+ float *rgba_fp = (float *)tile->rect + (((projPixel->x_px - x_round) + (projPixel->y_px - y_round) * IMAPAINT_TILE_SIZE)) * 4;
+ QUATCOPY(rgba_fp, projPixel->origColor.f);
+ }
+ else {
+ ((unsigned int *)tile->rect)[ (projPixel->x_px - x_round) + (projPixel->y_px - y_round) * IMAPAINT_TILE_SIZE ] = projPixel->origColor.uint;
+ }
+ }
+ }
+ }
+
+ if (tmpibuf) IMB_freeImBuf(tmpibuf);
+ if (tmpibuf_float) IMB_freeImBuf(tmpibuf_float);
+ }
+ /* done calculating undo data */
+
+ MEM_freeN(ps->screenCoords);
+ MEM_freeN(ps->bucketRect);
+ MEM_freeN(ps->bucketFaces);
+ MEM_freeN(ps->bucketFlags);
+
+ if (ps->seam_bleed_px > 0.0f) {
+ MEM_freeN(ps->vertFaces);
+ MEM_freeN(ps->faceSeamFlags);
+ MEM_freeN(ps->faceSeamUVs);
+ }
+
+ if (ps->vertFlags) MEM_freeN(ps->vertFlags);
+
+
+ for (a=0; a<ps->thread_tot; a++) {
+ BLI_memarena_free(ps->arena_mt[a]);
+ }
+
+ ps->dm->release(ps->dm);
+}
+
+/* Use this rather then mouse_cursor()
+ * so rotating the view leaves the 3D Cursor on the clone source surface.
+ */
+static void project_paint_setCursor(void) {
+ float *curs = give_cursor();
+ short mval[2];
+
+ mouse_cursor(); /* set the cursor location on the view X/Y */
+ getmouseco_areawin(mval);
+ view_mouse_depth(curs, mval, 1);
+}
/* 1= an undo, -1 is a redo. */
+static void partial_redraw_array_init(ImagePaintPartialRedraw *pr)
+{
+ int tot = PROJ_BOUNDBOX_SQUARED;
+ while (tot--) {
+ pr->x1 = 10000000;
+ pr->y1 = 10000000;
+
+ pr->x2 = -1;
+ pr->y2 = -1;
+
+ pr->enabled = 1;
+
+ pr++;
+ }
+}
+
+
+static int partial_redraw_array_merge(ImagePaintPartialRedraw *pr, ImagePaintPartialRedraw *pr_other, int tot)
+{
+ int touch;
+ while (tot--) {
+ pr->x1 = MIN2(pr->x1, pr_other->x1);
+ pr->y1 = MIN2(pr->y1, pr_other->y1);
+
+ pr->x2 = MAX2(pr->x2, pr_other->x2);
+ pr->y2 = MAX2(pr->y2, pr_other->y2);
+
+ if (pr->x2 != -1)
+ touch = 1;
+
+ pr++; pr_other++;
+ }
+
+ return touch;
+}
+
+/* Loop over all images on this mesh and update any we have touched */
+static int project_image_refresh_tagged(ProjPaintState *ps)
+{
+ ImagePaintPartialRedraw *pr;
+ ProjPaintImage *projIma;
+ int a,i;
+ int redraw = 0;
+
+
+ for (a=0, projIma=ps->projImages; a < ps->image_tot; a++, projIma++) {
+ if (projIma->touch) {
+ /* look over each bound cell */
+ for (i=0; i<PROJ_BOUNDBOX_SQUARED; i++) {
+ pr = &(projIma->partRedrawRect[i]);
+ if (pr->x2 != -1) { /* TODO - use 'enabled' ? */
+ imapaintpartial = *pr;
+ imapaint_image_update(projIma->ima, projIma->ibuf, 1); /*last 1 is for texpaint*/
+ redraw = 1;
+ }
+ }
+
+ projIma->touch = 0; /* clear for reuse */
+ }
+ }
+
+ return redraw;
+}
+
+/* run this per painting onto each mouse location */
+static int project_bucket_iter_init(ProjPaintState *ps, const float mval_f[2])
+{
+ float min_brush[2], max_brush[2];
+ float size_half = ((float)ps->brush->size) * 0.5f;
+
+ /* so we dont have a bucket bounds that is way too small to paint into */
+ // if (size_half < 1.0f) size_half = 1.0f; // this dosnt work yet :/
+
+ min_brush[0] = mval_f[0] - size_half;
+ min_brush[1] = mval_f[1] - size_half;
+
+ max_brush[0] = mval_f[0] + size_half;
+ max_brush[1] = mval_f[1] + size_half;
+
+ /* offset to make this a valid bucket index */
+ project_paint_bucket_bounds(ps, min_brush, max_brush, ps->bucketMin, ps->bucketMax);
+
+ /* mouse outside the model areas? */
+ if (ps->bucketMin[0]==ps->bucketMax[0] || ps->bucketMin[1]==ps->bucketMax[1]) {
+ return 0;
+ }
+
+ ps->context_bucket_x = ps->bucketMin[0];
+ ps->context_bucket_y = ps->bucketMin[1];
+ return 1;
+}
+
+static int project_bucket_iter_next(ProjPaintState *ps, int *bucket_index, rctf *bucket_bounds, const float mval[2])
+{
+ if (ps->thread_tot > 1)
+ BLI_lock_thread(LOCK_CUSTOM1);
+
+ //printf("%d %d \n", ps->context_bucket_x, ps->context_bucket_y);
+
+ for ( ; ps->context_bucket_y < ps->bucketMax[1]; ps->context_bucket_y++) {
+ for ( ; ps->context_bucket_x < ps->bucketMax[0]; ps->context_bucket_x++) {
+
+ /* use bucket_bounds for project_bucket_isect_circle and project_bucket_init*/
+ project_bucket_bounds(ps, ps->context_bucket_x, ps->context_bucket_y, bucket_bounds);
+
+ if (project_bucket_isect_circle(ps->context_bucket_x, ps->context_bucket_y, mval, ps->brush->size * ps->brush->size, bucket_bounds)) {
+ *bucket_index = ps->context_bucket_x + (ps->context_bucket_y * ps->buckets_x);
+ ps->context_bucket_x++;
+
+ if (ps->thread_tot > 1)
+ BLI_unlock_thread(LOCK_CUSTOM1);
+
+ return 1;
+ }
+ }
+ ps->context_bucket_x = ps->bucketMin[0];
+ }
+
+ if (ps->thread_tot > 1)
+ BLI_unlock_thread(LOCK_CUSTOM1);
+ return 0;
+}
+
+/* Each thread gets one of these, also used as an argument to pass to project_paint_op */
+typedef struct ProjectHandle {
+ /* args */
+ ProjPaintState *ps;
+ float prevmval[2];
+ float mval[2];
+
+ /* annoying but we need to have image bounds per thread, then merge into ps->projectPartialRedraws */
+ ProjPaintImage *projImages; /* array of partial redraws */
+
+ /* thread settings */
+ int thread_index;
+} ProjectHandle;
+
+static void blend_color_mix(unsigned char *cp, const unsigned char *cp1, const unsigned char *cp2, const int fac)
+{
+ /* this and other blending modes previously used >>8 instead of /255. both
+ are not equivalent (>>8 is /256), and the former results in rounding
+ errors that can turn colors black fast after repeated blending */
+ const int mfac= 255-fac;
+
+ cp[0]= (mfac*cp1[0]+fac*cp2[0])/255;
+ cp[1]= (mfac*cp1[1]+fac*cp2[1])/255;
+ cp[2]= (mfac*cp1[2]+fac*cp2[2])/255;
+}
+
+static void blend_color_mix_float(float *cp, const float *cp1, const float *cp2, const float fac)
+{
+ const float mfac= 1.0-fac;
+ cp[0]= mfac*cp1[0] + fac*cp2[0];
+ cp[1]= mfac*cp1[1] + fac*cp2[1];
+ cp[2]= mfac*cp1[2] + fac*cp2[2];
+}
+
+static void do_projectpaint_clone(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, float mask)
+{
+ if (ps->is_airbrush==0 && mask < 1.0f) {
+ projPixel->newColor.uint = IMB_blend_color(projPixel->newColor.uint, ((ProjPixelClone*)projPixel)->clonepx.uint, (int)(alpha*255), ps->blend);
+ blend_color_mix(projPixel->pixel.ch_pt, projPixel->origColor.ch, projPixel->newColor.ch, (int)(mask*255));
+ }
+ else {
+ *projPixel->pixel.uint_pt = IMB_blend_color(*projPixel->pixel.uint_pt, ((ProjPixelClone*)projPixel)->clonepx.uint, (int)(alpha*mask*255), ps->blend);
+ }
+}
+
+static void do_projectpaint_clone_f(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, float mask)
+{
+ if (ps->is_airbrush==0 && mask < 1.0f) {
+ IMB_blend_color_float(projPixel->newColor.f, projPixel->newColor.f, ((ProjPixelClone *)projPixel)->clonepx.f, alpha, ps->blend);
+ blend_color_mix_float(projPixel->pixel.f_pt, projPixel->origColor.f, projPixel->newColor.f, mask);
+ }
+ else {
+ IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->pixel.f_pt, ((ProjPixelClone *)projPixel)->clonepx.f, alpha*mask, ps->blend);
+ }
+}
+
+/* do_projectpaint_smear*
+ *
+ * note, mask is used to modify the alpha here, this is not correct since it allows
+ * accumulation of color greater then 'projPixel->mask' however in the case of smear its not
+ * really that important to be correct as it is with clone and painting
+ */
+static void do_projectpaint_smear(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, float mask, MemArena *smearArena, LinkNode **smearPixels, float co[2])
+{
+ unsigned char rgba_ub[4];
+
+ if (project_paint_PickColor(ps, co, NULL, rgba_ub, 1)==0)
+ return;
+
+ ((ProjPixelClone *)projPixel)->clonepx.uint = IMB_blend_color(*projPixel->pixel.uint_pt, *((unsigned int *)rgba_ub), (int)(alpha*mask*255), ps->blend);
+ BLI_linklist_prepend_arena(smearPixels, (void *)projPixel, smearArena);
+}
+
+static void do_projectpaint_smear_f(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, float mask, MemArena *smearArena, LinkNode **smearPixels_f, float co[2])
+{
+ unsigned char rgba_ub[4];
+ unsigned char rgba_smear[4];
+
+ if (project_paint_PickColor(ps, co, NULL, rgba_ub, 1)==0)
+ return;
+
+ IMAPAINT_FLOAT_RGBA_TO_CHAR(rgba_smear, projPixel->pixel.f_pt);
+ ((ProjPixelClone *)projPixel)->clonepx.uint = IMB_blend_color(*((unsigned int *)rgba_smear), *((unsigned int *)rgba_ub), (int)(alpha*mask*255), ps->blend);
+ BLI_linklist_prepend_arena(smearPixels_f, (void *)projPixel, smearArena);
+}
+
+static void do_projectpaint_draw(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, float mask)
+{
+ unsigned char rgba_ub[4];
+
+ if (ps->is_texbrush) {
+ rgba_ub[0] = FTOCHAR(rgba[0] * ps->brush->rgb[0]);
+ rgba_ub[1] = FTOCHAR(rgba[1] * ps->brush->rgb[1]);
+ rgba_ub[2] = FTOCHAR(rgba[2] * ps->brush->rgb[2]);
+ rgba_ub[3] = FTOCHAR(rgba[3]);
+ }
+ else {
+ IMAPAINT_FLOAT_RGB_TO_CHAR(rgba_ub, ps->brush->rgb);
+ rgba_ub[3] = 255;
+ }
+
+ if (ps->is_airbrush==0 && mask < 1.0f) {
+ projPixel->newColor.uint = IMB_blend_color(projPixel->newColor.uint, *((unsigned int *)rgba_ub), (int)(alpha*255), ps->blend);
+ blend_color_mix(projPixel->pixel.ch_pt, projPixel->origColor.ch, projPixel->newColor.ch, (int)(mask*255));
+ }
+ else {
+ *projPixel->pixel.uint_pt = IMB_blend_color(*projPixel->pixel.uint_pt, *((unsigned int *)rgba_ub), (int)(alpha*mask*255), ps->blend);
+ }
+}
+
+static void do_projectpaint_draw_f(ProjPaintState *ps, ProjPixel *projPixel, float *rgba, float alpha, float mask) {
+ if (ps->is_texbrush) {
+ rgba[0] *= ps->brush->rgb[0];
+ rgba[1] *= ps->brush->rgb[1];
+ rgba[2] *= ps->brush->rgb[2];
+ }
+ else {
+ VECCOPY(rgba, ps->brush->rgb);
+ }
+
+ if (ps->is_airbrush==0 && mask < 1.0f) {
+ IMB_blend_color_float(projPixel->newColor.f, projPixel->newColor.f, rgba, alpha, ps->blend);
+ blend_color_mix_float(projPixel->pixel.f_pt, projPixel->origColor.f, projPixel->newColor.f, mask);
+ }
+ else {
+ IMB_blend_color_float(projPixel->pixel.f_pt, projPixel->pixel.f_pt, rgba, alpha*mask, ps->blend);
+ }
+}
+
+
+
+/* run this for single and multithreaded painting */
+static void *do_projectpaint_thread(void *ph_v)
+{
+ /* First unpack args from the struct */
+ ProjPaintState *ps = ((ProjectHandle *)ph_v)->ps;
+ ProjPaintImage *projImages = ((ProjectHandle *)ph_v)->projImages;
+ const float *lastpos = ((ProjectHandle *)ph_v)->prevmval;
+ const float *pos = ((ProjectHandle *)ph_v)->mval;
+ const int thread_index = ((ProjectHandle *)ph_v)->thread_index;
+ /* Done with args from ProjectHandle */
+
+ LinkNode *node;
+ ProjPixel *projPixel;
+
+ int last_index = -1;
+ ProjPaintImage *last_projIma;
+ ImagePaintPartialRedraw *last_partial_redraw_cell;
+
+ float rgba[4], alpha, dist_nosqrt;
+
+ float brush_size_sqared;
+ float falloff;
+ int bucket_index;
+ int is_floatbuf = 0;
+ const short tool = ps->tool;
+ rctf bucket_bounds;
+
+ /* for smear only */
+ float pos_ofs[2];
+ float co[2];
+ float mask = 1.0f; /* airbrush wont use mask */
+ unsigned short mask_short;
+
+ LinkNode *smearPixels = NULL;
+ LinkNode *smearPixels_f = NULL;
+ MemArena *smearArena = NULL; /* mem arena for this brush projection only */
+
+
+ if (tool==PAINT_TOOL_SMEAR) {
+ pos_ofs[0] = pos[0] - lastpos[0];
+ pos_ofs[1] = pos[1] - lastpos[1];
+
+ smearArena = BLI_memarena_new(1<<16);
+ }
+
+ /* avoid a square root with every dist comparison */
+ brush_size_sqared = ps->brush->size * ps->brush->size;
+
+ /* printf("brush bounds %d %d %d %d\n", bucketMin[0], bucketMin[1], bucketMax[0], bucketMax[1]); */
+
+ while (project_bucket_iter_next(ps, &bucket_index, &bucket_bounds, pos)) {
+
+ /* Check this bucket and its faces are initialized */
+ if (ps->bucketFlags[bucket_index] == PROJ_BUCKET_NULL) {
+ /* No pixels initialized */
+ project_bucket_init(ps, thread_index, bucket_index, &bucket_bounds);
+ }
+
+ for (node = ps->bucketRect[bucket_index]; node; node = node->next) {
+
+ projPixel = (ProjPixel *)node->link;
+
+ /*dist = Vec2Lenf(projPixel->projCoSS, pos);*/ /* correct but uses a sqrt */
+ dist_nosqrt = Vec2Lenf_nosqrt(projPixel->projCoSS, pos);
+
+ /*if (dist < s->brush->size) {*/ /* correct but uses a sqrt */
+ if (dist_nosqrt < brush_size_sqared) {
+ falloff = brush_sample_falloff_noalpha(ps->brush, sqrt(dist_nosqrt));
+ if (falloff > 0.0f) {
+ if (ps->is_texbrush) {
+ brush_sample_tex(ps->brush, projPixel->projCoSS, rgba);
+ alpha = rgba[3];
+ } else {
+ alpha = 1.0f;
+ }
+
+ if (ps->is_airbrush) {
+ /* for an aurbrush there is no real mask, so just multiply the alpha by it */
+ alpha *= falloff * ps->brush->alpha;
+ mask = ((float)projPixel->mask)/65535.0f;
+ }
+ else {
+ /* This brush dosnt accumulate so add some curve to the brushes falloff */
+ falloff = 1.0f - falloff;
+ falloff = 1.0f - (falloff * falloff);
+
+ mask_short = projPixel->mask * (ps->brush->alpha * falloff);
+ if (mask_short > projPixel->mask_max) {
+ mask = ((float)mask_short)/65535.0f;
+ projPixel->mask_max = mask_short;
+ }
+ else {
+ /*mask = ((float)projPixel->mask_max)/65535.0f;*/
+
+ /* Go onto the next pixel */
+ continue;
+ }
+ }
+
+ if (alpha > 0.0f) {
+
+ if (last_index != projPixel->image_index) {
+ last_index = projPixel->image_index;
+ last_projIma = projImages + last_index;
+
+ last_projIma->touch = 1;
+ is_floatbuf = last_projIma->ibuf->rect_float ? 1 : 0;
+ }
+
+ last_partial_redraw_cell = last_projIma->partRedrawRect + projPixel->bb_cell_index;
+ last_partial_redraw_cell->x1 = MIN2(last_partial_redraw_cell->x1, projPixel->x_px);
+ last_partial_redraw_cell->y1 = MIN2(last_partial_redraw_cell->y1, projPixel->y_px);
+
+ last_partial_redraw_cell->x2 = MAX2(last_partial_redraw_cell->x2, projPixel->x_px+1);
+ last_partial_redraw_cell->y2 = MAX2(last_partial_redraw_cell->y2, projPixel->y_px+1);
+
+
+ switch(tool) {
+ case PAINT_TOOL_CLONE:
+ if (is_floatbuf) {
+ if (((ProjPixelClone *)projPixel)->clonepx.f[3]) {
+ do_projectpaint_clone_f(ps, projPixel, rgba, alpha, mask);
+ }
+ }
+ else {
+ if (((ProjPixelClone*)projPixel)->clonepx.ch[3]) {
+ do_projectpaint_clone(ps, projPixel, rgba, alpha, mask);
+ }
+ }
+ break;
+ case PAINT_TOOL_SMEAR:
+ Vec2Subf(co, projPixel->projCoSS, pos_ofs);
+
+ if (is_floatbuf) do_projectpaint_smear_f(ps, projPixel, rgba, alpha, mask, smearArena, &smearPixels_f, co);
+ else do_projectpaint_smear(ps, projPixel, rgba, alpha, mask, smearArena, &smearPixels, co);
+ break;
+ default:
+ if (is_floatbuf) do_projectpaint_draw_f(ps, projPixel, rgba, alpha, mask);
+ else do_projectpaint_draw(ps, projPixel, rgba, alpha, mask);
+ break;
+ }
+ }
+ /* done painting */
+ }
+ }
+ }
+ }
+
+
+ if (tool==PAINT_TOOL_SMEAR) {
+
+ for (node= smearPixels; node; node= node->next) { /* this wont run for a float image */
+ projPixel = node->link;
+ *projPixel->pixel.uint_pt = ((ProjPixelClone *)projPixel)->clonepx.uint;
+ }
+
+ for (node= smearPixels_f; node; node= node->next) { /* this wont run for a float image */
+ projPixel = node->link;
+ IMAPAINT_CHAR_RGBA_TO_FLOAT(projPixel->pixel.f_pt, ((ProjPixelClone *)projPixel)->clonepx.ch);
+ node = node->next;
+ }
+
+ BLI_memarena_free(smearArena);
+ }
+
+ return NULL;
+}
+
+static int project_paint_op(void *state, ImBuf *ibufb, float *lastpos, float *pos)
+{
+ /* First unpack args from the struct */
+ ProjPaintState *ps = (ProjPaintState *)state;
+ int touch_any = 0;
+
+ ProjectHandle handles[BLENDER_MAX_THREADS];
+ ListBase threads;
+ int a,i;
+
+ if (!project_bucket_iter_init(ps, pos)) {
+ return 0;
+ }
+
+ if (ps->thread_tot > 1)
+ BLI_init_threads(&threads, do_projectpaint_thread, ps->thread_tot);
+
+ /* get the threads running */
+ for(a=0; a < ps->thread_tot; a++) {
+
+ /* set defaults in handles */
+ //memset(&handles[a], 0, sizeof(BakeShade));
+
+ handles[a].ps = ps;
+ VECCOPY2D(handles[a].mval, pos);
+ VECCOPY2D(handles[a].prevmval, lastpos);
+
+ /* thread spesific */
+ handles[a].thread_index = a;
+
+ handles[a].projImages = (ProjPaintImage *)BLI_memarena_alloc(ps->arena_mt[a], ps->image_tot * sizeof(ProjPaintImage));
+
+ memcpy(handles[a].projImages, ps->projImages, ps->image_tot * sizeof(ProjPaintImage));
+
+ /* image bounds */
+ for (i=0; i< ps->image_tot; i++) {
+ handles[a].projImages[i].partRedrawRect = (ImagePaintPartialRedraw *)BLI_memarena_alloc(ps->arena_mt[a], sizeof(ImagePaintPartialRedraw) * PROJ_BOUNDBOX_SQUARED);
+ memcpy(handles[a].projImages[i].partRedrawRect, ps->projImages[i].partRedrawRect, sizeof(ImagePaintPartialRedraw) * PROJ_BOUNDBOX_SQUARED);
+ }
+
+ if (ps->thread_tot > 1)
+ BLI_insert_thread(&threads, &handles[a]);
+ }
+
+ if (ps->thread_tot > 1) /* wait for everything to be done */
+ BLI_end_threads(&threads);
+ else
+ do_projectpaint_thread(&handles[0]);
+
+
+ /* move threaded bounds back into ps->projectPartialRedraws */
+ for(i=0; i < ps->image_tot; i++) {
+ int touch = 0;
+ for(a=0; a < ps->thread_tot; a++) {
+ touch |= partial_redraw_array_merge(ps->projImages[i].partRedrawRect, handles[a].projImages[i].partRedrawRect, PROJ_BOUNDBOX_SQUARED);
+ }
+
+ if (touch) {
+ ps->projImages[i].touch = 1;
+ touch_any = 1;
+ }
+ }
+
+ return touch_any;
+}
+
+
+static int project_paint_sub_stroke(ProjPaintState *ps, BrushPainter *painter, short *prevmval_i, short *mval_i, double time, float pressure)
+{
+
+ /* Use mouse coords as floats for projection painting */
+ float pos[2];
+
+ pos[0] = mval_i[0];
+ pos[1] = mval_i[1];
+
+ // we may want to use this later
+ // brush_painter_require_imbuf(painter, ((ibuf->rect_float)? 1: 0), 0, 0);
+
+ if (brush_painter_paint(painter, project_paint_op, pos, time, pressure, ps)) {
+ return 1;
+ }
+ else return 0;
+}
+
+
+static int project_paint_stroke(ProjPaintState *ps, BrushPainter *painter, short *prevmval_i, short *mval_i, double time, int update, float pressure)
+{
+ int a, redraw = 0;
+
+ for (a=0; a < ps->image_tot; a++) {
+ partial_redraw_array_init(ps->projImages[a].partRedrawRect);
+ }
+
+ redraw |= project_paint_sub_stroke(ps, painter, prevmval_i, mval_i, time, pressure);
+
+ if (update) {
+ if (project_image_refresh_tagged(ps)) {
+ if (redraw) {
+ force_draw(0); /* imapaint_redraw just calls this in viewport paint anyway */
+ /* imapaint_redraw(0, 1, NULL); */
+ /* imapaint_clear_partial_redraw(); */ /* not needed since we use our own array */
+ }
+ }
+ }
+
+ return redraw;
+}
+
void undo_imagepaint_step(int step)
{
UndoElem *undo;
@@ -333,9 +3944,9 @@ static void imapaint_clear_partial_redraw()
static void imapaint_dirty_region(Image *ima, ImBuf *ibuf, int x, int y, int w, int h)
{
- ImBuf *tmpibuf;
+ ImBuf *tmpibuf = NULL;
UndoTile *tile;
- int srcx= 0, srcy= 0, origx, allocsize;
+ int srcx= 0, srcy= 0, origx;
IMB_rectclip(ibuf, NULL, &x, &y, &srcx, &srcy, &w, &h);
@@ -360,9 +3971,6 @@ static void imapaint_dirty_region(Image *ima, ImBuf *ibuf, int x, int y, int w,
h = ((y + h - 1) >> IMAPAINT_TILE_BITS);
origx = (x >> IMAPAINT_TILE_BITS);
y = (y >> IMAPAINT_TILE_BITS);
-
- tmpibuf= IMB_allocImBuf(IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE, 32,
- IB_rectfloat|IB_rect, 0);
for (; y <= h; y++) {
for (x=origx; x <= w; x++) {
@@ -371,32 +3979,21 @@ static void imapaint_dirty_region(Image *ima, ImBuf *ibuf, int x, int y, int w,
break;
if(!tile) {
- tile= MEM_callocN(sizeof(UndoTile), "ImaUndoTile");
- tile->id= ima->id;
- tile->x= x;
- tile->y= y;
-
- allocsize= IMAPAINT_TILE_SIZE*IMAPAINT_TILE_SIZE*4;
- allocsize *= (ibuf->rect_float)? sizeof(float): sizeof(char);
- tile->rect= MEM_mapallocN(allocsize, "ImaUndoRect");
-
- undo_copy_tile(tile, tmpibuf, ibuf, 0);
- curundo->undosize += allocsize;
-
- BLI_addtail(&curundo->tiles, tile);
+ undo_init_tile(&ima->id, ibuf, &tmpibuf, x, y);
}
}
}
ibuf->userflags |= IB_BITMAPDIRTY;
-
- IMB_freeImBuf(tmpibuf);
+
+ if (tmpibuf)
+ IMB_freeImBuf(tmpibuf);
}
static void imapaint_image_update(Image *image, ImBuf *ibuf, short texpaint)
{
if(ibuf->rect_float)
- imb_freerectImBuf(ibuf); /* force recreate of char rect */
+ imb_freerectImBuf(ibuf); /* force recreate of char rect */ /* TODO - should just update a portion from imapaintpartial! */
if(ibuf->mipmap[0])
imb_freemipmapImBuf(ibuf);
@@ -404,6 +4001,7 @@ static void imapaint_image_update(Image *image, ImBuf *ibuf, short texpaint)
if(texpaint || G.sima->lock) {
int w = imapaintpartial.x2 - imapaintpartial.x1;
int h = imapaintpartial.y2 - imapaintpartial.y1;
+ // printf("%d, %d, \n", w, h);
GPU_paint_update_image(image, imapaintpartial.x1, imapaintpartial.y1, w, h);
}
}
@@ -461,14 +4059,20 @@ static void imapaint_ibuf_get_set_rgb(ImBuf *ibuf, int x, int y, short torus, sh
if (ibuf->rect_float) {
float *rrgbf = ibuf->rect_float + (ibuf->x*y + x)*4;
- if (set) IMAPAINT_FLOAT_RGB_COPY(rrgbf, rgb)
- else IMAPAINT_FLOAT_RGB_COPY(rgb, rrgbf)
+ if (set) {
+ IMAPAINT_FLOAT_RGB_COPY(rrgbf, rgb);
+ } else {
+ IMAPAINT_FLOAT_RGB_COPY(rgb, rrgbf);
+ }
}
else {
char *rrgb = (char*)ibuf->rect + (ibuf->x*y + x)*4;
- if (set) IMAPAINT_FLOAT_RGB_TO_CHAR(rrgb, rgb)
- else IMAPAINT_CHAR_RGB_TO_FLOAT(rgb, rrgb)
+ if (set) {
+ IMAPAINT_FLOAT_RGB_TO_CHAR(rrgb, rgb)
+ } else {
+ IMAPAINT_CHAR_RGB_TO_FLOAT(rgb, rrgb)
+ }
}
}
@@ -569,6 +4173,8 @@ static void imapaint_convert_brushco(ImBuf *ibufb, float *pos, int *ipos)
ipos[1]= (int)(pos[1] - ibufb->y/2);
}
+/* dosnt run for projection painting
+ * only the old style painting in the 3d view */
static int imapaint_paint_op(void *state, ImBuf *ibufb, float *lastpos, float *pos)
{
ImagePaintState *s= ((ImagePaintState*)state);
@@ -793,30 +4399,53 @@ static void imapaint_paint_stroke(ImagePaintState *s, BrushPainter *painter, sho
void imagepaint_paint(short mousebutton, short texpaint)
{
ImagePaintState s;
+ ProjPaintState ps;
BrushPainter *painter;
ToolSettings *settings= G.scene->toolsettings;
- short prevmval[2], mval[2];
+ short prevmval[2], mval[2], project = 0;
double time;
float pressure;
-
+ int init = 1;
+
if(!settings->imapaint.brush)
return;
-
+
+ if (texpaint) { /* are we painting in the 3D view ? */
+ if ((settings->imapaint.flag & IMAGEPAINT_PROJECT_DISABLE)==0) {
+ project= 1;
+ }
+ }
+
+ if (G.qual & LR_CTRLKEY) {
+ persp(PERSP_VIEW);
+ project_paint_setCursor();
+ persp(PERSP_WIN);
+ return;
+ }
+
/* initialize state */
memset(&s, 0, sizeof(s));
+ memset(&ps, 0, sizeof(ps));
+
s.brush = settings->imapaint.brush;
s.tool = settings->imapaint.tool;
- if(texpaint && (s.tool == PAINT_TOOL_CLONE))
+ if(texpaint && (project==0) && (s.tool == PAINT_TOOL_CLONE))
s.tool = PAINT_TOOL_DRAW;
s.blend = s.brush->blend;
-
+
+ if (project) {
+ ps.brush = s.brush;
+ ps.tool = s.tool;
+ ps.blend = s.blend;
+ }
+
if(texpaint) {
- s.ob = OBACT;
+ ps.ob = s.ob = OBACT;
if (!s.ob || !(s.ob->lay & G.vd->lay)) return;
s.me = get_mesh(s.ob);
if (!s.me) return;
- persp(PERSP_VIEW);
+ persp(PERSP_VIEW); /* set back to PERSP_WIN before returning */
}
else {
s.image = G.sima->image;
@@ -829,28 +4458,76 @@ void imagepaint_paint(short mousebutton, short texpaint)
return;
}
}
+
+ getmouseco_areawin(mval); /* make sure this runs before project_paint_begin() */
+
+ /* note, if we have no UVs on the derived mesh, then we must return here */
+ if (project) {
+ /* 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;;
+
+ if (ps.tool == PAINT_TOOL_CLONE)
+ ps.do_layer_clone = (settings->imapaint.flag & IMAGEPAINT_PROJECT_LAYER_CLONE);
+
+ ps.do_layer_mask = (settings->imapaint.flag & IMAGEPAINT_PROJECT_LAYER_MASK) ? 1 : 0;
+ ps.do_layer_mask_inv = (settings->imapaint.flag & IMAGEPAINT_PROJECT_LAYER_MASK_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 *= M_PI_2 / 90;
+ ps.normal_angle *= 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 = 0; /* no need to do blending */
+
+ project_paint_begin(&ps, mval);
+
+ if (ps.dm==NULL) {
+ persp(PERSP_WIN);
+ return;
+ }
+ }
+
settings->imapaint.flag |= IMAGEPAINT_DRAWING;
undo_imagepaint_push_begin("Image Paint");
/* create painter and paint once */
painter= brush_painter_new(s.brush);
- getmouseco_areawin(mval);
-
pressure = get_pressure();
s.blend = (get_activedevice() == 2)? BRUSH_BLEND_ERASE_ALPHA: s.brush->blend;
time= PIL_check_seconds_timer();
prevmval[0]= mval[0];
prevmval[1]= mval[1];
-
+
/* special exception here for too high pressure values on first touch in
- windows for some tablets */
- if (!((s.brush->flag & (BRUSH_ALPHA_PRESSURE|BRUSH_SIZE_PRESSURE|
+ windows for some tablets */
+ if (!((s.brush->flag & (BRUSH_ALPHA_PRESSURE|BRUSH_SIZE_PRESSURE|
BRUSH_SPACING_PRESSURE|BRUSH_RAD_PRESSURE)) && (get_activedevice() != 0) && (pressure >= 0.99f)))
- imapaint_paint_stroke(&s, painter, texpaint, prevmval, mval, time, pressure);
-
+ {
+ if (project) {
+ project_paint_stroke(&ps, painter, prevmval, mval, time, 1, pressure);
+ }
+ else {
+ imapaint_paint_stroke(&s, painter, texpaint, prevmval, mval, time, pressure);
+ }
+ }
+
/* paint loop */
do {
getmouseco_areawin(mval);
@@ -860,16 +4537,37 @@ void imagepaint_paint(short mousebutton, short texpaint)
time= PIL_check_seconds_timer();
- if((mval[0] != prevmval[0]) || (mval[1] != prevmval[1])) {
- imapaint_paint_stroke(&s, painter, texpaint, prevmval, mval, time, pressure);
- prevmval[0]= mval[0];
- prevmval[1]= mval[1];
- }
- else if (s.brush->flag & BRUSH_AIRBRUSH)
- imapaint_paint_stroke(&s, painter, texpaint, prevmval, mval, time, pressure);
- else
- BIF_wait_for_statechange();
+ if (project) { /* Projection Painting */
+ int redraw = 1;
+
+ if (((s.brush->flag & BRUSH_AIRBRUSH) || init) || ((mval[0] != prevmval[0]) || (mval[1] != prevmval[1]))) {
+ redraw = project_paint_stroke(&ps, painter, prevmval, mval, time, 1, pressure);
+ prevmval[0]= mval[0];
+ prevmval[1]= mval[1];
+ }
+ else {
+ BIF_wait_for_statechange();
+ if (redraw==0) {
+ /* Only so the brush outline is redrawn, pitty we need to do this
+ * however it wont run when the mouse is still so not too bad */
+ force_draw(0);
+ }
+ }
+
+ init = 0;
+ }
+ else {
+ if((mval[0] != prevmval[0]) || (mval[1] != prevmval[1])) {
+ imapaint_paint_stroke(&s, painter, texpaint, prevmval, mval, time, pressure);
+ prevmval[0]= mval[0];
+ prevmval[1]= mval[1];
+ }
+ else if (s.brush->flag & BRUSH_AIRBRUSH)
+ imapaint_paint_stroke(&s, painter, texpaint, prevmval, mval, time, pressure);
+ else
+ BIF_wait_for_statechange();
+ }
/* do mouse checking at the end, so don't check twice, and potentially
miss a short tap */
} while(get_mbut() & mousebutton);
@@ -879,6 +4577,10 @@ void imagepaint_paint(short mousebutton, short texpaint)
imapaint_canvas_free(&s);
brush_painter_free(painter);
+ if (project) {
+ project_paint_end(&ps);
+ }
+
imapaint_redraw(1, texpaint, s.image);
undo_imagepaint_push_end();
diff --git a/source/blender/src/space.c b/source/blender/src/space.c
index e34624a25fe..4267159baee 100644
--- a/source/blender/src/space.c
+++ b/source/blender/src/space.c
@@ -1307,6 +1307,14 @@ static void winqreadview3dspace(ScrArea *sa, void *spacedata, BWinEvent *evt)
}
}
}
+ else if (!G.obedit && OBACT && G.f&G_TEXTUREPAINT){
+ if(G.scene->toolsettings->imapaint.brush &&
+ event!=LEFTMOUSE && event!=RIGHTMOUSE && event!=MIDDLEMOUSE &&
+ (event==MOUSEY || event==MOUSEX) && bwin_qtest(sa->win)==0) {
+ allqueue(REDRAWVIEW3D, 0);
+ }
+ }
+
/* Handle retopo painting */
if(retopo_mesh_paint_check()) {
diff --git a/source/blender/src/view.c b/source/blender/src/view.c
index 258e4fd99e0..8c16cfe705d 100644
--- a/source/blender/src/view.c
+++ b/source/blender/src/view.c
@@ -796,12 +796,15 @@ void viewmoveNDOFfly(int mode)
BIF_view3d_previewrender_signal(curarea, PR_DBASE|PR_DISPRECT);
}
-int view_autodist( float mouse_worldloc[3] ) //, float *autodist )
+/* Be sure to run persp(PERSP_VIEW) if this isnt set,
+ *
+ * mouse_worldloc - worldspace vector that is set
+ * mval - screenspace location, or from getmouseco_areawin(mval)
+ * dist - the size of the square to use when averaging the Z depth.
+ */
+int view_mouse_depth( float mouse_worldloc[3], short mval[2], int dist)
{
View3D *v3d = G.vd;
-
- /* Zooms in on a border drawn by the user */
- short mval[2];
rcti rect;
/* ZBuffer depth vars */
@@ -813,13 +816,13 @@ int view_autodist( float mouse_worldloc[3] ) //, float *autodist )
getmouseco_areawin(mval);
- persp(PERSP_VIEW);
+ /* persp(PERSP_VIEW); */
- rect.xmax = mval[0] + 4;
- rect.ymax = mval[1] + 4;
+ rect.xmax = mval[0] + dist;
+ rect.ymax = mval[1] + dist;
- rect.xmin = mval[0] - 4;
- rect.ymin = mval[1] - 4;
+ rect.xmin = mval[0] - dist;
+ rect.ymin = mval[1] - dist;
/* Get Z Depths, needed for perspective, nice for ortho */
bgl_get_mats(&mats);
@@ -943,7 +946,8 @@ void viewmove(int mode)
VecMulf(obofs, -1.0f);
}
else if (U.uiflag & USER_ORBIT_ZBUF) {
- if ((use_sel=view_autodist(obofs))) {
+ persp(PERSP_VIEW);
+ if ((use_sel=view_mouse_depth(obofs, mval_area, 4))) {
if (G.vd->persp==V3D_PERSP) {
float my_origin[3]; /* original G.vd->ofs */
float my_pivot[3]; /* view */
@@ -974,6 +978,8 @@ void viewmove(int mode)
} else {
ofs[0] = ofs[1] = ofs[2] = 0.0f;
}
+
+ persp(PERSP_WIN);
}
else
ofs[0] = ofs[1] = ofs[2] = 0.0f;