doxygen: blender/editors tagged.
[blender.git] / source / blender / editors / gpencil / gpencil_paint.c
index bf9dea7c97894dbb6a6c4a9e9bfe0ab4a62dd098..472750113b91d27db3bafd40535e55b3250d6732 100644 (file)
@@ -1,4 +1,4 @@
-/**
+/*
  * $Id$
  *
  * ***** BEGIN GPL LICENSE BLOCK *****
  * ***** END GPL LICENSE BLOCK *****
  */
 
+/** \file blender/editors/gpencil/gpencil_paint.c
+ *  \ingroup edgpencil
+ */
+
+
 #include <stdio.h>
 #include <stddef.h>
 #include <stdlib.h>
@@ -35,6 +40,7 @@
 
 #include "BLI_blenlib.h"
 #include "BLI_math.h"
+#include "BLI_utildefines.h"
 
 #include "BKE_gpencil.h"
 #include "BKE_context.h"
@@ -44,6 +50,7 @@
 #include "DNA_object_types.h"
 #include "DNA_scene_types.h"
 #include "DNA_gpencil_types.h"
+#include "DNA_windowmanager_types.h"
 
 #include "UI_view2d.h"
 
@@ -65,6 +72,8 @@
 /* Temporary 'Stroke' Operation data */
 typedef struct tGPsdata {
        Scene *scene;       /* current scene from context */
+       
+       wmWindow *win;          /* window where painting originated */
        ScrArea *sa;            /* area where painting originated */
        ARegion *ar;        /* region where painting originated */
        View2D *v2d;            /* needed for GP_STROKE_2DSPACE */
@@ -180,7 +189,7 @@ static void gp_get_3d_reference (tGPsdata *p, float *vec)
        float *fp= give_cursor(p->scene, v3d);
        
        /* the reference point used depends on the owner... */
-#if 0 // XXX: disabled for now, since we can't draw relative ot the owner yet
+#if 0 // XXX: disabled for now, since we can't draw relative to the owner yet
        if (p->ownerPtr.type == &RNA_Object) 
        {
                Object *ob= (Object *)p->ownerPtr.data;
@@ -210,13 +219,17 @@ static short gp_stroke_filtermval (tGPsdata *p, int mval[2], int pmval[2])
        if (p->gpd->sbuffer_size == 0)
                return 1;
        
-       /* check if mouse moved at least certain distance on both axes (best case) */
+       /* check if mouse moved at least certain distance on both axes (best case) 
+        *      - aims to eliminate some jitter-noise from input when trying to draw straight lines freehand
+        */
        else if ((dx > MIN_MANHATTEN_PX) && (dy > MIN_MANHATTEN_PX))
                return 1;
        
-       /* check if the distance since the last point is significant enough */
-       // future optimisation: sqrt here may be too slow?
-       else if (sqrt(dx*dx + dy*dy) > MIN_EUCLIDEAN_PX)
+       /* check if the distance since the last point is significant enough 
+        *      - prevents points being added too densely
+        *      - distance here doesn't use sqrt to prevent slowness... we should still be safe from overflows though
+        */
+       else if ((dx*dx + dy*dy) > MIN_EUCLIDEAN_PX*MIN_EUCLIDEAN_PX)
                return 1;
        
        /* mouse 'didn't move' */
@@ -232,7 +245,7 @@ static void gp_stroke_convertcoords (tGPsdata *p, short mval[], float out[], flo
        
        /* in 3d-space - pt->x/y/z are 3 side-by-side floats */
        if (gpd->sbuffer_sflag & GP_STROKE_3DSPACE) {
-               if(gpencil_project_check(p) && (view_autodist_simple(p->ar, mval, out, 0, depth))) {
+               if (gpencil_project_check(p) && (view_autodist_simple(p->ar, mval, out, 0, depth))) {
                        /* projecting onto 3D-Geometry
                         *      - nothing more needs to be done here, since view_autodist_simple() has already done it
                         */
@@ -290,7 +303,7 @@ static void gp_stroke_convertcoords (tGPsdata *p, short mval[], float out[], flo
        
        /* 2d - relative to screen (viewport area) */
        else {
-               if(p->subrect == NULL) { /* normal 3D view */
+               if (p->subrect == NULL) { /* normal 3D view */
                        out[0] = (float)(mval[0]) / (float)(p->ar->winx) * 100;
                        out[1] = (float)(mval[1]) / (float)(p->ar->winy) * 100;
                }
@@ -371,10 +384,18 @@ static short gp_stroke_addpoint (tGPsdata *p, int mval[2], float pressure)
        return GP_STROKEADD_INVALID;
 }
 
+
+/* temp struct for gp_stroke_smooth() */
+typedef struct tGpSmoothCo {
+       short x;
+       short y;
+} tGpSmoothCo;
+
 /* smooth a stroke (in buffer) before storing it */
 static void gp_stroke_smooth (tGPsdata *p)
 {
        bGPdata *gpd= p->gpd;
+       tGpSmoothCo *smoothArray, *spc;
        int i=0, cmx=gpd->sbuffer_size;
        
        /* only smooth if smoothing is enabled, and we're not doing a straight line */
@@ -385,17 +406,31 @@ static void gp_stroke_smooth (tGPsdata *p)
        if ((cmx <= 2) || (gpd->sbuffer == NULL))
                return;
        
-       /* apply weighting-average (note doing this along path sequentially does introduce slight error) */
-       for (i=0; i < gpd->sbuffer_size; i++) {
+       /* create a temporary smoothing coordinates buffer, use to store calculated values to prevent sequential error */
+       smoothArray = MEM_callocN(sizeof(tGpSmoothCo)*cmx, "gp_stroke_smooth smoothArray");
+       
+       /* first pass: calculate smoothing coordinates using weighted-averages */
+       for (i=0, spc=smoothArray; i < gpd->sbuffer_size; i++, spc++) {
+               const tGPspoint *pc= (((tGPspoint *)gpd->sbuffer) + i);
+               const tGPspoint *pb= (i-1 > 0)?(pc-1):(pc);
+               const tGPspoint *pa= (i-2 > 0)?(pc-2):(pb);
+               const tGPspoint *pd= (i+1 < cmx)?(pc+1):(pc);
+               const tGPspoint *pe= (i+2 < cmx)?(pc+2):(pd);
+               
+               spc->x= (short)(0.1*pa->x + 0.2*pb->x + 0.4*pc->x + 0.2*pd->x + 0.1*pe->x);
+               spc->y= (short)(0.1*pa->y + 0.2*pb->y + 0.4*pc->y + 0.2*pd->y + 0.1*pe->y);
+       }
+       
+       /* second pass: apply smoothed coordinates */
+       for (i=0, spc=smoothArray; i < gpd->sbuffer_size; i++, spc++) {
                tGPspoint *pc= (((tGPspoint *)gpd->sbuffer) + i);
-               tGPspoint *pb= (i-1 > 0)?(pc-1):(pc);
-               tGPspoint *pa= (i-2 > 0)?(pc-2):(pb);
-               tGPspoint *pd= (i+1 < cmx)?(pc+1):(pc);
-               tGPspoint *pe= (i+2 < cmx)?(pc+2):(pd);
                
-               pc->x= (short)(0.1*pa->x + 0.2*pb->x + 0.4*pc->x + 0.2*pd->x + 0.1*pe->x);
-               pc->y= (short)(0.1*pa->y + 0.2*pb->y + 0.4*pc->y + 0.2*pd->y + 0.1*pe->y);
+               pc->x = spc->x;
+               pc->y = spc->y;
        }
+       
+       /* free temp array */
+       MEM_freeN(smoothArray);
 }
 
 /* simplify a stroke (in buffer) before storing it 
@@ -410,12 +445,12 @@ static void gp_stroke_simplify (tGPsdata *p)
        short flag= gpd->sbuffer_sflag;
        short i, j;
        
-       /* only simplify if simlification is enabled, and we're not doing a straight line */
+       /* only simplify if simplification is enabled, and we're not doing a straight line */
        if (!(U.gp_settings & GP_PAINT_DOSIMPLIFY) || (p->paintmode == GP_PAINTMODE_DRAW_STRAIGHT))
                return;
        
        /* don't simplify if less than 4 points in buffer */
-       if ((num_points <= 2) || (old_points == NULL))
+       if ((num_points <= 4) || (old_points == NULL))
                return;
                
        /* clear buffer (but don't free mem yet) so that we can write to it 
@@ -536,60 +571,69 @@ static void gp_stroke_newfrombuffer (tGPsdata *p)
        }
        else {
                float *depth_arr= NULL;
-
+               
                /* get an array of depths, far depths are blended */
-               if(gpencil_project_check(p)) {
-                       short mval[2];
+               if (gpencil_project_check(p)) {
+                       short mval[2], mval_prev[2]= {0};
                        int interp_depth = 0;
                        int found_depth = 0;
-
+                       
                        depth_arr= MEM_mallocN(sizeof(float) * gpd->sbuffer_size, "depth_points");
 
                        for (i=0, ptc=gpd->sbuffer; i < gpd->sbuffer_size; i++, ptc++, pt++) {
                                mval[0]= ptc->x; mval[1]= ptc->y;
-                               if(view_autodist_depth(p->ar, mval, depth_margin, depth_arr+i) == 0)
+
+                               if ((view_autodist_depth(p->ar, mval, depth_margin, depth_arr+i) == 0) &&
+                                       (i && (view_autodist_depth_segment(p->ar, mval, mval_prev, depth_margin + 1, depth_arr+i) == 0))
+                               ) {
                                        interp_depth= TRUE;
-                               else
+                               }
+                               else {
                                        found_depth= TRUE;
-                       }
+                               }
 
-                       if(found_depth==FALSE) {
+                               VECCOPY2D(mval_prev, mval);
+                       }
+                       
+                       if (found_depth == FALSE) {
                                /* eeh... not much we can do.. :/, ignore depth in this case, use the 3D cursor */
                                for (i=gpd->sbuffer_size-1; i >= 0; i--)
                                        depth_arr[i] = 0.9999f;
                        }
                        else {
-                               if(p->gpd->flag & GP_DATA_DEPTH_STROKE_ENDPOINTS) {
+                               if (p->gpd->flag & GP_DATA_DEPTH_STROKE_ENDPOINTS) {
                                        /* remove all info between the valid endpoints */
                                        int first_valid = 0;
                                        int last_valid = 0;
-
-                                       for (i=0; i < gpd->sbuffer_size; i++)
-                                               if(depth_arr[i] != FLT_MAX)
+                                       
+                                       for (i=0; i < gpd->sbuffer_size; i++) {
+                                               if (depth_arr[i] != FLT_MAX)
                                                        break;
+                                       }
                                        first_valid= i;
-
-                                       for (i=gpd->sbuffer_size-1; i >= 0; i--)
-                                               if(depth_arr[i] != FLT_MAX)
+                                       
+                                       for (i=gpd->sbuffer_size-1; i >= 0; i--) {
+                                               if (depth_arr[i] != FLT_MAX)
                                                        break;
+                                       }
                                        last_valid= i;
-
+                                       
                                        /* invalidate non-endpoints, so only blend between first and last */
                                        for (i=first_valid+1; i < last_valid; i++)
                                                depth_arr[i]= FLT_MAX;
-
+                                       
                                        interp_depth= TRUE;
                                }
-
-                               if(interp_depth) {
+                               
+                               if (interp_depth) {
                                        interp_sparse_array(depth_arr, gpd->sbuffer_size, FLT_MAX);
                                }
                        }
                }
-
-
+               
+               
                pt= gps->points;
-
+               
                /* convert all points (normal behaviour) */
                for (i=0, ptc=gpd->sbuffer; i < gpd->sbuffer_size && ptc; i++, ptc++, pt++) {
                        /* convert screen-coordinates to appropriate coordinates (and store them) */
@@ -598,8 +642,8 @@ static void gp_stroke_newfrombuffer (tGPsdata *p)
                        /* copy pressure */
                        pt->pressure= ptc->pressure;
                }
-
-               if(depth_arr)
+               
+               if (depth_arr)
                        MEM_freeN(depth_arr);
        }
        
@@ -729,7 +773,7 @@ static void gp_stroke_eraser_dostroke (tGPsdata *p, int mval[], int mvalo[], sho
                }
 #endif
                else {
-                       if(p->subrect == NULL) { /* normal 3D view */
+                       if (p->subrect == NULL) { /* normal 3D view */
                                x0= (int)(gps->points->x / 100 * p->ar->winx);
                                y0= (int)(gps->points->y / 100 * p->ar->winy);
                        }
@@ -881,15 +925,16 @@ static tGPsdata *gp_session_initpaint (bContext *C)
        /* create new context data */
        p= MEM_callocN(sizeof(tGPsdata), "GPencil Drawing Data");
        
-       /* pass on current scene */
+       /* pass on current scene and window */
        p->scene= CTX_data_scene(C);
+       p->win= CTX_wm_window(C);
        
        switch (curarea->spacetype) {
                /* supported views first */
                case SPACE_VIEW3D:
                {
-                       View3D *v3d= curarea->spacedata.first;
-                       RegionView3D *rv3d= ar->regiondata;
+                       // View3D *v3d= curarea->spacedata.first;
+                       // RegionView3D *rv3d= ar->regiondata;
                        
                        /* set current area 
                         *      - must verify that region data is 3D-view (and not something else)
@@ -903,12 +948,6 @@ static tGPsdata *gp_session_initpaint (bContext *C)
                                        printf("Error: 3D-View active region doesn't have any region data, so cannot be drawable \n");
                                return p;
                        }
-                       
-                       /* for camera view set the subrect */
-                       if(rv3d->persp == RV3D_CAMOB) {
-                               view3d_calc_camera_border(p->scene, p->ar, NULL, v3d, &p->subrect_data, -1); /* negative shift */
-                               p->subrect= &p->subrect_data;
-                       }
 
 #if 0 // XXX will this sort of antiquated stuff be restored?
                        /* check that gpencil data is allowed to be drawn */
@@ -1090,9 +1129,17 @@ static void gp_paint_initstroke (tGPsdata *p, short paintmode)
                switch (p->sa->spacetype) {
                        case SPACE_VIEW3D:
                        {
+                               View3D *v3d= p->sa->spacedata.first;
                                RegionView3D *rv3d= p->ar->regiondata;
                                float rvec[3];
                                
+                               /* for camera view set the subrect */
+                               if (rv3d->persp == RV3D_CAMOB) {
+                                       view3d_calc_camera_border(p->scene, p->ar, NULL, v3d, &p->subrect_data, -1); /* negative shift */
+                                       p->subrect= &p->subrect_data;
+                               }
+                               
+                               /* get reference point for 3d space placement */
                                gp_get_3d_reference(p, rvec);
                                initgrabz(rv3d, rvec[0], rvec[1], rvec[2]);
                                
@@ -1165,6 +1212,17 @@ static void gp_paint_initstroke (tGPsdata *p, short paintmode)
 /* finish off a stroke (clears buffer, but doesn't finish the paint operation) */
 static void gp_paint_strokeend (tGPsdata *p)
 {
+       /* for surface sketching, need to set the right OpenGL context stuff so that 
+        * the conversions will project the values correctly...
+        */
+       if (gpencil_project_check(p)) {
+               View3D *v3d= p->sa->spacedata.first;
+               
+               /* need to restore the original projection settings before packing up */
+               view3d_region_operator_needs_opengl(p->win, p->ar);
+               view_autodist_init(p->scene, p->ar, v3d, (p->gpd->flag & GP_DATA_DEPTH_STROKE) ? 1:0);
+       }
+       
        /* check if doing eraser or not */
        if ((p->gpd->sbuffer_sflag & GP_STROKE_ERASER) == 0) {
                /* smooth stroke before transferring? */
@@ -1240,14 +1298,6 @@ static void gpencil_draw_exit (bContext *C, wmOperator *op)
        }
        
        /* cleanup */
-       if (gpencil_project_check(p)) {
-               View3D *v3d= p->sa->spacedata.first;
-               
-               /* need to restore the original projection settings before packing up */
-               view3d_operator_needs_opengl(C);
-               view_autodist_init(p->scene, p->ar, v3d, (p->gpd->flag & GP_DATA_DEPTH_STROKE) ? 1:0);
-       }
-
        gp_paint_cleanup(p);
        gp_session_cleanup(p);
        
@@ -1548,7 +1598,7 @@ static int gpencil_draw_modal (bContext *C, wmOperator *op, wmEvent *event)
        //printf("\tGP - handle modal event...\n");
        
        /* exit painting mode (and/or end current stroke) */
-       if (ELEM3(event->type, RETKEY, PADENTER, ESCKEY)) {
+       if (ELEM4(event->type, RETKEY, PADENTER, ESCKEY, SPACEKEY)) {
                /* exit() ends the current stroke before cleaning up */
                //printf("\t\tGP - end of paint op + end of stroke\n");
                gpencil_draw_exit(C, op);
@@ -1566,7 +1616,7 @@ static int gpencil_draw_modal (bContext *C, wmOperator *op, wmEvent *event)
                        if (GPENCIL_SKETCH_SESSIONS_ON(p->scene)) {
                                /* end stroke only, and then wait to resume painting soon */
                                //printf("\t\tGP - end stroke only\n");
-                               gp_paint_strokeend(p);
+                               gp_paint_cleanup(p);
                                p->status= GP_STATUS_IDLING;
                                
                                /* we've just entered idling state, so this event was processed (but no others yet) */
@@ -1608,26 +1658,52 @@ static int gpencil_draw_modal (bContext *C, wmOperator *op, wmEvent *event)
                }
        }
        
-       /* handle painting mouse-movements? */
-       if ((p->status == GP_STATUS_PAINTING) && 
-               (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) || (p->flags & GP_PAINTFLAG_FIRSTRUN)) ) 
-       {
-               /* handle drawing event */
-               //printf("\t\tGP - add point\n");
-               gpencil_draw_apply_event(op, event);
-               
-               /* finish painting operation if anything went wrong just now */
-               if (p->status == GP_STATUS_ERROR) {
-                       //printf("\t\t\t\tGP - add error done! \n");
-                       gpencil_draw_exit(C, op);
-                       estate = OPERATOR_CANCELLED;
+       /* handle mode-specific events */
+       if (p->status == GP_STATUS_PAINTING) {
+               /* handle painting mouse-movements? */
+               if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) || (p->flags & GP_PAINTFLAG_FIRSTRUN)) 
+               {
+                       /* handle drawing event */
+                       //printf("\t\tGP - add point\n");
+                       gpencil_draw_apply_event(op, event);
+                       
+                       /* finish painting operation if anything went wrong just now */
+                       if (p->status == GP_STATUS_ERROR) {
+                               //printf("\t\t\t\tGP - add error done! \n");
+                               gpencil_draw_exit(C, op);
+                               estate = OPERATOR_CANCELLED;
+                       }
+                       else {
+                               /* event handled, so just tag as running modal */
+                               //printf("\t\t\t\tGP - add point handled!\n");
+                               estate = OPERATOR_RUNNING_MODAL;
+                       }
                }
+               /* there shouldn't be any other events, but just in case there are, let's swallow them 
+                * (i.e. to prevent problems with with undo)
+                */
                else {
-                       /* event handled, so just tag as running modal */
-                       //printf("\t\t\t\tGP - add point handled!\n");
+                       /* swallow event to save ourselves trouble */
                        estate = OPERATOR_RUNNING_MODAL;
                }
        }
+       else if (p->status == GP_STATUS_IDLING) {
+               /* standard undo/redo shouldn't be allowed to execute or else it causes crashes, so catch it here */
+               // FIXME: this is a hardcoded hotkey that can't be changed
+               // TODO: catch redo as well, but how?
+               if (event->type == ZKEY) {
+                       /* oskey = cmd key on macs as they seem to use cmd-z for undo as well? */
+                       if ((event->ctrl) || (event->oskey)) {
+                               /* just delete last stroke, which will look like undo to the end user */
+                               //printf("caught attempted undo event... deleting last stroke \n");
+                               gpencil_frame_delete_laststroke(p->gpl, p->gpf);
+                               
+                               /* event handled, so force refresh */
+                               ED_region_tag_redraw(p->ar); /* just active area for now, since doing whole screen is too slow */
+                               estate = OPERATOR_RUNNING_MODAL; 
+                       }
+               }
+       }
        
        /* update status indicators - cursor, header, etc. */
        gpencil_draw_status_indicators(p);