Painting / Sculpting: more tweaks to pressure sensitivity
authorBrecht Van Lommel <brechtvanlommel@pandora.be>
Sat, 18 May 2013 11:25:24 +0000 (11:25 +0000)
committerBrecht Van Lommel <brechtvanlommel@pandora.be>
Sat, 18 May 2013 11:25:24 +0000 (11:25 +0000)
* Also do pressure interpolation for brush size and spacing.
* Do smoothing of pressure when smooth stroke and sample average is enabled.
* Revert the OS X specific pressure change to pressure ^ 2.5, for low pressure
  values like 0.05 it makes the pressure 100x lower, which is problematic. If
  we need to adjust the pressure curve it should be done for all platforms.

Still weak:

* Pressure of first touch on tablet is difficult to control, usually it's low
  which makes the stroke start out small or soft, but other times not. Finer
  event capturing at ghost level would help, along with pressure changes without
  mouse movement, but this may also need different paint stroke logic.
* Brush radius is rounded to integers, this gives noticeable stepping.
* Brush falloff is not antialiased, gives noticeable aliasing for small brush
  sizes which was always a problem, but is more common with size pressure control.

intern/ghost/intern/GHOST_SystemCocoa.mm
source/blender/editors/sculpt_paint/paint_stroke.c

index 0be26e594f19145dcf89ad16e455c72ff98b1d59..607b6ae77b2bb6670443b22b60759144ac96bed0 100644 (file)
@@ -1390,8 +1390,8 @@ GHOST_TSuccess GHOST_SystemCocoa::handleTabletEvent(void *eventPtr, short eventT
                        // 2. device is not sending [event pointingDeviceType], due no eraser
                        if (ct.Active == GHOST_kTabletModeNone)
                                ct.Active = GHOST_kTabletModeStylus;
-                               
-                       ct.Pressure = sqrtf(powf([event pressure], 5 )); // experimental: change sensivity curve
+
+                       ct.Pressure = [event pressure];
                        ct.Xtilt = [event tilt].x;
                        ct.Ytilt = [event tilt].y;
                        break;
index 0ff09ddbb8f25590f4c6f1afd63529509cef3a03..ea5175df39ab0be6fdedad884144e95f1e3b945a 100644 (file)
@@ -63,8 +63,7 @@
 
 typedef struct PaintSample {
        float mouse[2];
-
-       /* TODO: other input properties, e.g. tablet pressure */
+       float pressure;
 } PaintSample;
 
 typedef struct PaintStroke {
@@ -290,6 +289,7 @@ static void paint_brush_stroke_add_step(bContext *C, wmOperator *op, const float
        /* copy last position -before- jittering, or space fill code
         * will create too many dabs */
        copy_v2_v2(stroke->last_mouse_position, mouse_in);
+       stroke->last_pressure = pressure;
 
        paint_brush_update(C, brush, mode, stroke, mouse_in, pressure);
 
@@ -338,11 +338,12 @@ static void paint_brush_stroke_add_step(bContext *C, wmOperator *op, const float
 }
 
 /* Returns zero if no sculpt changes should be made, non-zero otherwise */
-static int paint_smooth_stroke(PaintStroke *stroke, float output[2],
+static int paint_smooth_stroke(PaintStroke *stroke, float output[2], float *outpressure,
                                const PaintSample *sample, PaintMode mode)
 {
        output[0] = sample->mouse[0];
        output[1] = sample->mouse[1];
+       *outpressure = sample->pressure;
 
        if (paint_supports_smooth_stroke(stroke->brush, mode)) {
                float radius = stroke->brush->smooth_stroke_radius * stroke->zoom_2d;
@@ -357,71 +358,98 @@ static int paint_smooth_stroke(PaintStroke *stroke, float output[2],
 
                output[0] = sample->mouse[0] * v + stroke->last_mouse_position[0] * u;
                output[1] = sample->mouse[1] * v + stroke->last_mouse_position[1] * u;
+               *outpressure = sample->pressure * v + stroke->last_pressure * u;
        }
 
        return 1;
 }
 
-/* For brushes with stroke spacing enabled, moves mouse in steps
- * towards the final mouse location. */
-static int paint_space_stroke(bContext *C, wmOperator *op, const float final_mouse[2], float pressure)
+static float paint_space_stroke_spacing(const Scene *scene, PaintStroke *stroke, float size_pressure, float spacing_pressure)
 {
-       PaintStroke *stroke = op->customdata;
+       /* brushes can have a minimum size of 1.0 but with pressure it can be smaller then a pixel
+        * causing very high step sizes, hanging blender [#32381] */
+       const float size_clamp = max_ff(1.0f, BKE_brush_size_get(scene, stroke->brush) * size_pressure);
+       float spacing = stroke->brush->spacing;
 
-       int cnt = 0;
+       /* apply spacing pressure */
+       if (stroke->brush->flag & BRUSH_SPACING_PRESSURE)
+               spacing = max_ff(1.0f, spacing * (1.5f - spacing_pressure));
 
-       float mouse[2];
-       float vec[2];
-       float length, scale;
+       /* stroke system is used for 2d paint too, so we need to account for
+        * the fact that brush can be scaled there. */
+       spacing *= stroke->zoom_2d;
 
-       copy_v2_v2(mouse, stroke->last_mouse_position);
-       sub_v2_v2v2(vec, final_mouse, mouse);
+       return (size_clamp * spacing / 50.0f);
+}
 
-       length = len_v2(vec);
+static float paint_space_stroke_spacing_variable(const Scene *scene, PaintStroke *stroke, float pressure, float dpressure, float length)
+{
+       if (BKE_brush_use_size_pressure(scene, stroke->brush)) {
+               /* use pressure to modify size. set spacing so that at 100%, the circles
+                * are aligned nicely with no overlap. for this the spacing needs to be
+                * the average of the previous and next size. */
+               float s = paint_space_stroke_spacing(scene, stroke, 1.0f, pressure);
+               float q = s*dpressure/(2.0f*length);
+               float pressure_fac = (1.0f + q)/(1.0f - q);
+
+               float last_size_pressure = stroke->last_pressure;
+               float new_size_pressure = stroke->last_pressure*pressure_fac;
+
+               /* average spacing */
+               float last_spacing = paint_space_stroke_spacing(scene, stroke, last_size_pressure, pressure);
+               float new_spacing = paint_space_stroke_spacing(scene, stroke, new_size_pressure, pressure);
+
+               return 0.5f*(last_spacing + new_spacing);
+       }
+       else {
+               /* no size pressure */
+               return paint_space_stroke_spacing(scene, stroke, 1.0f, pressure);
+       }
+}
 
-       if (length > FLT_EPSILON) {
-               const Scene *scene = CTX_data_scene(C);
-               int steps;
-               int i;
-               float size_pressure = 1.0f;
+/* For brushes with stroke spacing enabled, moves mouse in steps
+ * towards the final mouse location. */
+static int paint_space_stroke(bContext *C, wmOperator *op, const float final_mouse[2], float final_pressure)
+{
+       const Scene *scene = CTX_data_scene(C);
+       PaintStroke *stroke = op->customdata;
+       PaintMode mode = BKE_paintmode_get_active_from_context(C);
+       int cnt = 0;
 
-               /* XXX mysterious :) what has 'use size' do with this here... if you don't check for it, pressure fails */
-               if (BKE_brush_use_size_pressure(scene, stroke->brush))
-                       size_pressure = pressure;
+       if (paint_space_stroke_enabled(stroke->brush, mode)) {
+               float pressure, dpressure;
+               float mouse[2], dmouse[2];
+               float length;
 
-               if (size_pressure > FLT_EPSILON) {
-                       /* brushes can have a minimum size of 1.0 but with pressure it can be smaller then a pixel
-                        * causing very high step sizes, hanging blender [#32381] */
-                       const float size_clamp = max_ff(1.0f, BKE_brush_size_get(scene, stroke->brush) * size_pressure);
-                       float spacing = stroke->brush->spacing;
+               sub_v2_v2v2(dmouse, final_mouse, stroke->last_mouse_position);
 
-                       /* stroke system is used for 2d paint too, so we need to account for
-                                * the fact that brush can be scaled there. */
+               pressure = stroke->last_pressure;
+               dpressure = final_pressure - stroke->last_pressure;
 
-                       if (stroke->brush->flag & BRUSH_SPACING_PRESSURE)
-                               spacing = max_ff(1.0f, spacing * (1.5f - pressure));
+               length = normalize_v2(dmouse);
 
-                       spacing *= stroke->zoom_2d;
+               while (length > 0.0f) {
+                       float spacing = paint_space_stroke_spacing_variable(scene, stroke, pressure, dpressure, length);
+                       
+                       if (length >= spacing) {
+                               mouse[0] = stroke->last_mouse_position[0] + dmouse[0]*spacing;
+                               mouse[1] = stroke->last_mouse_position[1] + dmouse[1]*spacing;
+                               pressure = stroke->last_pressure + (spacing/length)*dpressure;
 
-                       scale = (size_clamp * spacing / 50.0f) / length;
-                       if (scale > FLT_EPSILON) {
-                               float pressure_diff = (pressure - stroke->last_pressure)*scale;
-                               float final_pressure = stroke->last_pressure;
-                               mul_v2_fl(vec, scale);
+                               paint_brush_stroke_add_step(C, op, mouse, pressure);
 
-                               steps = (int)(1.0f / scale);
+                               length -= spacing;
+                               pressure = stroke->last_pressure;
+                               dpressure = final_pressure - stroke->last_pressure;
 
-                               for (i = 0; i < steps; ++i, ++cnt) {
-                                       final_pressure += pressure_diff;
-                                       add_v2_v2(mouse, vec);
-                                       paint_brush_stroke_add_step(C, op, mouse, final_pressure);
-                               }
+                               cnt++;
+                       }
+                       else {
+                               break;
                        }
                }
        }
 
-       stroke->last_pressure = pressure;
-
        return cnt;
 }
 
@@ -582,7 +610,7 @@ struct wmKeyMap *paint_stroke_modal_keymap(struct wmKeyConfig *keyconf)
 
 static void paint_stroke_add_sample(const Paint *paint,
                                     PaintStroke *stroke,
-                                    float x, float y)
+                                    float x, float y, float pressure)
 {
        PaintSample *sample = &stroke->samples[stroke->cur_sample];
        int max_samples = MIN2(PAINT_MAX_INPUT_SAMPLES,
@@ -590,6 +618,7 @@ static void paint_stroke_add_sample(const Paint *paint,
 
        sample->mouse[0] = x;
        sample->mouse[1] = y;
+       sample->pressure = pressure;
 
        stroke->cur_sample++;
        if (stroke->cur_sample >= max_samples)
@@ -607,10 +636,13 @@ static void paint_stroke_sample_average(const PaintStroke *stroke,
 
        BLI_assert(stroke->num_samples > 0);
        
-       for (i = 0; i < stroke->num_samples; i++)
+       for (i = 0; i < stroke->num_samples; i++) {
                add_v2_v2(average->mouse, stroke->samples[i].mouse);
+               average->pressure += stroke->samples[i].pressure;
+       }
 
        mul_v2_fl(average->mouse, 1.0f / stroke->num_samples);
+       average->pressure /= stroke->num_samples;
 
        /*printf("avg=(%f, %f), num=%d\n", average->mouse[0], average->mouse[1], stroke->num_samples);*/
 }
@@ -627,15 +659,15 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event)
        bool redraw = false;
        float pressure;
 
-       paint_stroke_add_sample(p, stroke, event->mval[0], event->mval[1]);
+       /* see if tablet affects event */
+       pressure = event_tablet_data(event, &stroke->pen_flip);
+
+       paint_stroke_add_sample(p, stroke, event->mval[0], event->mval[1], pressure);
        paint_stroke_sample_average(stroke, &sample_average);
 
        get_imapaint_zoom(C, &zoomx, &zoomy);
        stroke->zoom_2d = max_ff(zoomx, zoomy);
 
-       /* see if tablet affects event */
-       pressure = event_tablet_data(event, &stroke->pen_flip);
-
        /* let NDOF motion pass through to the 3D view so we can paint and rotate simultaneously!
         * this isn't perfect... even when an extra MOUSEMOVE is spoofed, the stroke discards it
         * since the 2D deltas are zero -- code in this file needs to be updated to use the
@@ -645,6 +677,7 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event)
 
        if (!stroke->stroke_started) {
                copy_v2_v2(stroke->last_mouse_position, sample_average.mouse);
+               stroke->last_pressure = sample_average.pressure;
                stroke->stroke_started = stroke->test_start(C, op, sample_average.mouse);
                BLI_assert((stroke->stroke_started & ~1) == 0);  /* 0/1 */
                stroke->last_pressure = pressure;
@@ -678,7 +711,7 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event)
                 (event->type == TIMER && (event->customdata == stroke->timer)) )
        {
                if (stroke->stroke_started) {
-                       if (paint_smooth_stroke(stroke, mouse, &sample_average, mode)) {
+                       if (paint_smooth_stroke(stroke, mouse, &pressure, &sample_average, mode)) {
                                if (paint_space_stroke_enabled(stroke->brush, mode)) {
                                        if (paint_space_stroke(C, op, mouse, pressure))
                                                redraw = true;