Painting / Sculpting: more tweaks to pressure sensitivity

* 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.
This commit is contained in:
Brecht Van Lommel 2013-05-18 11:25:24 +00:00
parent 75e36650e3
commit 423ffa6043
2 changed files with 88 additions and 55 deletions

@ -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;

@ -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;
}
static float paint_space_stroke_spacing(const Scene *scene, PaintStroke *stroke, float size_pressure, float spacing_pressure)
{
/* 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;
/* apply spacing pressure */
if (stroke->brush->flag & BRUSH_SPACING_PRESSURE)
spacing = max_ff(1.0f, spacing * (1.5f - spacing_pressure));
/* 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;
return (size_clamp * spacing / 50.0f);
}
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);
}
}
/* 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 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;
float mouse[2];
float vec[2];
float length, scale;
if (paint_space_stroke_enabled(stroke->brush, mode)) {
float pressure, dpressure;
float mouse[2], dmouse[2];
float length;
copy_v2_v2(mouse, stroke->last_mouse_position);
sub_v2_v2v2(vec, final_mouse, mouse);
sub_v2_v2v2(dmouse, final_mouse, stroke->last_mouse_position);
length = len_v2(vec);
pressure = stroke->last_pressure;
dpressure = final_pressure - stroke->last_pressure;
if (length > FLT_EPSILON) {
const Scene *scene = CTX_data_scene(C);
int steps;
int i;
float size_pressure = 1.0f;
length = normalize_v2(dmouse);
/* 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;
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;
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;
paint_brush_stroke_add_step(C, op, mouse, pressure);
/* stroke system is used for 2d paint too, so we need to account for
* the fact that brush can be scaled there. */
length -= spacing;
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));
spacing *= stroke->zoom_2d;
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);
steps = (int)(1.0f / scale);
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;