Recreating my GSoC branch.
[blender.git] / source / blender / editors / sculpt_paint / paint_stroke.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software  Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2009 by Nicholas Bishop
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  *
23  * Contributor(s): none yet.
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  *
27  */
28
29 #include "MEM_guardedalloc.h"
30
31 #include "DNA_object_types.h"
32 #include "DNA_scene_types.h"
33
34 #include "RNA_access.h"
35
36 #include "BKE_context.h"
37 #include "BKE_paint.h"
38
39 #include "WM_api.h"
40 #include "WM_types.h"
41
42 #include "BLI_math.h"
43
44
45 #include "BIF_gl.h"
46 #include "BIF_glutil.h"
47
48 #include "ED_screen.h"
49 #include "ED_view3d.h"
50
51 #include "paint_intern.h"
52
53 #include <float.h>
54 #include <math.h>
55
56 typedef struct PaintStroke {
57         void *mode_data;
58         void *smooth_stroke_cursor;
59         wmTimer *timer;
60
61         /* Cached values */
62         ViewContext vc;
63         bglMats mats;
64         Brush *brush;
65
66         float last_mouse_position[2];
67
68         /* Set whether any stroke step has yet occured
69            e.g. in sculpt mode, stroke doesn't start until cursor
70            passes over the mesh */
71         int stroke_started;
72
73         StrokeGetLocation get_location;
74         StrokeTestStart test_start;
75         StrokeUpdateStep update_step;
76         StrokeDone done;
77 } PaintStroke;
78
79 /*** Cursor ***/
80 static void paint_draw_smooth_stroke(bContext *C, int x, int y, void *customdata) 
81 {
82         Brush *brush = paint_brush(paint_get_active(CTX_data_scene(C)));
83         PaintStroke *stroke = customdata;
84
85         glColor4ubv(paint_get_active(CTX_data_scene(C))->paint_cursor_col);
86         glEnable(GL_LINE_SMOOTH);
87         glEnable(GL_BLEND);
88
89         if(stroke && brush && (brush->flag & BRUSH_SMOOTH_STROKE)) {
90                 ARegion *ar = CTX_wm_region(C);
91                 sdrawline(x, y, (int)stroke->last_mouse_position[0] - ar->winrct.xmin,
92                           (int)stroke->last_mouse_position[1] - ar->winrct.ymin);
93         }
94
95         glDisable(GL_BLEND);
96         glDisable(GL_LINE_SMOOTH);
97 }
98
99 static void paint_draw_cursor(bContext *C, int x, int y, void *customdata)
100 {
101         Paint *paint = paint_get_active(CTX_data_scene(C));
102         Brush *brush = paint_brush(paint);
103
104         if(!(paint->flags & PAINT_SHOW_BRUSH))
105                 return;
106
107         glColor4ubv(paint_get_active(CTX_data_scene(C))->paint_cursor_col);
108         glEnable(GL_LINE_SMOOTH);
109         glEnable(GL_BLEND);
110
111         glTranslatef((float)x, (float)y, 0.0f);
112         glutil_draw_lined_arc(0.0, M_PI*2.0, brush->size, 40);
113         glTranslatef((float)-x, (float)-y, 0.0f);
114
115         glDisable(GL_BLEND);
116         glDisable(GL_LINE_SMOOTH);
117 }
118
119 /* Put the location of the next stroke dot into the stroke RNA and apply it to the mesh */
120 static void paint_brush_stroke_add_step(bContext *C, wmOperator *op, wmEvent *event, float mouse[2])
121 {
122         PointerRNA itemptr;
123         float pressure = 1;
124         float center[3] = {0, 0, 0};
125         int flip= event->shift?1:0;
126         PaintStroke *stroke = op->customdata;
127
128         /* XXX: can remove the if statement once all modes have this */
129         if(stroke->get_location)
130                 stroke->get_location(C, stroke, center, mouse);
131
132         /* Tablet */
133         if(event->custom == EVT_DATA_TABLET) {
134                 wmTabletData *wmtab= event->customdata;
135                 if(wmtab->Active != EVT_TABLET_NONE)
136                         pressure= wmtab->Pressure;
137                 if(wmtab->Active == EVT_TABLET_ERASER)
138                         flip = 1;
139         }
140                                 
141         /* Add to stroke */
142         RNA_collection_add(op->ptr, "stroke", &itemptr);
143         RNA_float_set_array(&itemptr, "location", center);
144         RNA_float_set_array(&itemptr, "mouse", mouse);
145         RNA_boolean_set(&itemptr, "flip", flip);
146         RNA_float_set(&itemptr, "pressure", pressure);
147
148         stroke->last_mouse_position[0] = mouse[0];
149         stroke->last_mouse_position[1] = mouse[1];
150
151         stroke->update_step(C, stroke, &itemptr);
152 }
153
154 /* Returns zero if no sculpt changes should be made, non-zero otherwise */
155 static int paint_smooth_stroke(PaintStroke *stroke, float output[2], wmEvent *event)
156 {
157         output[0] = event->x;
158         output[1] = event->y;
159
160         if(stroke->brush->flag & BRUSH_SMOOTH_STROKE && stroke->brush->sculpt_tool != SCULPT_TOOL_GRAB) {
161                 float u = stroke->brush->smooth_stroke_factor, v = 1.0 - u;
162                 float dx = stroke->last_mouse_position[0] - event->x, dy = stroke->last_mouse_position[1] - event->y;
163
164                 /* If the mouse is moving within the radius of the last move,
165                    don't update the mouse position. This allows sharp turns. */
166                 if(dx*dx + dy*dy < stroke->brush->smooth_stroke_radius * stroke->brush->smooth_stroke_radius)
167                         return 0;
168
169                 output[0] = event->x * v + stroke->last_mouse_position[0] * u;
170                 output[1] = event->y * v + stroke->last_mouse_position[1] * u;
171         }
172
173         return 1;
174 }
175
176 /* Returns zero if the stroke dots should not be spaced, non-zero otherwise */
177 static int paint_space_stroke_enabled(Brush *br)
178 {
179         return (br->flag & BRUSH_SPACE) && !(br->flag & BRUSH_ANCHORED) && (br->sculpt_tool != SCULPT_TOOL_GRAB);
180 }
181
182 /* For brushes with stroke spacing enabled, moves mouse in steps
183    towards the final mouse location. */
184 static int paint_space_stroke(bContext *C, wmOperator *op, wmEvent *event, const float final_mouse[2])
185 {
186         PaintStroke *stroke = op->customdata;
187         int cnt = 0;
188
189         if(paint_space_stroke_enabled(stroke->brush)) {
190                 float mouse[2] = {stroke->last_mouse_position[0], stroke->last_mouse_position[1]};
191                 float vec[2] = {final_mouse[0] - mouse[0], final_mouse[1] - mouse[1]};
192                 float length, scale;
193                 int steps = 0, i;
194
195                 /* Normalize the vector between the last stroke dot and the goal */
196                 length = sqrt(vec[0]*vec[0] + vec[1]*vec[1]);
197
198                 if(length > FLT_EPSILON) {
199                         scale = stroke->brush->spacing / length;
200                         vec[0] *= scale;
201                         vec[1] *= scale;
202
203                         steps = (int)(length / stroke->brush->spacing);
204                         for(i = 0; i < steps; ++i, ++cnt) {
205                                 mouse[0] += vec[0];
206                                 mouse[1] += vec[1];
207                                 paint_brush_stroke_add_step(C, op, event, mouse);
208                         }
209                 }
210         }
211
212         return cnt;
213 }
214
215 /**** Public API ****/
216
217 PaintStroke *paint_stroke_new(bContext *C,
218                                   StrokeGetLocation get_location,
219                                   StrokeTestStart test_start,
220                                   StrokeUpdateStep update_step,
221                                   StrokeDone done)
222 {
223         PaintStroke *stroke = MEM_callocN(sizeof(PaintStroke), "PaintStroke");
224
225         stroke->brush = paint_brush(paint_get_active(CTX_data_scene(C)));
226         view3d_set_viewcontext(C, &stroke->vc);
227         view3d_get_transformation(stroke->vc.ar, stroke->vc.rv3d, stroke->vc.obact, &stroke->mats);
228
229         stroke->get_location = get_location;
230         stroke->test_start = test_start;
231         stroke->update_step = update_step;
232         stroke->done = done;
233
234         return stroke;
235 }
236
237 void paint_stroke_free(PaintStroke *stroke)
238 {
239         MEM_freeN(stroke);
240 }
241
242 int paint_stroke_modal(bContext *C, wmOperator *op, wmEvent *event)
243 {
244         PaintStroke *stroke = op->customdata;
245         float mouse[2];
246         int first= 0;
247
248         if(!stroke->stroke_started) {
249                 stroke->last_mouse_position[0] = event->x;
250                 stroke->last_mouse_position[1] = event->y;
251                 stroke->stroke_started = stroke->test_start(C, op, event);
252
253                 if(stroke->stroke_started) {
254                         stroke->smooth_stroke_cursor =
255                                 WM_paint_cursor_activate(CTX_wm_manager(C), paint_poll, paint_draw_smooth_stroke, stroke);
256
257                         if(stroke->brush->flag & BRUSH_AIRBRUSH)
258                                 stroke->timer = WM_event_add_timer(CTX_wm_manager(C), CTX_wm_window(C), TIMER, stroke->brush->rate);
259                 }
260
261                 first= 1;
262                 //ED_region_tag_redraw(ar);
263         }
264
265         /* TODO: fix hardcoded events here */
266         if(event->type == LEFTMOUSE && event->val == KM_RELEASE) {
267                 /* exit stroke, free data */
268                 if(stroke->smooth_stroke_cursor)
269                         WM_paint_cursor_end(CTX_wm_manager(C), stroke->smooth_stroke_cursor);
270
271                 if(stroke->timer)
272                         WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), stroke->timer);
273
274                 stroke->done(C, stroke);
275                 MEM_freeN(stroke);
276                 return OPERATOR_FINISHED;
277         }
278         else if(first || event->type == MOUSEMOVE || (event->type == TIMER && (event->customdata == stroke->timer))) {
279                 if(stroke->stroke_started) {
280                         if(paint_smooth_stroke(stroke, mouse, event)) {
281                                 if(paint_space_stroke_enabled(stroke->brush)) {
282                                         if(!paint_space_stroke(C, op, event, mouse)) {
283                                                 //ED_region_tag_redraw(ar);
284                                         }
285                                 }
286                                 else
287                                         paint_brush_stroke_add_step(C, op, event, mouse);
288                         }
289                         else
290                                 ;//ED_region_tag_redraw(ar);
291                 }
292         }
293
294         return OPERATOR_RUNNING_MODAL;
295 }
296
297 int paint_stroke_exec(bContext *C, wmOperator *op)
298 {
299         PaintStroke *stroke = op->customdata;
300
301         RNA_BEGIN(op->ptr, itemptr, "stroke") {
302                 stroke->update_step(C, stroke, &itemptr);
303         }
304         RNA_END;
305
306         MEM_freeN(stroke);
307         op->customdata = NULL;
308
309         return OPERATOR_FINISHED;
310 }
311
312 ViewContext *paint_stroke_view_context(PaintStroke *stroke)
313 {
314         return &stroke->vc;
315 }
316
317 void *paint_stroke_mode_data(struct PaintStroke *stroke)
318 {
319         return stroke->mode_data;
320 }
321
322 void paint_stroke_set_mode_data(PaintStroke *stroke, void *mode_data)
323 {
324         stroke->mode_data = mode_data;
325 }
326
327 int paint_poll(bContext *C)
328 {
329         Paint *p = paint_get_active(CTX_data_scene(C));
330         Object *ob = CTX_data_active_object(C);
331
332         return p && ob && paint_brush(p) &&
333                 CTX_wm_area(C)->spacetype == SPACE_VIEW3D &&
334                 CTX_wm_region(C)->regiontype == RGN_TYPE_WINDOW;
335 }
336
337 void paint_cursor_start(bContext *C, int (*poll)(bContext *C))
338 {
339         Paint *p = paint_get_active(CTX_data_scene(C));
340
341         if(p && !p->paint_cursor)
342                 p->paint_cursor = WM_paint_cursor_activate(CTX_wm_manager(C), poll, paint_draw_cursor, NULL);
343 }
344