Blender V5.0
paint_stroke.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2009 by Nicholas Bishop. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include <algorithm>
10#include <cfloat>
11#include <cmath>
12
13#include "fmt/format.h"
14
15#include "MEM_guardedalloc.h"
16
17#include "BLI_math_matrix.h"
18#include "BLI_math_matrix.hh"
19#include "BLI_math_vector.h"
20#include "BLI_rand.hh"
21#include "BLI_utildefines.h"
22
23#include "DNA_brush_types.h"
24#include "DNA_curve_types.h"
25#include "DNA_object_types.h"
26#include "DNA_scene_types.h"
27
28#include "RNA_access.hh"
29
30#include "BKE_brush.hh"
31#include "BKE_colortools.hh"
32#include "BKE_context.hh"
33#include "BKE_curve.hh"
34#include "BKE_global.hh"
35#include "BKE_image.hh"
36#include "BKE_paint.hh"
37#include "BKE_paint_types.hh"
38
39#include "WM_api.hh"
40#include "WM_types.hh"
41
42#include "GPU_immediate.hh"
43#include "GPU_state.hh"
44
45#include "ED_screen.hh"
46#include "ED_view3d.hh"
47
48#include "IMB_imbuf_types.hh"
49
50#include "paint_intern.hh"
51#include "sculpt_cloth.hh"
52#include "sculpt_intern.hh"
53
54// #define DEBUG_TIME
55
56#ifdef DEBUG_TIME
57# include "BLI_time_utildefines.h"
58#endif
59
61
64 float pressure;
65};
66
76 std::unique_ptr<PaintModeData> mode_data;
79 std::optional<RandomNumberGenerator> rng;
80
81 /* Cached values */
86
87 /* Paint stroke can use up to PAINT_MAX_INPUT_SAMPLES prior inputs
88 * to smooth the stroke */
93
97
99 /* space distance covered so far */
101
102 /* Set whether any stroke step has yet occurred
103 * e.g. in sculpt mode, stroke doesn't start until cursor
104 * passes over the mesh */
106 /* Set when enough motion was found for rake rotation */
108 /* event that started stroke, for modal() return */
110 /* check if stroke variables have been initialized */
112 /* check if input variables have been initialized (e.g. cursor position & pressure)*/
116 /* last pressure will store last pressure value for use in interpolation for space strokes */
119
121
122 float zoom_2d;
124
125 /* Tilt, as read from the event. */
127
128 /* line constraint */
131
137
138 bool original; /* Ray-cast original mesh at start of stroke. */
139};
140
141/*** Cursors ***/
143 const blender::int2 &xy,
144 const blender::float2 & /*tilt*/,
145 void *customdata)
146{
148 const Brush *brush = BKE_paint_brush_for_read(paint);
149 PaintStroke *stroke = static_cast<PaintStroke *>(customdata);
151
152 if ((mode == PaintMode::GPencil) && (paint->flags & PAINT_SHOW_BRUSH) == 0) {
153 return;
154 }
155
156 if (stroke && brush) {
157 GPU_line_smooth(true);
159
160 const ARegion *region = stroke->vc.region;
161
163 immVertexFormat(), "pos", blender::gpu::VertAttrType::SFLOAT_32_32);
165 const uchar4 color = uchar4(255, 100, 100, 128);
167
171 stroke->last_mouse_position[0] + region->winrct.xmin,
172 stroke->last_mouse_position[1] + region->winrct.ymin);
173
174 immEnd();
175
177
179 GPU_line_smooth(false);
180 }
181}
182
184 const blender::int2 &xy,
185 const blender::float2 & /*tilt*/,
186 void *customdata)
187{
188 PaintStroke *stroke = static_cast<PaintStroke *>(customdata);
189
190 GPU_line_smooth(true);
191
192 const uint shdr_pos = GPU_vertformat_attr_add(
193 immVertexFormat(), "pos", blender::gpu::VertAttrType::SFLOAT_32_32);
194
196
197 float4 viewport_size;
198 GPU_viewport_size_get_f(viewport_size);
199 immUniform2f("viewport_size", viewport_size[2], viewport_size[3]);
200
201 immUniform1i("colors_len", 2); /* "advanced" mode */
202 immUniform4f("color", 0.0f, 0.0f, 0.0f, 0.5);
203 immUniform4f("color2", 1.0f, 1.0f, 1.0f, 0.5);
204 immUniform1f("dash_width", 6.0f);
205 immUniform1f("udash_factor", 0.5f);
206
208
209 const ARegion *region = stroke->vc.region;
210
211 if (stroke->constrain_line) {
212 immVertex2f(shdr_pos,
213 stroke->last_mouse_position[0] + region->winrct.xmin,
214 stroke->last_mouse_position[1] + region->winrct.ymin);
215
216 immVertex2f(shdr_pos,
217 stroke->constrained_pos[0] + region->winrct.xmin,
218 stroke->constrained_pos[1] + region->winrct.ymin);
219 }
220 else {
221 immVertex2f(shdr_pos,
222 stroke->last_mouse_position[0] + region->winrct.xmin,
223 stroke->last_mouse_position[1] + region->winrct.ymin);
224
225 immVertex2fv(shdr_pos, float2(xy));
226 }
227
228 immEnd();
229
231
232 GPU_line_smooth(false);
233}
234
235static bool image_paint_brush_type_require_location(const Brush &brush, const PaintMode mode)
236{
237 switch (mode) {
239 if (ELEM(brush.sculpt_brush_type,
247 {
248 return false;
249 }
250 else if (cloth::is_cloth_deform_brush(brush)) {
251 return false;
252 }
253 else {
254 return true;
255 }
256 default:
257 break;
258 }
259
260 return true;
261}
262
263static bool paint_stroke_use_scene_spacing(const Brush &brush, const PaintMode mode)
264{
265 switch (mode) {
267 return brush.flag & BRUSH_SCENE_SPACING;
268 default:
269 break;
270 }
271 return false;
272}
273
274static bool image_paint_brush_type_raycast_original(const Brush &brush, PaintMode /*mode*/)
275{
276 return brush.flag & (BRUSH_ANCHORED | BRUSH_DRAG_DOT);
277}
278
280 const PaintMode mode)
281{
282 if (brush.flag & BRUSH_ANCHORED) {
283 return false;
284 }
285
286 switch (mode) {
288 if (ELEM(brush.sculpt_brush_type,
297 {
298 return false;
299 }
300 else {
301 return true;
302 }
303 default:
304 break;
305 }
306
307 return true;
308}
309
311 const Brush &brush,
312 const PaintMode mode,
313 PaintStroke *stroke,
314 const float mouse_init[2],
315 float mouse[2],
316 const float pressure,
317 float r_location[3],
318 bool *r_location_is_set)
319{
320 Scene *scene = CTX_data_scene(C);
322 bke::PaintRuntime &paint_runtime = *paint->runtime;
323 bool location_sampled = false;
324 bool location_success = false;
325 /* Use to perform all operations except applying the stroke,
326 * needed for operations that require cursor motion (rake). */
327 bool is_dry_run = false;
328 bool do_random = false;
329 bool do_random_mask = false;
330 *r_location_is_set = false;
331 /* XXX: Use pressure value from first brush step for brushes which don't
332 * support strokes (grab, thumb). They depends on initial state and
333 * brush coord/pressure/etc.
334 * It's more an events design issue, which doesn't split coordinate/pressure/angle
335 * changing events. We should avoid this after events system re-design */
336 if (!stroke->input_init) {
337 copy_v2_v2(stroke->initial_mouse, mouse);
338 copy_v2_v2(paint_runtime.last_rake, mouse);
339 copy_v2_v2(paint_runtime.tex_mouse, mouse);
340 copy_v2_v2(paint_runtime.mask_tex_mouse, mouse);
341 stroke->cached_size_pressure = pressure;
342
343 stroke->input_init = true;
344 }
345
346 if (paint_supports_dynamic_size(brush, mode)) {
347 copy_v2_v2(paint_runtime.tex_mouse, mouse);
348 copy_v2_v2(paint_runtime.mask_tex_mouse, mouse);
349 }
350
351 /* Truly temporary data that isn't stored in properties */
352
353 paint_runtime.stroke_active = true;
354 const float pressure_to_evaluate = paint_supports_dynamic_size(brush, mode) ?
355 pressure :
356 stroke->cached_size_pressure;
357 paint_runtime.size_pressure_value = BKE_brush_use_size_pressure(&brush) ?
359 brush.curve_size, 0, pressure_to_evaluate) :
360 1.0f;
361
362 paint_runtime.pixel_radius = BKE_brush_radius_get(paint, &brush) *
363 paint_runtime.size_pressure_value;
364 paint_runtime.initial_pixel_radius = BKE_brush_radius_get(paint, &brush);
365
366 if (paint_supports_dynamic_tex_coords(brush, mode)) {
367
368 if (ELEM(brush.mtex.brush_map_mode,
372 {
373 do_random = true;
374 }
375
378 }
379 else {
380 copy_v2_v2(paint_runtime.tex_mouse, mouse);
381 }
382
383 /* take care of mask texture, if any */
384 if (brush.mask_mtex.tex) {
385
386 if (ELEM(brush.mask_mtex.brush_map_mode,
390 {
391 do_random_mask = true;
392 }
393
396 }
397 else {
398 copy_v2_v2(paint_runtime.mask_tex_mouse, mouse);
399 }
400 }
401 }
402
403 if (brush.flag & BRUSH_ANCHORED) {
404 bool hit = false;
405 float2 halfway;
406
407 const float dx = mouse[0] - stroke->initial_mouse[0];
408 const float dy = mouse[1] - stroke->initial_mouse[1];
409
410 paint_runtime.anchored_size = paint_runtime.pixel_radius = sqrtf(dx * dx + dy * dy);
411
412 paint_runtime.brush_rotation = paint_runtime.brush_rotation_sec = atan2f(dy, dx) +
413 float(0.5f * M_PI);
414
415 if (brush.flag & BRUSH_EDGE_TO_EDGE) {
416 halfway[0] = dx * 0.5f + stroke->initial_mouse[0];
417 halfway[1] = dy * 0.5f + stroke->initial_mouse[1];
418
419 if (stroke->get_location) {
420 if (stroke->get_location(C, r_location, halfway, stroke->original)) {
421 hit = true;
422 location_sampled = true;
423 location_success = true;
424 *r_location_is_set = true;
425 }
426 else if (!image_paint_brush_type_require_location(brush, mode)) {
427 hit = true;
428 }
429 }
430 else {
431 hit = true;
432 }
433 }
434 if (hit) {
435 copy_v2_v2(paint_runtime.anchored_initial_mouse, halfway);
436 copy_v2_v2(paint_runtime.tex_mouse, halfway);
437 copy_v2_v2(paint_runtime.mask_tex_mouse, halfway);
438 copy_v2_v2(mouse, halfway);
439 paint_runtime.anchored_size /= 2.0f;
440 paint_runtime.pixel_radius /= 2.0f;
441 stroke->stroke_distance = paint_runtime.pixel_radius;
442 }
443 else {
444 copy_v2_v2(paint_runtime.anchored_initial_mouse, stroke->initial_mouse);
445 copy_v2_v2(mouse, stroke->initial_mouse);
446 stroke->stroke_distance = paint_runtime.pixel_radius;
447 }
448 paint_runtime.pixel_radius /= stroke->zoom_2d;
449 paint_runtime.draw_anchored = true;
450 }
451 else {
452 /* curve strokes do their own rake calculation */
453 if (!(brush.flag & BRUSH_CURVE)) {
454 if (!paint_calculate_rake_rotation(*paint, brush, mouse_init, mode, stroke->rake_started)) {
455 /* Not enough motion to define an angle. */
456 if (!stroke->rake_started) {
457 is_dry_run = true;
458 }
459 }
460 else {
461 stroke->rake_started = true;
462 }
463 }
464 }
465
466 if ((do_random || do_random_mask) && !stroke->rng) {
467 /* Lazy initialization. */
469 }
470
471 if (do_random) {
473 paint_runtime.brush_rotation += -brush.mtex.random_angle / 2.0f +
474 brush.mtex.random_angle * stroke->rng->get_float();
475 }
476 }
477
478 if (do_random_mask) {
480 paint_runtime.brush_rotation_sec += -brush.mask_mtex.random_angle / 2.0f +
481 brush.mask_mtex.random_angle * stroke->rng->get_float();
482 }
483 }
484
485 if (!location_sampled) {
486 if (stroke->get_location) {
487 if (stroke->get_location(C, r_location, mouse, stroke->original)) {
488 location_success = true;
489 *r_location_is_set = true;
490 }
491 else if (!image_paint_brush_type_require_location(brush, mode)) {
492 location_success = true;
493 }
494 }
495 else {
496 zero_v3(r_location);
497 location_success = true;
498 /* don't set 'r_location_is_set', since we don't want to use the value. */
499 }
500 }
501
502 return location_success && !is_dry_run;
503}
504
505static bool paint_stroke_use_dash(const Brush &brush)
506{
507 /* Only these stroke modes support dash lines */
508 return brush.flag & BRUSH_SPACE || brush.flag & BRUSH_LINE || brush.flag & BRUSH_CURVE;
509}
510
511static bool paint_stroke_use_jitter(const PaintMode mode, const Brush &brush, const bool invert)
512{
513 bool use_jitter = brush.flag & BRUSH_ABSOLUTE_JITTER ? brush.jitter_absolute != 0 :
514 brush.jitter != 0;
515
516 /* jitter-ed brush gives weird and unpredictable result for this
517 * kinds of stroke, so manually disable jitter usage (sergey) */
518 use_jitter &= (brush.flag & (BRUSH_DRAG_DOT | BRUSH_ANCHORED)) == 0;
519 use_jitter &= !ELEM(mode, PaintMode::Texture2D, PaintMode::Texture3D) ||
521
522 return use_jitter;
523}
524
526 const PaintMode mode,
527 const Brush &brush,
528 const float pressure,
529 const float mval[2],
530 float r_mouse_out[2])
531{
532 if (paint_stroke_use_jitter(mode, brush, stroke.stroke_mode == BRUSH_STROKE_INVERT)) {
533 float factor = stroke.zoom_2d;
534
535 if (brush.flag & BRUSH_JITTER_PRESSURE) {
536 factor *= BKE_curvemapping_evaluateF(brush.curve_jitter, 0, pressure);
537 }
538
539 BKE_brush_jitter_pos(*stroke.paint, brush, mval, r_mouse_out);
540
541 /* XXX: meh, this is round about because
542 * BKE_brush_jitter_pos isn't written in the best way to
543 * be reused here */
544 if (factor != 1.0f) {
545 float2 delta;
546 sub_v2_v2v2(delta, r_mouse_out, mval);
547 mul_v2_fl(delta, factor);
548 add_v2_v2v2(r_mouse_out, mval, delta);
549 }
550 }
551 else {
552 copy_v2_v2(r_mouse_out, mval);
553 }
554}
555
556/* Put the location of the next stroke dot into the stroke RNA and apply it to the mesh */
558 bContext *C, wmOperator *op, PaintStroke *stroke, const float2 mval, float pressure)
559{
562 const Brush &brush = *BKE_paint_brush_for_read(&paint);
563 bke::PaintRuntime *paint_runtime = stroke->paint->runtime;
564
565/* the following code is adapted from texture paint. It may not be needed but leaving here
566 * just in case for reference (code in texpaint removed as part of refactoring).
567 * It's strange that only texpaint had these guards. */
568#if 0
569 /* special exception here for too high pressure values on first touch in
570 * windows for some tablets, then we just skip first touch. */
571 if (tablet && (pressure >= 0.99f) &&
572 ((pop->s.brush.flag & BRUSH_SPACING_PRESSURE) ||
573 BKE_brush_use_alpha_pressure(pop->s.brush) || BKE_brush_use_size_pressure(pop->s.brush)))
574 {
575 return;
576 }
577
578 /* This can be removed once fixed properly in
579 * BKE_brush_painter_paint(
580 * BrushPainter *painter, BrushFunc func,
581 * float *pos, double time, float pressure, void *user);
582 * at zero pressure we should do nothing 1/2^12 is 0.0002
583 * which is the sensitivity of the most sensitive pen tablet available */
584 if (tablet && (pressure < 0.0002f) &&
585 ((pop->s.brush.flag & BRUSH_SPACING_PRESSURE) ||
586 BKE_brush_use_alpha_pressure(pop->s.brush) || BKE_brush_use_size_pressure(pop->s.brush)))
587 {
588 return;
589 }
590#endif
591
592 /* copy last position -before- jittering, or space fill code
593 * will create too many dabs */
594 stroke->last_mouse_position = mval;
595 stroke->last_pressure = pressure;
596
597 if (paint_stroke_use_scene_spacing(brush, mode)) {
598 float3 world_space_position;
599
601 C, world_space_position, stroke->last_mouse_position, stroke->original))
602 {
604 stroke->vc.obact->object_to_world(), world_space_position);
605 }
606 else {
608 }
609 }
610
611 float2 mouse_out;
612 /* Get jitter position (same as mval if no jitter is used). */
613 paint_stroke_jitter_pos(*stroke, mode, brush, pressure, mval, mouse_out);
614
615 float3 location;
616 bool is_location_is_set;
617 paint_runtime->last_hit = paint_brush_update(
618 C, brush, mode, stroke, mval, mouse_out, pressure, location, &is_location_is_set);
619 if (is_location_is_set) {
620 copy_v3_v3(paint_runtime->last_location, location);
621 }
622 if (!paint_runtime->last_hit) {
623 return;
624 }
625
626 /* Dash */
627 bool add_step = true;
628 if (paint_stroke_use_dash(brush)) {
629 const int dash_samples = stroke->tot_samples % brush.dash_samples;
630 const float dash = float(dash_samples) / float(brush.dash_samples);
631 if (dash > brush.dash_ratio) {
632 add_step = false;
633 }
634 }
635
636 /* Add to stroke */
637 if (add_step) {
638 PointerRNA itemptr;
639 RNA_collection_add(op->ptr, "stroke", &itemptr);
640 RNA_float_set(&itemptr, "size", paint_runtime->pixel_radius);
641 RNA_float_set_array(&itemptr, "location", location);
642 /* Mouse coordinates modified by the stroke type options. */
643 RNA_float_set_array(&itemptr, "mouse", mouse_out);
644 /* Original mouse coordinates. */
645 RNA_float_set_array(&itemptr, "mouse_event", mval);
646 RNA_float_set(&itemptr, "pressure", pressure);
647 RNA_float_set(&itemptr, "x_tilt", stroke->tilt.x);
648 RNA_float_set(&itemptr, "y_tilt", stroke->tilt.y);
649
650 stroke->update_step(C, op, stroke, &itemptr);
651
652 /* don't record this for now, it takes up a lot of memory when doing long
653 * strokes with small brush size, and operators have register disabled */
654 RNA_collection_clear(op->ptr, "stroke");
655 }
656
657 stroke->tot_samples++;
658}
659
660/* Returns zero if no sculpt changes should be made, non-zero otherwise */
662 const PaintSample *sample,
663 const PaintMode mode,
664 float2 &r_mouse,
665 float &r_pressure)
666{
667 if (paint_supports_smooth_stroke(stroke, *stroke->brush, mode)) {
668 const float radius = stroke->brush->smooth_stroke_radius * stroke->zoom_2d;
669 const float u = stroke->brush->smooth_stroke_factor;
670
671 /* If the mouse is moving within the radius of the last move,
672 * don't update the mouse position. This allows sharp turns. */
673 if (math::distance_squared(stroke->last_mouse_position, sample->mouse) < square_f(radius)) {
674 return false;
675 }
676
677 r_mouse = math::interpolate(sample->mouse, stroke->last_mouse_position, u);
678 r_pressure = math::interpolate(sample->pressure, stroke->last_pressure, u);
679 }
680 else {
681 r_mouse = sample->mouse;
682 r_pressure = sample->pressure;
683 }
684
685 return true;
686}
687
689 PaintStroke *stroke,
690 const float size_factor,
691 const float pressure)
692{
695 const Brush &brush = *BKE_paint_brush_for_read(paint);
696
697 float size_clamp = 0.0f;
698 if (paint_stroke_use_scene_spacing(brush, mode)) {
699 const float3 last_object_space_position = math::transform_point(
700 stroke->vc.obact->world_to_object(), stroke->last_world_space_position);
701 size_clamp = object_space_radius_get(
702 stroke->vc, *paint, brush, last_object_space_position, size_factor);
703 }
704 else {
705 /* brushes can have a minimum size of 1.0 but with pressure it can be smaller than a pixel
706 * causing very high step sizes, hanging blender #32381. */
707 size_clamp = max_ff(1.0f, BKE_brush_radius_get(stroke->paint, stroke->brush) * size_factor);
708 }
709
710 float spacing = stroke->brush->spacing;
711
712 /* apply spacing pressure */
713 if (stroke->brush->flag & BRUSH_SPACE && stroke->brush->flag & BRUSH_SPACING_PRESSURE) {
714 spacing = spacing * (1.5f - pressure);
715 }
716
717 if (cloth::is_cloth_deform_brush(brush)) {
718 /* The spacing in tools that use the cloth solver should not be affected by the brush radius to
719 * avoid affecting the simulation update rate when changing the radius of the brush.
720 * With a value of 100 and the brush default of 10 for spacing, a simulation step runs every 2
721 * pixels movement of the cursor. */
722 size_clamp = 100.0f;
723 }
724
725 /* stroke system is used for 2d paint too, so we need to account for
726 * the fact that brush can be scaled there. */
727 spacing *= stroke->zoom_2d;
728
729 if (paint_stroke_use_scene_spacing(brush, mode)) {
730 /* Low pressure on size (with tablets) can cause infinite recursion in paint_space_stroke(),
731 * see #129853. */
732 return max_ff(FLT_EPSILON, size_clamp * spacing / 50.0f);
733 }
734 return max_ff(stroke->zoom_2d, size_clamp * spacing / 50.0f);
735}
736
738{
739 /* Unlike many paint pressure curves, spacing assumes that a stroke without pressure (e.g. with
740 * the mouse, or with the setting turned off) represents an input of 0.5, not 1.0. */
741 return paint_space_stroke_spacing(C, stroke, 1.0f, 0.5f);
742}
743
744static float paint_stroke_overlapped_curve(const Brush &br, const float x, const float spacing)
745{
746 /* Avoid division by small numbers, can happen
747 * on some pen setups. See #105341.
748 */
749
750 const float clamped_spacing = max_ff(spacing, 0.1f);
751
752 const int n = 100 / clamped_spacing;
753 const float h = clamped_spacing / 50.0f;
754 const float x0 = x - 1;
755
756 float sum = 0;
757 for (int i = 0; i < n; i++) {
758 float xx = fabsf(x0 + i * h);
759
760 if (xx < 1.0f) {
761 sum += BKE_brush_curve_strength(&br, xx, 1);
762 }
763 }
764
765 return sum;
766}
767
768static float paint_stroke_integrate_overlap(const Brush &br, const float factor)
769{
770 const float spacing = br.spacing * factor;
771
772 if (!(br.flag & BRUSH_SPACE_ATTEN && (br.spacing < 100))) {
773 return 1.0;
774 }
775
776 constexpr int m = 10;
777 float g = 1.0f / m;
778 float max = 0;
779 for (int i = 0; i < m; i++) {
780 const float overlap = fabs(paint_stroke_overlapped_curve(br, i * g, spacing));
781
782 max = std::max(overlap, max);
783 }
784
785 if (max == 0.0f) {
786 return 1.0f;
787 }
788 return 1.0f / max;
789}
790
792 PaintStroke *stroke,
793 const float pressure,
794 const float pressure_delta,
795 const float length)
796{
797 if (BKE_brush_use_size_pressure(stroke->brush)) {
798 const float max_size_factor = BKE_curvemapping_evaluateF(stroke->brush->curve_size, 0, 1.0f);
799 /* use pressure to modify size. set spacing so that at 100%, the circles
800 * are aligned nicely with no overlap. for this the spacing needs to be
801 * the average of the previous and next size. */
802 const float s = paint_space_stroke_spacing(C, stroke, max_size_factor, pressure);
803 const float q = s * pressure_delta / (2.0f * length);
804 const float pressure_fac = (1.0f + q) / (1.0f - q);
805
806 const float last_size_factor = BKE_curvemapping_evaluateF(
807 stroke->brush->curve_size, 0, stroke->last_pressure);
808 const float new_size_factor = BKE_curvemapping_evaluateF(
809 stroke->brush->curve_size, 0, stroke->last_pressure * pressure_fac);
810
811 /* average spacing */
812 const float last_spacing = paint_space_stroke_spacing(C, stroke, last_size_factor, pressure);
813 const float new_spacing = paint_space_stroke_spacing(C, stroke, new_size_factor, pressure);
814
815 return 0.5f * (last_spacing + new_spacing);
816 }
817
818 /* no size pressure */
819 return paint_space_stroke_spacing(C, stroke, 1.0f, pressure);
820}
821
822/* For brushes with stroke spacing enabled, moves mouse in steps
823 * towards the final mouse location. */
825 wmOperator *op,
826 PaintStroke *stroke,
827 const float2 final_mouse,
828 const float final_pressure)
829{
830 const ARegion *region = CTX_wm_region(C);
831 bke::PaintRuntime *paint_runtime = stroke->paint->runtime;
834 const Brush &brush = *BKE_paint_brush_for_read(&paint);
835
836 float2 mouse_delta = final_mouse - stroke->last_mouse_position;
837 float length = normalize_v2(mouse_delta);
838
839 float3 world_space_position_delta;
840 const bool use_scene_spacing = paint_stroke_use_scene_spacing(brush, mode);
841 if (use_scene_spacing) {
842 float3 world_space_position;
843 const bool hit = stroke_get_location_bvh(
844 C, world_space_position, final_mouse, stroke->original);
845 world_space_position = math::transform_point(stroke->vc.obact->object_to_world(),
846 world_space_position);
847 if (hit && stroke->stroke_over_mesh) {
848 world_space_position_delta = world_space_position - stroke->last_world_space_position;
849 length = math::length(world_space_position_delta);
850 stroke->stroke_over_mesh = true;
851 }
852 else {
853 length = 0.0f;
854 world_space_position_delta = {0.0f, 0.0f, 0.0f};
855 stroke->stroke_over_mesh = hit;
856 if (stroke->stroke_over_mesh) {
857 stroke->last_world_space_position = world_space_position;
858 }
859 }
860 }
861
862 float pressure = stroke->last_pressure;
863 float pressure_delta = final_pressure - stroke->last_pressure;
864 const float no_pressure_spacing = paint_space_stroke_spacing_no_pressure(C, stroke);
865 int count = 0;
866 while (length > 0.0f) {
867 const float spacing = paint_space_stroke_spacing_variable(
868 C, stroke, pressure, pressure_delta, length);
869 BLI_assert(spacing >= 0.0f);
870
871 if (length >= spacing) {
872 float2 mouse;
873 if (use_scene_spacing) {
874 float3 final_world_space_position;
875 world_space_position_delta = math::normalize(world_space_position_delta);
876 final_world_space_position = world_space_position_delta * spacing +
878 ED_view3d_project_v2(region, final_world_space_position, mouse);
879
880 stroke->last_scene_spacing_delta = world_space_position_delta * spacing;
881 }
882 else {
883 mouse = stroke->last_mouse_position + mouse_delta * spacing;
884 }
885 pressure = stroke->last_pressure + (spacing / length) * pressure_delta;
886
888 *stroke->brush, spacing / no_pressure_spacing);
889
890 stroke->stroke_distance += spacing / stroke->zoom_2d;
891 paint_brush_stroke_add_step(C, op, stroke, mouse, pressure);
892
893 length -= spacing;
894 pressure = stroke->last_pressure;
895 pressure_delta = final_pressure - stroke->last_pressure;
896
897 count++;
898 }
899 else {
900 break;
901 }
902 }
903
904 return count;
905}
906
908{
909 return (G.debug_value == 887);
910}
911
912/**** Public API ****/
913
915 wmOperator *op,
916 const StrokeGetLocation get_location,
917 const StrokeTestStart test_start,
918 const StrokeUpdateStep update_step,
919 const StrokeRedraw redraw,
920 const StrokeDone done,
921 const int event_type)
922{
924 PaintStroke *stroke = MEM_new<PaintStroke>(__func__);
926 stroke->paint = paint;
927 UnifiedPaintSettings *ups = &paint->unified_paint_settings;
928 bke::PaintRuntime *paint_runtime = paint->runtime;
929 Brush *br = stroke->brush = BKE_paint_brush(paint);
931
933
934 stroke->get_location = get_location;
935 stroke->test_start = test_start;
936 stroke->update_step = update_step;
937 stroke->redraw = redraw;
938 stroke->done = done;
939 stroke->event_type = event_type; /* for modal, return event */
940 stroke->ups = ups;
941 stroke->stroke_mode = RNA_enum_get(op->ptr, "mode");
942
945
946 float zoomx;
947 float zoomy;
948 get_imapaint_zoom(C, &zoomx, &zoomy);
949 stroke->zoom_2d = std::max(zoomx, zoomy);
950
951 /* Check here if color sampling the main brush should do color conversion. This is done here
952 * to avoid locking up to get the image buffer during sampling. */
953 paint_runtime->do_linear_conversion = false;
954 paint_runtime->colorspace = nullptr;
955
956 if (br->mtex.tex && br->mtex.tex->type == TEX_IMAGE && br->mtex.tex->ima) {
958 br->mtex.tex->ima, &br->mtex.tex->iuser, nullptr);
959 if (tex_ibuf && tex_ibuf->float_buffer.data == nullptr) {
960 paint_runtime->do_linear_conversion = true;
961 paint_runtime->colorspace = tex_ibuf->byte_buffer.colorspace;
962 }
963 BKE_image_pool_release_ibuf(br->mtex.tex->ima, tex_ibuf, nullptr);
964 }
965
966 if (stroke->stroke_mode == BRUSH_STROKE_INVERT) {
967 if (br->flag & BRUSH_CURVE) {
969 }
970 }
971 /* initialize here */
972 paint_runtime->overlap_factor = 1.0;
973 paint_runtime->stroke_active = true;
974
975 if (rv3d) {
976 rv3d->rflag |= RV3D_PAINTING;
977 }
978
979 /* Preserve location from last stroke while applying and resetting
980 * ups->average_stroke_counter to 1.
981 */
982 if (paint_runtime->average_stroke_counter) {
983 mul_v3_fl(paint_runtime->average_stroke_accum,
984 1.0f / float(paint_runtime->average_stroke_counter));
985 paint_runtime->average_stroke_counter = 1;
986 }
987
988 /* initialize here to avoid initialization conflict with threaded strokes */
990 if (paint->flags & PAINT_USE_CAVITY_MASK) {
991 BKE_curvemapping_init(paint->cavity_curve);
992 }
993
995
996 paint_runtime->start_pixel_radius = BKE_brush_radius_get(stroke->paint, br);
997
998 return stroke;
999}
1000
1002{
1003 if (RegionView3D *rv3d = CTX_wm_region_view3d(C)) {
1004 rv3d->rflag &= ~RV3D_PAINTING;
1005 }
1006
1008
1009 if (stroke == nullptr) {
1010 return;
1011 }
1012
1013 bke::PaintRuntime *paint_runtime = stroke->paint->runtime;
1014 paint_runtime->draw_anchored = false;
1015 paint_runtime->stroke_active = false;
1016
1017 if (stroke->timer) {
1019 }
1020
1021 if (stroke->stroke_cursor) {
1022 WM_paint_cursor_end(static_cast<wmPaintCursor *>(stroke->stroke_cursor));
1023 }
1024
1025 MEM_delete(stroke);
1026}
1027
1028static void stroke_done(bContext *C, wmOperator *op, PaintStroke *stroke)
1029{
1031 ED_workspace_status_text(C, nullptr);
1032 }
1033 bke::PaintRuntime *paint_runtime = stroke->paint->runtime;
1034
1035 /* reset rotation here to avoid doing so in cursor display */
1036 if (!(stroke->brush->mtex.brush_angle_mode & MTEX_ANGLE_RAKE)) {
1037 paint_runtime->brush_rotation = 0.0f;
1038 }
1039
1040 if (!(stroke->brush->mask_mtex.brush_angle_mode & MTEX_ANGLE_RAKE)) {
1041 paint_runtime->brush_rotation_sec = 0.0f;
1042 }
1043
1044 if (stroke->stroke_started) {
1045 if (stroke->redraw) {
1046 stroke->redraw(C, stroke, true);
1047 }
1048
1049 if (stroke->done) {
1050 stroke->done(C, stroke);
1051 }
1052 }
1053
1054 paint_stroke_free(C, op, stroke);
1055}
1056
1061
1062bool paint_space_stroke_enabled(const Brush &br, const PaintMode mode)
1063{
1064 if ((br.flag & BRUSH_SPACE) == 0) {
1065 return false;
1066 }
1067
1069 /* The Cloth Brush is a special case for stroke spacing. Even if it has grab modes which do
1070 * not support dynamic size, stroke spacing needs to be enabled so it is possible to control
1071 * whether the simulation runs constantly or only when the brush moves when using the cloth
1072 * grab brushes. */
1073 return true;
1074 }
1075
1076 if (mode == PaintMode::SculptCurves &&
1078 {
1079 return false;
1080 }
1081
1083 /* No spacing needed for now. */
1084 return false;
1085 }
1086
1087 return paint_supports_dynamic_size(br, mode);
1088}
1089
1106
1108{
1109 if (br.flag & BRUSH_ANCHORED) {
1110 return false;
1111 }
1112
1113 switch (mode) {
1114 case PaintMode::Sculpt:
1116 break;
1117
1118 case PaintMode::Texture2D: /* fall through */
1121 return false;
1122 }
1123 break;
1124
1125 default:
1126 break;
1127 }
1128 return true;
1129}
1130
1131bool paint_supports_smooth_stroke(PaintStroke *stroke, const Brush &brush, const PaintMode mode)
1132{
1133 /* The grease pencil draw tool needs to enable this when the `stroke_mode` is set to
1134 * `BRUSH_STROKE_SMOOTH`. */
1135 if (mode == PaintMode::GPencil &&
1138 {
1139 return true;
1140 }
1141 if (!(brush.flag & BRUSH_SMOOTH_STROKE) ||
1143 {
1144 return false;
1145 }
1146
1147 switch (mode) {
1148 case PaintMode::Sculpt:
1149 if (sculpt_is_grab_tool(brush)) {
1150 return false;
1151 }
1152 break;
1153 default:
1154 break;
1155 }
1156 return true;
1157}
1158
1160{
1161 /* omit: PAINT_WEIGHT, PAINT_SCULPT_UV, PAINT_INVALID */
1162 return ELEM(
1164}
1165
1167{
1168 if (br.flag & BRUSH_ANCHORED) {
1169 return false;
1170 }
1171
1172 switch (mode) {
1173 case PaintMode::Sculpt:
1174 if (sculpt_is_grab_tool(br)) {
1175 return false;
1176 }
1177 break;
1178 default:
1179 break;
1180 }
1181 return true;
1182}
1183
1184#define PAINT_STROKE_MODAL_CANCEL 1
1185
1187{
1188 static const EnumPropertyItem modal_items[] = {
1189 {PAINT_STROKE_MODAL_CANCEL, "CANCEL", 0, "Cancel", "Cancel and undo a stroke in progress"},
1190 {0}};
1191
1192 static const char *name = "Paint Stroke Modal";
1193
1194 wmKeyMap *keymap = WM_modalkeymap_find(keyconf, name);
1195
1196 /* This function is called for each space-type, only needs to add map once. */
1197 if (!keymap) {
1198 keymap = WM_modalkeymap_ensure(keyconf, name, modal_items);
1199 }
1200
1201 return keymap;
1202}
1203
1205 const int input_samples,
1206 const float x,
1207 const float y,
1208 const float pressure)
1209{
1210 PaintSample *sample = &stroke->samples[stroke->cur_sample];
1211 const int max_samples = std::clamp(input_samples, 1, PAINT_MAX_INPUT_SAMPLES);
1212
1213 sample->mouse[0] = x;
1214 sample->mouse[1] = y;
1215 sample->pressure = pressure;
1216
1217 stroke->cur_sample++;
1218 if (stroke->cur_sample >= max_samples) {
1219 stroke->cur_sample = 0;
1220 }
1221 if (stroke->num_samples < max_samples) {
1222 stroke->num_samples++;
1223 }
1224}
1225
1227{
1228 memset(average, 0, sizeof(*average));
1229
1230 BLI_assert(stroke->num_samples > 0);
1231
1232 for (int i = 0; i < stroke->num_samples; i++) {
1233 average->mouse += stroke->samples[i].mouse;
1234 average->pressure += stroke->samples[i].pressure;
1235 }
1236
1237 average->mouse /= stroke->num_samples;
1238 average->pressure /= stroke->num_samples;
1239
1240 // printf("avg=(%f, %f), num=%d\n", average->mouse[0], average->mouse[1], stroke->num_samples);
1241}
1242
1248 wmOperator *op,
1249 PaintStroke *stroke,
1250 const float spacing,
1251 float *length_residue,
1252 const float2 old_pos,
1253 const float2 new_pos)
1254{
1256 bke::PaintRuntime *paint_runtime = stroke->paint->runtime;
1257 const Brush &brush = *BKE_paint_brush(paint);
1259 const ARegion *region = CTX_wm_region(C);
1260
1261 const bool use_scene_spacing = paint_stroke_use_scene_spacing(brush, mode);
1262
1263 float2 mouse_delta;
1264 float length;
1265 float3 world_space_position_delta;
1266 float3 world_space_position_old;
1267
1268 stroke->last_mouse_position = old_pos;
1269
1270 if (use_scene_spacing) {
1271 const bool hit_old = stroke_get_location_bvh(
1272 C, world_space_position_old, old_pos, stroke->original);
1273
1274 float3 world_space_position_new;
1275 const bool hit_new = stroke_get_location_bvh(
1276 C, world_space_position_new, new_pos, stroke->original);
1277
1278 world_space_position_old = math::transform_point(stroke->vc.obact->object_to_world(),
1279 world_space_position_old);
1280 world_space_position_new = math::transform_point(stroke->vc.obact->object_to_world(),
1281 world_space_position_new);
1282 if (hit_old && hit_new && stroke->stroke_over_mesh) {
1283 world_space_position_delta = world_space_position_new - world_space_position_old;
1284 length = math::length(world_space_position_delta);
1285 stroke->stroke_over_mesh = true;
1286 }
1287 else {
1288 length = 0.0f;
1289 world_space_position_delta = {0.0f, 0.0f, 0.0f};
1290 stroke->stroke_over_mesh = hit_new;
1291 if (stroke->stroke_over_mesh) {
1292 stroke->last_world_space_position = world_space_position_old;
1293 }
1294 }
1295 }
1296 else {
1297 mouse_delta = new_pos - old_pos;
1298 mouse_delta = math::normalize_and_get_length(mouse_delta, length);
1299 }
1300
1301 BLI_assert(length >= 0.0f);
1302
1303 if (length == 0.0f) {
1304 return;
1305 }
1306
1307 float2 mouse;
1308 while (length > 0.0f) {
1309 float spacing_final = spacing - *length_residue;
1310 length += *length_residue;
1311 *length_residue = 0.0;
1312
1313 if (length >= spacing) {
1314 if (use_scene_spacing) {
1315 world_space_position_delta = math::normalize(world_space_position_delta);
1316 const float3 final_world_space_position = world_space_position_delta * spacing_final +
1317 world_space_position_old;
1318 ED_view3d_project_v2(region, final_world_space_position, mouse);
1319 }
1320 else {
1321 mouse = stroke->last_mouse_position + mouse_delta * spacing_final;
1322 }
1323
1324 paint_runtime->overlap_factor = paint_stroke_integrate_overlap(*stroke->brush, 1.0);
1325
1326 stroke->stroke_distance += spacing / stroke->zoom_2d;
1327 paint_brush_stroke_add_step(C, op, stroke, mouse, 1.0);
1328
1329 length -= spacing;
1330 spacing_final = spacing;
1331 }
1332 else {
1333 break;
1334 }
1335 }
1336
1337 *length_residue = length;
1338}
1339
1341 wmOperator *op,
1342 PaintStroke *stroke,
1343 const float2 mouse)
1344{
1345 Brush *br = stroke->brush;
1346 bke::PaintRuntime *paint_runtime = stroke->paint->runtime;
1347 if (stroke->stroke_started && (br->flag & BRUSH_LINE)) {
1348 paint_runtime->overlap_factor = paint_stroke_integrate_overlap(*br, 1.0);
1349
1350 paint_brush_stroke_add_step(C, op, stroke, stroke->last_mouse_position, 1.0);
1351 paint_space_stroke(C, op, stroke, mouse, 1.0);
1352 }
1353}
1354
1356{
1357 const Brush &br = *stroke->brush;
1358 if (!(br.flag & BRUSH_CURVE)) {
1359 return false;
1360 }
1361
1363 bke::PaintRuntime *paint_runtime = stroke->paint->runtime;
1364 const float no_pressure_spacing = paint_space_stroke_spacing_no_pressure(C, stroke);
1365 const PaintCurve *pc = br.paint_curve;
1366
1367 if (!pc) {
1368 return true;
1369 }
1370
1371#ifdef DEBUG_TIME
1372 TIMEIT_START_AVERAGED(whole_stroke);
1373#endif
1374
1375 const PaintCurvePoint *pcp = pc->points;
1376 paint_runtime->overlap_factor = paint_stroke_integrate_overlap(br, 1.0);
1377
1378 float length_residue = 0.0f;
1379 for (int i = 0; i < pc->tot_points - 1; i++, pcp++) {
1380 float data[(PAINT_CURVE_NUM_SEGMENTS + 1) * 2];
1381 float tangents[(PAINT_CURVE_NUM_SEGMENTS + 1) * 2];
1382 const PaintCurvePoint *pcp_next = pcp + 1;
1383 bool do_rake = false;
1384
1385 for (int j = 0; j < 2; j++) {
1387 pcp->bez.vec[2][j],
1388 pcp_next->bez.vec[0][j],
1389 pcp_next->bez.vec[1][j],
1390 data + j,
1392 sizeof(float[2]));
1393 }
1394
1397 {
1398 do_rake = true;
1399 for (int j = 0; j < 2; j++) {
1401 pcp->bez.vec[2][j],
1402 pcp_next->bez.vec[0][j],
1403 pcp_next->bez.vec[1][j],
1404 tangents + j,
1406 sizeof(float[2]));
1407 }
1408 }
1409
1410 for (int j = 0; j < PAINT_CURVE_NUM_SEGMENTS; j++) {
1411 if (do_rake) {
1412 const float rotation = atan2f(tangents[2 * j + 1], tangents[2 * j]) + float(0.5f * M_PI);
1414 }
1415
1416 if (!stroke->stroke_started) {
1417 stroke->last_pressure = 1.0;
1418 copy_v2_v2(stroke->last_mouse_position, data + 2 * j);
1419
1422 C, stroke->last_world_space_position, data + 2 * j, stroke->original);
1423 mul_m4_v3(stroke->vc.obact->object_to_world().ptr(), stroke->last_world_space_position);
1424 }
1425
1426 stroke->stroke_started = stroke->test_start(C, op, stroke->last_mouse_position);
1427
1428 if (stroke->stroke_started) {
1429 paint_brush_stroke_add_step(C, op, stroke, data + 2 * j, 1.0);
1431 op,
1432 stroke,
1433 no_pressure_spacing,
1434 &length_residue,
1435 data + 2 * j,
1436 data + 2 * (j + 1));
1437 }
1438 }
1439 else {
1441 C, op, stroke, no_pressure_spacing, &length_residue, data + 2 * j, data + 2 * (j + 1));
1442 }
1443 }
1444 }
1445
1446 stroke_done(C, op, stroke);
1447
1448#ifdef DEBUG_TIME
1449 TIMEIT_END_AVERAGED(whole_stroke);
1450#endif
1451
1452 return true;
1453}
1454
1456{
1457 if (stroke->constrain_line) {
1458 float2 line = mouse - stroke->last_mouse_position;
1459 float angle = atan2f(line[1], line[0]);
1460 const float len = math::length(line);
1461
1462 /* divide angle by PI/4 */
1463 angle = 4.0f * angle / float(M_PI);
1464
1465 /* now take residue */
1466 const float res = angle - floorf(angle);
1467
1468 /* residue decides how close we are at a certain angle */
1469 if (res <= 0.5f) {
1471 }
1472 else {
1473 angle = (floorf(angle) + 1.0f) * float(M_PI_4);
1474 }
1475
1476 mouse[0] = stroke->constrained_pos[0] = len * cosf(angle) + stroke->last_mouse_position[0];
1477 mouse[1] = stroke->constrained_pos[1] = len * sinf(angle) + stroke->last_mouse_position[1];
1478 }
1479}
1480
1482 wmOperator *op,
1483 const wmEvent *event,
1484 PaintStroke **stroke_p)
1485{
1488 bke::PaintRuntime &paint_runtime = *paint->runtime;
1489 PaintStroke *stroke = *stroke_p;
1490 const Brush *br = stroke->brush = BKE_paint_brush(paint);
1491 bool first_dab = false;
1492 bool first_modal = false;
1493 bool redraw = false;
1494
1495 if (event->type == INBETWEEN_MOUSEMOVE &&
1497 {
1499 }
1500
1501 /* see if tablet affects event. Line, anchored and drag dot strokes do not support pressure */
1502 const float tablet_pressure = WM_event_tablet_data(event, &stroke->pen_flip, nullptr);
1503 float pressure = ((br->flag & (BRUSH_LINE | BRUSH_ANCHORED | BRUSH_DRAG_DOT)) ? 1.0f :
1504 tablet_pressure);
1505
1507 std::string msg = fmt::format("Tablet Pressure: {:.4f}", pressure);
1508 ED_workspace_status_text(C, msg.c_str());
1509 }
1510
1511 /* When processing a timer event the pressure from the event is 0, so use the last valid
1512 * pressure. */
1513 if (event->type == TIMER) {
1514 pressure = stroke->last_tablet_event_pressure;
1515 }
1516 else {
1517 stroke->last_tablet_event_pressure = pressure;
1518 }
1519
1520 const int input_samples = BKE_brush_input_samples_get(stroke->paint, br);
1521 paint_stroke_add_sample(stroke, input_samples, event->mval[0], event->mval[1], pressure);
1522
1523 PaintSample sample_average;
1524 paint_stroke_sample_average(stroke, &sample_average);
1525
1526 /* Tilt. */
1527 if (WM_event_is_tablet(event)) {
1528 stroke->tilt = event->tablet.tilt;
1529 }
1530
1531#ifdef WITH_INPUT_NDOF
1532 /* let NDOF motion pass through to the 3D view so we can paint and rotate simultaneously!
1533 * this isn't perfect... even when an extra MOUSEMOVE is spoofed, the stroke discards it
1534 * since the 2D deltas are zero -- code in this file needs to be updated to use the
1535 * post-NDOF_MOTION MOUSEMOVE */
1536 if (event->type == NDOF_MOTION) {
1537 return OPERATOR_PASS_THROUGH;
1538 }
1539#endif
1540
1541 /* one time initialization */
1542 if (!stroke->stroke_init) {
1543 if (paint_stroke_curve_end(C, op, stroke)) {
1544 *stroke_p = nullptr;
1545 return OPERATOR_FINISHED;
1546 }
1547
1548 stroke->stroke_init = true;
1549 first_modal = true;
1550 }
1551
1552 /* one time stroke initialization */
1553 if (!stroke->stroke_started) {
1554 RNA_boolean_set(op->ptr, "pen_flip", stroke->pen_flip);
1555
1556 stroke->last_pressure = sample_average.pressure;
1557 stroke->last_mouse_position = sample_average.mouse;
1558 if (paint_stroke_use_scene_spacing(*br, mode)) {
1560 C, stroke->last_world_space_position, sample_average.mouse, stroke->original);
1562 stroke->vc.obact->object_to_world(), stroke->last_world_space_position);
1563 }
1564 stroke->stroke_started = stroke->test_start(C, op, sample_average.mouse);
1565
1566 if (stroke->stroke_started) {
1567 /* StrokeTestStart often updates the currently active brush so we need to re-retrieve it
1568 * here. */
1569 br = BKE_paint_brush(paint);
1570
1571 if (paint_supports_smooth_stroke(stroke, *br, mode)) {
1576 stroke);
1577 }
1578
1579 if (br->flag & BRUSH_AIRBRUSH) {
1580 stroke->timer = WM_event_timer_add(
1582 }
1583
1584 if (br->flag & BRUSH_LINE) {
1587 }
1588
1592
1593 first_dab = true;
1594 }
1595 }
1596
1597 /* Cancel */
1598 if (event->type == EVT_MODAL_MAP && event->val == PAINT_STROKE_MODAL_CANCEL) {
1599 if (op->type->cancel) {
1600 op->type->cancel(C, op);
1601 }
1602 else {
1603 paint_stroke_cancel(C, op, stroke);
1604 }
1605 return OPERATOR_CANCELLED;
1606 }
1607
1608 /* Handles shift-key active smooth toggling during a grease pencil stroke. */
1609 if (mode == PaintMode::GPencil) {
1610 if (event->modifier & KM_SHIFT) {
1612 if (!stroke->stroke_cursor) {
1617 stroke);
1618 }
1619 }
1620 else {
1622 if (stroke->stroke_cursor != nullptr) {
1623 WM_paint_cursor_end(static_cast<wmPaintCursor *>(stroke->stroke_cursor));
1624 stroke->stroke_cursor = nullptr;
1625 }
1626 }
1627 }
1628
1629 float2 mouse;
1630 if (event->type == stroke->event_type && !first_modal) {
1631 if (event->val == KM_RELEASE) {
1632 mouse = {float(event->mval[0]), float(event->mval[1])};
1633 paint_stroke_line_constrain(stroke, mouse);
1634 paint_stroke_line_end(C, op, stroke, mouse);
1635 stroke_done(C, op, stroke);
1636 *stroke_p = nullptr;
1637 return OPERATOR_FINISHED;
1638 }
1639 }
1640 else if (ELEM(event->type, EVT_RETKEY, EVT_SPACEKEY)) {
1641 paint_stroke_line_end(C, op, stroke, sample_average.mouse);
1642 stroke_done(C, op, stroke);
1643 *stroke_p = nullptr;
1644 return OPERATOR_FINISHED;
1645 }
1646 else if (br->flag & BRUSH_LINE) {
1647 if (event->modifier & KM_ALT) {
1648 stroke->constrain_line = true;
1649 }
1650 else {
1651 stroke->constrain_line = false;
1652 }
1653
1654 mouse = {float(event->mval[0]), float(event->mval[1])};
1655 paint_stroke_line_constrain(stroke, mouse);
1656
1657 if (stroke->stroke_started && (first_modal || ISMOUSE_MOTION(event->type))) {
1660 {
1661 copy_v2_v2(paint_runtime.last_rake, stroke->last_mouse_position);
1662 }
1663 paint_calculate_rake_rotation(*stroke->paint, *br, mouse, mode, true);
1664 }
1665 }
1666 else if (first_modal ||
1667 /* regular dabs */
1668 (!(br->flag & BRUSH_AIRBRUSH) && ISMOUSE_MOTION(event->type)) ||
1669 /* airbrush */
1670 ((br->flag & BRUSH_AIRBRUSH) && event->type == TIMER &&
1671 event->customdata == stroke->timer))
1672 {
1673 if (paint_smooth_stroke(stroke, &sample_average, mode, mouse, pressure)) {
1674 if (stroke->stroke_started) {
1675 if (paint_space_stroke_enabled(*br, mode)) {
1676 if (paint_space_stroke(C, op, stroke, mouse, pressure)) {
1677 redraw = true;
1678 }
1679 }
1680 else {
1681 const float2 mouse_delta = mouse - stroke->last_mouse_position;
1682 stroke->stroke_distance += math::length(mouse_delta);
1683 paint_brush_stroke_add_step(C, op, stroke, mouse, pressure);
1684 redraw = true;
1685 }
1686 }
1687 }
1688 }
1689
1690 /* we want the stroke to have the first daub at the start location
1691 * instead of waiting till we have moved the space distance */
1692 if (first_dab && paint_space_stroke_enabled(*br, mode) && !(br->flag & BRUSH_SMOOTH_STROKE)) {
1693 paint_runtime.overlap_factor = paint_stroke_integrate_overlap(*br, 1.0);
1694 paint_brush_stroke_add_step(C, op, stroke, sample_average.mouse, sample_average.pressure);
1695 redraw = true;
1696 }
1697
1698 /* Don't update the paint cursor in #INBETWEEN_MOUSEMOVE events. */
1699 if (event->type != INBETWEEN_MOUSEMOVE) {
1700 wmWindow *window = CTX_wm_window(C);
1701 ARegion *region = CTX_wm_region(C);
1702
1703 if (region && (paint->flags & PAINT_SHOW_BRUSH)) {
1704 WM_paint_cursor_tag_redraw(window, region);
1705 }
1706 }
1707
1708 /* Draw for all events (even in between) otherwise updating the brush
1709 * display is noticeably delayed.
1710 */
1711 if (redraw && stroke->redraw) {
1712 stroke->redraw(C, stroke, false);
1713 }
1714
1716}
1717
1719{
1720 /* only when executed for the first time */
1721 if (!stroke->stroke_started) {
1722 PointerRNA firstpoint;
1723 PropertyRNA *strokeprop = RNA_struct_find_property(op->ptr, "stroke");
1724
1725 if (RNA_property_collection_lookup_int(op->ptr, strokeprop, 0, &firstpoint)) {
1726 float2 mouse;
1727 RNA_float_get_array(&firstpoint, "mouse", mouse);
1728 stroke->stroke_started = stroke->test_start(C, op, mouse);
1729 }
1730 }
1731
1733 PropertyRNA *prop = RNA_struct_find_property(op->ptr, "override_location");
1734 const bool override_location = prop && RNA_property_boolean_get(op->ptr, prop) &&
1735 stroke->get_location;
1736 if (stroke->stroke_started) {
1737 RNA_BEGIN (op->ptr, itemptr, "stroke") {
1738 float2 mval;
1739 RNA_float_get_array(&itemptr, "mouse_event", mval);
1740
1741 const float pressure = RNA_float_get(&itemptr, "pressure");
1742 float2 dummy_mouse;
1743 RNA_float_get_array(&itemptr, "mouse", dummy_mouse);
1744
1745 float3 dummy_location;
1746 bool dummy_is_set;
1747
1749 *stroke->brush,
1750 mode,
1751 stroke,
1752 mval,
1753 dummy_mouse,
1754 pressure,
1755 dummy_location,
1756 &dummy_is_set);
1757
1758 if (override_location) {
1759 float3 location;
1760 if (stroke->get_location(C, location, mval, false)) {
1761 RNA_float_set_array(&itemptr, "location", location);
1762 stroke->update_step(C, op, stroke, &itemptr);
1763 }
1764 }
1765 else {
1766 stroke->update_step(C, op, stroke, &itemptr);
1767 }
1768 }
1769 RNA_END;
1770 }
1771
1772 const bool ok = stroke->stroke_started;
1773
1774 stroke_done(C, op, stroke);
1775
1777}
1778
1780{
1781 stroke_done(C, op, stroke);
1782}
1783
1785{
1786 return &stroke->vc;
1787}
1788
1790{
1791 return stroke->mode_data.get();
1792}
1793
1795{
1796 return stroke->pen_flip;
1797}
1798
1800{
1801 return stroke->stroke_mode == BRUSH_STROKE_INVERT;
1802}
1803
1805{
1806 return stroke->stroke_distance;
1807}
1808
1809void paint_stroke_set_mode_data(PaintStroke *stroke, std::unique_ptr<PaintModeData> mode_data)
1810{
1811 stroke->mode_data = std::move(mode_data);
1812}
1813
1815{
1816 return stroke->stroke_started;
1817}
1818
1819static const bToolRef *brush_tool_get(const bContext *C)
1820{
1822 const Object *ob = CTX_data_active_object(C);
1823 const ScrArea *area = CTX_wm_area(C);
1824 const ARegion *region = CTX_wm_region(C);
1825
1826 if (paint && ob && BKE_paint_brush(paint) &&
1827 (area && ELEM(area->spacetype, SPACE_VIEW3D, SPACE_IMAGE)) &&
1828 (region && region->regiontype == RGN_TYPE_WINDOW))
1829 {
1830 if (area->runtime.tool && area->runtime.tool->runtime &&
1832 {
1833 return area->runtime.tool;
1834 }
1835 }
1836 return nullptr;
1837}
1838
1840{
1841 /* Check the current tool is a brush. */
1842 return brush_tool_get(C) != nullptr;
1843}
1844
1846{
1847 const bToolRef *tref = brush_tool_get(C);
1848 if (!tref) {
1849 return false;
1850 }
1851
1852 /* Don't use brush cursor when the tool sets its own cursor. */
1853 if (tref->runtime->cursor != WM_CURSOR_DEFAULT) {
1854 return false;
1855 }
1856
1857 return true;
1858}
1859
1860} // namespace blender::ed::sculpt_paint
bool BKE_brush_use_alpha_pressure(const Brush *brush)
Definition brush.cc:1290
float BKE_brush_curve_strength(eBrushCurvePreset preset, const CurveMapping *cumap, float distance, float brush_radius)
Definition brush.cc:1577
bool BKE_brush_use_size_pressure(const Brush *brush)
Definition brush.cc:1285
float BKE_brush_radius_get(const Paint *paint, const Brush *brush)
Definition brush.cc:1272
void BKE_brush_randomize_texture_coords(Paint *paint, bool mask)
Definition brush.cc:1433
void BKE_brush_jitter_pos(const Paint &paint, const Brush &brush, const float pos[2], float jitterpos[2])
Definition brush.cc:1406
int BKE_brush_input_samples_get(const Paint *paint, const Brush *brush)
Definition brush.cc:1384
float BKE_curvemapping_evaluateF(const CurveMapping *cumap, int cur, float value)
void BKE_curvemapping_init(CurveMapping *cumap)
Depsgraph * CTX_data_ensure_evaluated_depsgraph(const bContext *C)
ScrArea * CTX_wm_area(const bContext *C)
wmWindow * CTX_wm_window(const bContext *C)
Object * CTX_data_active_object(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
RegionView3D * CTX_wm_region_view3d(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
wmWindowManager * CTX_wm_manager(const bContext *C)
void BKE_curve_forward_diff_bezier(float q0, float q1, float q2, float q3, float *p, int it, int stride)
Definition curve.cc:1669
void BKE_curve_forward_diff_tangent_bezier(float q0, float q1, float q2, float q3, float *p, int it, int stride)
Definition curve.cc:1697
ImBuf * BKE_image_pool_acquire_ibuf(Image *ima, ImageUser *iuser, ImagePool *pool)
void BKE_image_pool_release_ibuf(Image *ima, ImBuf *ibuf, ImagePool *pool)
bool paint_calculate_rake_rotation(Paint &paint, const Brush &brush, const float mouse_pos[2], PaintMode paint_mode, bool stroke_has_started)
Definition paint.cc:2193
void BKE_paint_set_overlay_override(eOverlayFlags flag)
Definition paint.cc:303
void paint_update_brush_rake_rotation(Paint &paint, const Brush &brush, float rotation)
Definition paint.cc:2169
const Brush * BKE_paint_brush_for_read(const Paint *paint)
Definition paint.cc:650
Paint * BKE_paint_get_active_from_paintmode(Scene *sce, PaintMode mode)
Definition paint.cc:374
Paint * BKE_paint_get_active_from_context(const bContext *C)
Definition paint.cc:476
Brush * BKE_paint_brush(Paint *paint)
Definition paint.cc:645
PaintMode BKE_paintmode_get_active_from_context(const bContext *C)
Definition paint.cc:505
PaintMode
#define BLI_assert(a)
Definition BLI_assert.h:46
MINLINE float max_ff(float a, float b)
MINLINE float square_f(float a)
#define M_PI
#define M_PI_4
void mul_m4_v3(const float M[4][4], float r[3])
MINLINE void mul_v2_fl(float r[2], float f)
MINLINE void copy_v2_v2(float r[2], const float a[2])
MINLINE void mul_v3_fl(float r[3], float f)
MINLINE void copy_v3_v3(float r[3], const float a[3])
MINLINE void add_v2_v2v2(float r[2], const float a[2], const float b[2])
MINLINE void sub_v2_v2v2(float r[2], const float a[2], const float b[2])
MINLINE float normalize_v2(float n[2])
MINLINE void zero_v3(float r[3])
unsigned int uint
Utility defines for timing/benchmarks.
#define TIMEIT_START_AVERAGED(var)
#define TIMEIT_END_AVERAGED(var)
#define ELEM(...)
eBrushGPaintType
@ GPAINT_BRUSH_TYPE_DRAW
@ SCULPT_BRUSH_TYPE_THUMB
@ SCULPT_BRUSH_TYPE_GRAB
@ SCULPT_BRUSH_TYPE_BOUNDARY
@ SCULPT_BRUSH_TYPE_CLOTH
@ SCULPT_BRUSH_TYPE_POSE
@ SCULPT_BRUSH_TYPE_SNAKE_HOOK
@ SCULPT_BRUSH_TYPE_ELASTIC_DEFORM
@ SCULPT_BRUSH_TYPE_ROTATE
@ BRUSH_SPACE_ATTEN
@ BRUSH_DRAG_DOT
@ BRUSH_SPACING_PRESSURE
@ BRUSH_LINE
@ BRUSH_CURVE
@ BRUSH_EDGE_TO_EDGE
@ BRUSH_SMOOTH_STROKE
@ BRUSH_ANCHORED
@ BRUSH_JITTER_PRESSURE
@ BRUSH_ABSOLUTE_JITTER
@ BRUSH_USE_GRADIENT
@ BRUSH_SCENE_SPACING
@ BRUSH_AIRBRUSH
@ BRUSH_SPACE
@ IMAGE_PAINT_BRUSH_TYPE_FILL
@ IMAGE_PAINT_BRUSH_TYPE_CLONE
@ BRUSH_CLOTH_DEFORM_GRAB
eOverlayFlags
eBrushCurvesSculptType
@ CURVES_SCULPT_BRUSH_TYPE_ADD
@ CURVES_SCULPT_BRUSH_TYPE_DENSITY
Object is a sort of wrapper for general info.
@ PAINT_USE_CAVITY_MASK
@ PAINT_SHOW_BRUSH
#define PAINT_MAX_INPUT_SAMPLES
@ RGN_TYPE_WINDOW
#define RGN_TYPE_ANY
@ SPACE_IMAGE
@ SPACE_VIEW3D
#define SPACE_TYPE_ANY
@ TEX_IMAGE
@ MTEX_ANGLE_RANDOM
@ MTEX_ANGLE_RAKE
@ MTEX_MAP_MODE_AREA
@ MTEX_MAP_MODE_RANDOM
@ MTEX_MAP_MODE_VIEW
@ RV3D_PAINTING
@ OPERATOR_CANCELLED
@ OPERATOR_FINISHED
@ OPERATOR_RUNNING_MODAL
@ OPERATOR_PASS_THROUGH
@ TOOLREF_FLAG_USE_BRUSHES
void ED_workspace_status_text(bContext *C, const char *str)
Definition area.cc:1024
void ED_view3d_project_v2(const ARegion *region, const float world[3], float r_region_co[2])
ViewContext ED_view3d_viewcontext_init(bContext *C, Depsgraph *depsgraph)
void immUniformColor4ubv(const unsigned char rgba[4])
void immUniform4f(const char *name, float x, float y, float z, float w)
void immEnd()
void immUnbindProgram()
void immBindBuiltinProgram(GPUBuiltinShader shader_id)
void immUniform2f(const char *name, float x, float y)
void immVertex2f(uint attr_id, float x, float y)
void immVertex2fv(uint attr_id, const float data[2])
void immUniform1i(const char *name, int x)
void immUniform1f(const char *name, float x)
GPUVertFormat * immVertexFormat()
void immBegin(GPUPrimType, uint vertex_len)
@ GPU_PRIM_LINES
@ GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR
@ GPU_SHADER_3D_UNIFORM_COLOR
void GPU_line_smooth(bool enable)
Definition gpu_state.cc:78
@ GPU_BLEND_NONE
Definition GPU_state.hh:85
@ GPU_BLEND_ALPHA
Definition GPU_state.hh:87
void GPU_blend(GPUBlend blend)
Definition gpu_state.cc:42
void GPU_viewport_size_get_f(float coords[4])
Definition gpu_state.cc:273
uint GPU_vertformat_attr_add(GPUVertFormat *format, blender::StringRef name, blender::gpu::VertAttrType type)
static double angle(const Eigen::Vector3d &v1, const Eigen::Vector3d &v2)
Definition IK_Math.h:117
Read Guarded memory(de)allocation.
#define RNA_BEGIN(sptr, itemptr, propname)
#define RNA_END
#define C
Definition RandGen.cpp:29
@ KM_ALT
Definition WM_types.hh:280
@ KM_SHIFT
Definition WM_types.hh:278
@ KM_RELEASE
Definition WM_types.hh:312
BMesh const char void * data
BPy_StructRNA * depsgraph
static T sum(const btAlignedObjectArray< T > &items)
static RandomNumberGenerator from_random_seed()
Definition rand.cc:288
nullptr float
uint pos
float length(VecOp< float, D >) RET
int count
CCL_NAMESPACE_BEGIN ccl_device float invert(const float color, const float factor)
Definition invert.h:11
ccl_device_inline float2 fabs(const float2 a)
#define G(x, y, z)
bool supports_size_pressure(const Brush &brush)
Definition brush.cc:1925
bool is_cloth_deform_brush(const Brush &brush)
bool stroke_get_location_bvh(bContext *C, float out[3], const float mval[2], const bool force_original)
Definition sculpt.cc:4940
void paint_stroke_jitter_pos(const PaintStroke &stroke, PaintMode mode, const Brush &brush, float pressure, const float mval[2], float r_mouse_out[2])
static float paint_space_stroke_spacing_variable(bContext *C, PaintStroke *stroke, const float pressure, const float pressure_delta, const float length)
static void paint_draw_line_cursor(bContext *, const blender::int2 &xy, const blender::float2 &, void *customdata)
static void paint_draw_smooth_cursor(bContext *C, const blender::int2 &xy, const blender::float2 &, void *customdata)
static float paint_space_stroke_spacing(const bContext *C, PaintStroke *stroke, const float size_factor, const float pressure)
bool paint_supports_texture(PaintMode mode)
static const bToolRef * brush_tool_get(const bContext *C)
void(*)(const bContext *C, PaintStroke *stroke, bool final) StrokeRedraw
static float paint_space_stroke_spacing_no_pressure(const bContext *C, PaintStroke *stroke)
wmOperatorStatus paint_stroke_exec(bContext *C, wmOperator *op, PaintStroke *stroke)
static int paint_space_stroke(bContext *C, wmOperator *op, PaintStroke *stroke, const float2 final_mouse, const float final_pressure)
bool paint_stroke_started(PaintStroke *stroke)
static bool sculpt_is_grab_tool(const Brush &br)
static float paint_stroke_overlapped_curve(const Brush &br, const float x, const float spacing)
static bool paint_stroke_use_jitter(const PaintMode mode, const Brush &brush, const bool invert)
void paint_stroke_cancel(bContext *C, wmOperator *op, PaintStroke *stroke)
bool paint_supports_dynamic_tex_coords(const Brush &br, PaintMode mode)
bool paint_supports_dynamic_size(const Brush &br, PaintMode mode)
static void stroke_done(const bContext *C, PaintStroke *stroke)
static bool paint_stroke_curve_end(bContext *C, wmOperator *op, PaintStroke *stroke)
bool paint_stroke_flipped(PaintStroke *stroke)
static void paint_stroke_sample_average(const PaintStroke *stroke, PaintSample *average)
static bool print_pressure_status_enabled()
static float paint_stroke_integrate_overlap(const Brush &br, const float factor)
wmOperatorStatus paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event, PaintStroke **stroke_p)
bool(*)(bContext *C, float location[3], const float mouse[2], bool force_original) StrokeGetLocation
static bool paint_stroke_use_dash(const Brush &brush)
ViewContext * paint_stroke_view_context(PaintStroke *stroke)
bool paint_brush_cursor_poll(bContext *C)
float object_space_radius_get(const ViewContext &vc, const Paint &paint, const Brush &brush, const float3 &location, const float scale_factor)
Definition sculpt.cc:114
static bool image_paint_brush_type_require_inbetween_mouse_events(const Brush &brush, const PaintMode mode)
static void paint_stroke_line_end(bContext *C, wmOperator *op, PaintStroke *stroke, const float2 mouse)
static bool image_paint_brush_type_require_location(const Brush &brush, const PaintMode mode)
static void paint_stroke_line_constrain(PaintStroke *stroke, float2 &mouse)
static bool curves_sculpt_brush_uses_spacing(const eBrushCurvesSculptType tool)
wmKeyMap * paint_stroke_modal_keymap(wmKeyConfig *keyconf)
bool(*)(bContext *C, wmOperator *op, const float mouse[2]) StrokeTestStart
bool paint_brush_update(bContext *C, const Brush &brush, PaintMode mode, PaintStroke *stroke, const float mouse_init[2], float mouse[2], float pressure, float r_location[3], bool *r_location_is_set)
void * paint_stroke_mode_data(PaintStroke *stroke)
void(*)(bContext *C, wmOperator *op, PaintStroke *stroke, PointerRNA *itemptr) StrokeUpdateStep
float paint_stroke_distance_get(PaintStroke *stroke)
static void paint_stroke_add_sample(PaintStroke *stroke, const int input_samples, const float x, const float y, const float pressure)
bool paint_space_stroke_enabled(const Brush &br, PaintMode mode)
static bool paint_smooth_stroke(PaintStroke *stroke, const PaintSample *sample, const PaintMode mode, float2 &r_mouse, float &r_pressure)
void paint_stroke_free(bContext *C, wmOperator *op, PaintStroke *stroke)
PaintStroke * paint_stroke_new(bContext *C, wmOperator *op, StrokeGetLocation get_location, StrokeTestStart test_start, StrokeUpdateStep update_step, StrokeRedraw redraw, StrokeDone done, int event_type)
static bool paint_stroke_use_scene_spacing(const Brush &brush, const PaintMode mode)
static void paint_brush_stroke_add_step(bContext *C, wmOperator *op, PaintStroke *stroke, const float2 mval, float pressure)
static void paint_line_strokes_spacing(bContext *C, wmOperator *op, PaintStroke *stroke, const float spacing, float *length_residue, const float2 old_pos, const float2 new_pos)
void(*)(const bContext *C, PaintStroke *stroke) StrokeDone
static bool image_paint_brush_type_raycast_original(const Brush &brush, PaintMode)
bool paint_stroke_inverted(PaintStroke *stroke)
bool paint_brush_tool_poll(bContext *C)
void paint_stroke_set_mode_data(PaintStroke *stroke, std::unique_ptr< PaintModeData > mode_data)
bool paint_supports_smooth_stroke(PaintStroke *stroke, const Brush &br, PaintMode mode)
T length(const VecBase< T, Size > &a)
QuaternionBase< T > normalize_and_get_length(const QuaternionBase< T > &q, T &out_length)
T interpolate(const T &a, const T &b, const FactorT &t)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
T distance_squared(const VecBase< T, Size > &a, const VecBase< T, Size > &b)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
blender::VecBase< uint8_t, 4 > uchar4
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
VecBase< float, 3 > float3
float average(point a)
Definition node_math.h:144
bool get_imapaint_zoom(bContext *C, float *zoomx, float *zoomy)
@ BRUSH_STROKE_SMOOTH
@ BRUSH_STROKE_NORMAL
@ BRUSH_STROKE_INVERT
#define PAINT_CURVE_NUM_SEGMENTS
#define PAINT_STROKE_MODAL_CANCEL
const char * name
#define floorf
#define fabsf
#define sqrtf
#define sinf
#define cosf
#define atan2f
void RNA_collection_clear(PointerRNA *ptr, const char *name)
PropertyRNA * RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
void RNA_boolean_set(PointerRNA *ptr, const char *name, bool value)
void RNA_float_get_array(PointerRNA *ptr, const char *name, float *values)
bool RNA_property_boolean_get(PointerRNA *ptr, PropertyRNA *prop)
void RNA_collection_add(PointerRNA *ptr, const char *name, PointerRNA *r_value)
float RNA_float_get(PointerRNA *ptr, const char *name)
bool RNA_property_collection_lookup_int(PointerRNA *ptr, PropertyRNA *prop, int key, PointerRNA *r_ptr)
void RNA_float_set(PointerRNA *ptr, const char *name, float value)
void RNA_enum_set(PointerRNA *ptr, const char *name, int value)
void RNA_float_set_array(PointerRNA *ptr, const char *name, const float *values)
int RNA_enum_get(PointerRNA *ptr, const char *name)
float vec[3][3]
char sculpt_brush_type
float jitter
int cloth_deform_type
struct MTex mtex
float smooth_stroke_factor
int smooth_stroke_radius
char image_brush_type
struct CurveMapping * curve_jitter
int dash_samples
struct CurveMapping * curve_distance_falloff
int jitter_absolute
float dash_ratio
char curves_sculpt_brush_type
struct MTex mask_mtex
char gpencil_brush_type
struct PaintCurve * paint_curve
struct CurveMapping * curve_size
int overlay_flags
struct CurveMapping * curve_strength
const ColorSpace * colorspace
ImBufFloatBuffer float_buffer
ImBufByteBuffer byte_buffer
char brush_angle_mode
char brush_map_mode
float random_angle
struct Tex * tex
PaintCurvePoint * points
PaintRuntimeHandle * runtime
struct bToolRef * tool
ScrArea_Runtime runtime
struct ImageUser iuser
struct Image * ima
ARegion * region
Definition ED_view3d.hh:77
Object * obact
Definition ED_view3d.hh:75
bToolRef_Runtime * runtime
blender::float2 anchored_initial_mouse
const blender::ocio::ColorSpace * colorspace
blender::float3 average_stroke_accum
std::unique_ptr< PaintModeData > mode_data
std::optional< RandomNumberGenerator > rng
PaintSample samples[PAINT_MAX_INPUT_SAMPLES]
int ymin
int xmin
wmEventModifierFlag modifier
Definition WM_types.hh:774
wmEventType type
Definition WM_types.hh:757
short val
Definition WM_types.hh:759
int mval[2]
Definition WM_types.hh:763
void * customdata
Definition WM_types.hh:807
void(* cancel)(bContext *C, wmOperator *op)
Definition WM_types.hh:1073
struct wmOperatorType * type
struct PointerRNA * ptr
i
Definition text_draw.cc:230
max
Definition text_draw.cc:251
uint len
@ WM_CURSOR_DEFAULT
Definition wm_cursors.hh:15
int xy[2]
Definition wm_draw.cc:178
void WM_paint_cursor_tag_redraw(wmWindow *win, ARegion *)
Definition wm_draw.cc:1606
float WM_event_tablet_data(const wmEvent *event, bool *r_pen_flip, float r_tilt[2])
bool WM_event_is_tablet(const wmEvent *event)
#define ISMOUSE_MOTION(event_type)
@ TIMER
@ EVT_MODAL_MAP
@ EVT_SPACEKEY
@ NDOF_MOTION
@ INBETWEEN_MOUSEMOVE
@ EVT_RETKEY
wmKeyMap * WM_modalkeymap_ensure(wmKeyConfig *keyconf, const char *idname, const EnumPropertyItem *items)
Definition wm_keymap.cc:932
wmKeyMap * WM_modalkeymap_find(wmKeyConfig *keyconf, const char *idname)
Definition wm_keymap.cc:959
bool WM_paint_cursor_end(wmPaintCursor *handle)
wmPaintCursor * WM_paint_cursor_activate(short space_type, short region_type, bool(*poll)(bContext *C), wmPaintCursorDraw draw, void *customdata)
wmTimer * WM_event_timer_add(wmWindowManager *wm, wmWindow *win, const wmEventType event_type, const double time_step)
void WM_event_timer_remove(wmWindowManager *wm, wmWindow *, wmTimer *timer)