Blender V4.3
particle_edit.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2007 by Janne Karhu. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include <algorithm>
10#include <cmath>
11#include <cstdlib>
12#include <cstring>
13
14#include "MEM_guardedalloc.h"
15
16#include "DNA_mesh_types.h"
17#include "DNA_meshdata_types.h"
18#include "DNA_scene_types.h"
19#include "DNA_screen_types.h"
20#include "DNA_space_types.h"
21#include "DNA_view3d_types.h"
22
23#include "BLI_kdtree.h"
24#include "BLI_lasso_2d.hh"
25#include "BLI_listbase.h"
26#include "BLI_math_matrix.h"
27#include "BLI_rand.h"
28#include "BLI_rect.h"
29#include "BLI_task.h"
31#include "BLI_utildefines.h"
32
33#include "BKE_bvhutils.hh"
34#include "BKE_context.hh"
35#include "BKE_customdata.hh"
36#include "BKE_global.hh"
37#include "BKE_layer.hh"
38#include "BKE_main.hh"
39#include "BKE_mesh.hh"
41#include "BKE_mesh_runtime.hh"
42#include "BKE_modifier.hh"
43#include "BKE_object.hh"
44#include "BKE_particle.h"
45#include "BKE_pointcache.h"
46#include "BKE_report.hh"
47#include "BKE_scene.hh"
48
49#include "DEG_depsgraph.hh"
50
51#include "ED_mesh.hh"
52#include "ED_object.hh"
53#include "ED_particle.hh"
54#include "ED_physics.hh"
55#include "ED_screen.hh"
56#include "ED_select_utils.hh"
57#include "ED_view3d.hh"
58
59#include "GPU_immediate.hh"
60#include "GPU_immediate_util.hh"
61#include "GPU_state.hh"
62
63#include "UI_resources.hh"
64
65#include "WM_api.hh"
66#include "WM_message.hh"
67#include "WM_toolsystem.hh"
68#include "WM_types.hh"
69
70#include "RNA_access.hh"
71#include "RNA_define.hh"
72
74
75#include "physics_intern.hh"
76
78
79/* -------------------------------------------------------------------- */
84{
86 Scene *scene = CTX_data_scene(C);
88
89 if (!scene || !ob || !(ob->mode & OB_MODE_PARTICLE_EDIT)) {
90 return false;
91 }
92
93 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
94 if (edit == nullptr) {
95 return false;
96 }
97 if (edit->psmd_eval == nullptr || edit->psmd_eval->mesh_final == nullptr) {
98 return false;
99 }
100
101 return true;
102}
103
105{
107 Scene *scene = CTX_data_scene(C);
109
110 if (!scene || !ob || !(ob->mode & OB_MODE_PARTICLE_EDIT)) {
111 return false;
112 }
113
114 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
115 if (edit == nullptr || edit->psys == nullptr) {
116 return false;
117 }
118 if (edit->psmd_eval == nullptr || edit->psmd_eval->mesh_final == nullptr) {
119 return false;
120 }
121
122 return true;
123}
124
126{
127 ScrArea *area = CTX_wm_area(C);
128 ARegion *region = CTX_wm_region(C);
129
130 return (PE_poll(C) && (area && area->spacetype == SPACE_VIEW3D) &&
131 (region && region->regiontype == RGN_TYPE_WINDOW));
132}
133
135{
136 POINT_P;
137
138 if (edit == nullptr) {
139 return;
140 }
141
142 if (edit->points) {
144 if (point->keys) {
145 MEM_freeN(point->keys);
146 }
147 }
148
149 MEM_freeN(edit->points);
150 }
151
152 if (edit->mirror_cache) {
153 MEM_freeN(edit->mirror_cache);
154 }
155
156 if (edit->emitter_cosnos) {
158 edit->emitter_cosnos = nullptr;
159 }
160
161 if (edit->emitter_field) {
162 BLI_kdtree_3d_free(edit->emitter_field);
163 edit->emitter_field = nullptr;
164 }
165
166 psys_free_path_cache(edit->psys, edit);
167
168 MEM_freeN(edit);
169}
170
171int PE_minmax(Depsgraph *depsgraph,
172 Scene *scene,
173 ViewLayer *view_layer,
175 blender::float3 &max)
176{
177 BKE_view_layer_synced_ensure(scene, view_layer);
179 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
180 ParticleSystem *psys;
181 ParticleSystemModifierData *psmd_eval = nullptr;
182 POINT_P;
183 KEY_K;
184 float co[3], mat[4][4];
185 int ok = 0;
186
187 if (!edit) {
188 return ok;
189 }
190
191 if ((psys = edit->psys)) {
192 psmd_eval = edit->psmd_eval;
193 }
194 else {
195 unit_m4(mat);
196 }
197
199 if (psys) {
201 ob, psmd_eval->mesh_final, psys->part->from, psys->particles + p, mat);
202 }
203
205 copy_v3_v3(co, key->co);
206 mul_m4_v3(mat, co);
208 ok = 1;
209 }
210 }
211
212 if (!ok) {
213 BKE_object_minmax(ob, min, max);
214 ok = 1;
215 }
216
217 return ok;
218}
219
222/* -------------------------------------------------------------------- */
227{
228 if (edit) {
229 edit->edited = 1;
230 if (edit->psys) {
231 edit->psys->flag |= PSYS_EDITED;
232 }
233 return 1;
234 }
235
236 return 0;
237}
238
240{
241 return scene->toolsettings ? &scene->toolsettings->particle : nullptr;
242}
243
244static float pe_brush_size_get(const Scene * /*scene*/, ParticleBrushData *brush)
245{
246#if 0 /* TODO: Here we can enable unified brush size, needs more work. */
247 UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
248 float size = (ups->flag & UNIFIED_PAINT_SIZE) ? ups->size : brush->size;
249#endif
250
251 return brush->size;
252}
253
255{
256 if (psys->part && psys->part->type == PART_HAIR) {
257 if ((psys->flag & PSYS_HAIR_DYNAMICS) != 0 && (psys->pointcache->flag & PTCACHE_BAKED) != 0) {
258 return psys->pointcache->edit;
259 }
260 return psys->edit;
261 }
262 if (psys->pointcache->flag & PTCACHE_BAKED) {
263 return psys->pointcache->edit;
264 }
265 return nullptr;
266}
267
268/* NOTE: Similar to creation of edit, but only updates pointers in the
269 * existing struct.
270 */
272{
273 ParticleSystem *psys = edit->psys;
274 ParticleData *pa = psys->particles;
275 for (int p = 0; p < edit->totpoint; p++) {
276 PTCacheEditPoint *point = &edit->points[p];
277 HairKey *hair_key = pa->hair;
278 for (int k = 0; k < point->totkey; k++) {
279 PTCacheEditKey *key = &point->keys[k];
280 key->co = hair_key->co;
281 key->time = &hair_key->time;
282 key->flag = hair_key->editflag;
283 if (!(psys->flag & PSYS_GLOBAL_HAIR)) {
284 key->flag |= PEK_USE_WCO;
285 hair_key->editflag |= PEK_USE_WCO;
286 }
287 hair_key++;
288 }
289 pa++;
290 }
291}
292
293/* always gets at least the first particlesystem even if PSYS_CURRENT flag is not set
294 *
295 * NOTE: this function runs on poll, therefore it can runs many times a second
296 * keep it fast! */
297static PTCacheEdit *pe_get_current(Depsgraph *depsgraph, Scene *scene, Object *ob, bool create)
298{
299 ParticleEditSettings *pset = PE_settings(scene);
300 PTCacheEdit *edit = nullptr;
301 ListBase pidlist;
302 PTCacheID *pid;
303
304 if (pset == nullptr || ob == nullptr) {
305 return nullptr;
306 }
307
308 pset->scene = scene;
309 pset->object = ob;
310
311 BKE_ptcache_ids_from_object(&pidlist, ob, nullptr, 0);
312
313 /* in the case of only one editable thing, set pset->edittype accordingly */
314 if (BLI_listbase_is_single(&pidlist)) {
315 pid = static_cast<PTCacheID *>(pidlist.first);
316 switch (pid->type) {
319 break;
322 break;
324 pset->edittype = PE_TYPE_CLOTH;
325 break;
326 }
327 }
328
329 for (pid = static_cast<PTCacheID *>(pidlist.first); pid; pid = pid->next) {
330 if (pset->edittype == PE_TYPE_PARTICLES && pid->type == PTCACHE_TYPE_PARTICLES) {
331 ParticleSystem *psys = static_cast<ParticleSystem *>(pid->calldata);
332
333 if (psys->flag & PSYS_CURRENT) {
334 if (psys->part && psys->part->type == PART_HAIR) {
335 if (psys->flag & PSYS_HAIR_DYNAMICS && psys->pointcache->flag & PTCACHE_BAKED) {
336 if (create && !psys->pointcache->edit) {
337 PE_create_particle_edit(depsgraph, scene, ob, pid->cache, nullptr);
338 }
339 edit = pid->cache->edit;
340 }
341 else {
342 if (create && !psys->edit) {
343 if (psys->flag & PSYS_HAIR_DONE) {
344 PE_create_particle_edit(depsgraph, scene, ob, nullptr, psys);
345 }
346 }
347 edit = psys->edit;
348 }
349 }
350 else {
351 if (create && pid->cache->flag & PTCACHE_BAKED && !pid->cache->edit) {
352 PE_create_particle_edit(depsgraph, scene, ob, pid->cache, psys);
353 }
354 edit = pid->cache->edit;
355 }
356
357 break;
358 }
359 }
360 else if (pset->edittype == PE_TYPE_SOFTBODY && pid->type == PTCACHE_TYPE_SOFTBODY) {
361 if (create && pid->cache->flag & PTCACHE_BAKED && !pid->cache->edit) {
362 pset->flag |= PE_FADE_TIME;
363 /* Nice to have but doesn't work: `pset->brushtype = PE_BRUSH_COMB;`. */
364 PE_create_particle_edit(depsgraph, scene, ob, pid->cache, nullptr);
365 }
366 edit = pid->cache->edit;
367 break;
368 }
369 else if (pset->edittype == PE_TYPE_CLOTH && pid->type == PTCACHE_TYPE_CLOTH) {
370 if (create && pid->cache->flag & PTCACHE_BAKED && !pid->cache->edit) {
371 pset->flag |= PE_FADE_TIME;
372 /* Nice to have but doesn't work: `pset->brushtype = PE_BRUSH_COMB;`. */
373 PE_create_particle_edit(depsgraph, scene, ob, pid->cache, nullptr);
374 }
375 edit = pid->cache->edit;
376 break;
377 }
378 }
379
380 /* Don't consider inactive or render dependency graphs, since they might be evaluated for a
381 * different number of children. or have different pointer to evaluated particle system or
382 * modifier which will also cause troubles. */
383 if (edit && DEG_is_active(depsgraph)) {
384 edit->pid = *pid;
386 if (edit->psys != nullptr && edit->psys_eval != nullptr) {
387 psys_copy_particles(edit->psys, edit->psys_eval);
389 }
390 edit->flags &= ~PT_CACHE_EDIT_UPDATE_PARTICLE_FROM_EVAL;
391 }
392 }
393
394 BLI_freelistN(&pidlist);
395
396 return edit;
397}
398
400{
401 return pe_get_current(depsgraph, scene, ob, false);
402}
403
405{
406 return pe_get_current(depsgraph, scene, ob, true);
407}
408
409void PE_current_changed(Depsgraph *depsgraph, Scene *scene, Object *ob)
410{
411 if (ob->mode == OB_MODE_PARTICLE_EDIT) {
412 PE_create_current(depsgraph, scene, ob);
413 }
414}
415
416void PE_hide_keys_time(Scene *scene, PTCacheEdit *edit, float cfra)
417{
418 ParticleEditSettings *pset = PE_settings(scene);
419 POINT_P;
420 KEY_K;
421
422 if (pset->flag & PE_FADE_TIME && pset->selectmode == SCE_SELECT_POINT) {
424 LOOP_KEYS {
425 if (fabsf(cfra - *key->time) < pset->fade_frames) {
426 key->flag &= ~PEK_HIDE;
427 }
428 else {
429 key->flag |= PEK_HIDE;
430 // key->flag &= ~PEK_SELECT;
431 }
432 }
433 }
434 }
435 else {
437 LOOP_KEYS {
438 key->flag &= ~PEK_HIDE;
439 }
440 }
441 }
442}
443
444static int pe_x_mirror(Object *ob)
445{
446 if (ob->type == OB_MESH) {
447 return (((Mesh *)ob->data)->symmetry & ME_SYMMETRY_X);
448 }
449
450 return 0;
451}
452
455/* -------------------------------------------------------------------- */
501
502static void PE_set_data(bContext *C, PEData *data)
503{
504 *data = {};
505
506 data->context = C;
507 data->bmain = CTX_data_main(C);
508 data->scene = CTX_data_scene(C);
509 data->view_layer = CTX_data_view_layer(C);
510 data->ob = CTX_data_active_object(C);
511 data->depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
512 data->edit = PE_get_current(data->depsgraph, data->scene, data->ob);
513}
514
515static void PE_set_view3d_data(bContext *C, PEData *data)
516{
517 PE_set_data(C, data);
518
519 data->vc = ED_view3d_viewcontext_init(C, data->depsgraph);
520
521 if (!XRAY_ENABLED(data->vc.v3d)) {
522 ED_view3d_depth_override(data->depsgraph,
523 data->vc.region,
524 data->vc.v3d,
525 data->vc.obact,
527 &data->depths);
528 }
529}
530
531static bool PE_create_shape_tree(PEData *data, Object *shapeob)
532{
533 Object *shapeob_eval = DEG_get_evaluated_object(data->depsgraph, shapeob);
534 const Mesh *mesh = BKE_object_get_evaluated_mesh(shapeob_eval);
535
536 data->shape_bvh = {};
537
538 if (!mesh) {
539 return false;
540 }
541
542 return (BKE_bvhtree_from_mesh_get(&data->shape_bvh, mesh, BVHTREE_FROM_CORNER_TRIS, 4) !=
543 nullptr);
544}
545
546static void PE_free_shape_tree(PEData *data)
547{
548 free_bvhtree_from_mesh(&data->shape_bvh);
549}
550
552{
554 rng_seed ^= POINTER_AS_UINT(data->ob);
555 rng_seed ^= POINTER_AS_UINT(data->edit);
556 data->rng = BLI_rng_new(rng_seed);
557}
558
560{
561 if (data->rng != nullptr) {
562 BLI_rng_free(data->rng);
563 data->rng = nullptr;
564 }
565}
566
567static void PE_data_free(PEData *data)
568{
570 PE_free_shape_tree(data);
571 if (data->depths) {
572 ED_view3d_depths_free(data->depths);
573 data->depths = nullptr;
574 }
575}
576
579/* -------------------------------------------------------------------- */
583static bool key_test_depth(const PEData *data, const float co[3], const int screen_co[2])
584{
585 View3D *v3d = data->vc.v3d;
586 ViewDepths *vd = data->depths;
587 float depth;
588
589 /* nothing to do */
590 if (XRAY_ENABLED(v3d)) {
591 return true;
592 }
593
594/* used to calculate here but all callers have the screen_co already, so pass as arg */
595#if 0
596 if (ED_view3d_project_int_global(data->vc.region,
597 co,
598 screen_co,
601 {
602 return 0;
603 }
604#endif
605
606 /* check if screen_co is within bounds because brush_cut uses out of screen coords */
607 if (screen_co[0] >= 0 && screen_co[0] < vd->w && screen_co[1] >= 0 && screen_co[1] < vd->h) {
608 BLI_assert(vd && vd->depths);
609 /* we know its not clipped */
610 depth = vd->depths[screen_co[1] * vd->w + screen_co[0]];
611 }
612 else {
613 return false;
614 }
615
616 float win[3];
617 ED_view3d_project_v3(data->vc.region, co, win);
618
619 if (win[2] - 0.00001f > depth) {
620 return false;
621 }
622 return true;
623}
624
625static bool key_inside_circle(const PEData *data, float rad, const float co[3], float *distance)
626{
627 float dx, dy, dist;
628 int screen_co[2];
629
630 /* TODO: should this check V3D_PROJ_TEST_CLIP_BB too? */
631 if (ED_view3d_project_int_global(data->vc.region, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) !=
633 {
634 return false;
635 }
636
637 dx = data->mval[0] - screen_co[0];
638 dy = data->mval[1] - screen_co[1];
639 dist = sqrtf(dx * dx + dy * dy);
640
641 if (dist > rad) {
642 return false;
643 }
644
645 if (key_test_depth(data, co, screen_co)) {
646 if (distance) {
647 *distance = dist;
648 }
649
650 return true;
651 }
652
653 return false;
654}
655
656static bool key_inside_rect(PEData *data, const float co[3])
657{
658 int screen_co[2];
659
660 if (ED_view3d_project_int_global(data->vc.region, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) !=
662 {
663 return false;
664 }
665
666 if (screen_co[0] > data->rect->xmin && screen_co[0] < data->rect->xmax &&
667 screen_co[1] > data->rect->ymin && screen_co[1] < data->rect->ymax)
668 {
669 return key_test_depth(data, co, screen_co);
670 }
671
672 return false;
673}
674
675static bool key_inside_test(PEData *data, const float co[3])
676{
677 if (data->mval) {
678 return key_inside_circle(data, data->rad, co, nullptr);
679 }
680 return key_inside_rect(data, co);
681}
682
684{
685 KEY_K;
686
687 if (point->flag & PEP_HIDE) {
688 return false;
689 }
690
692 return true;
693 }
694
695 return false;
696}
697
700/* -------------------------------------------------------------------- */
704using ForPointFunc = void (*)(PEData *data, int point_index);
705using ForHitPointFunc = void (*)(PEData *data, int point_index, float mouse_distance);
706
707using ForKeyFunc = void (*)(PEData *data, int point_index, int key_index, bool is_inside);
708
709using ForKeyMatFunc = void (*)(PEData *data,
710 const float mat[4][4],
711 const float imat[4][4],
712 int point_index,
713 int key_index,
714 PTCacheEditKey *key);
715using ForHitKeyMatFunc = void (*)(PEData *data,
716 float mat[4][4],
717 float imat[4][4],
718 int point_index,
719 int key_index,
720 PTCacheEditKey *key,
721 float mouse_distance);
722
724 PSEL_NEAREST = (1 << 0),
725 PSEL_ALL_KEYS = (1 << 1),
726};
727
728static void for_mouse_hit_keys(PEData *data, ForKeyFunc func, const enum eParticleSelectFlag flag)
729{
730 ParticleEditSettings *pset = PE_settings(data->scene);
731 PTCacheEdit *edit = data->edit;
732 POINT_P;
733 KEY_K;
734 int nearest_point, nearest_key;
735 float dist = data->rad;
736
737 /* in path select mode we have no keys */
738 if (pset->selectmode == SCE_SELECT_PATH) {
739 return;
740 }
741
742 nearest_point = -1;
743 nearest_key = -1;
744
746 if (pset->selectmode == SCE_SELECT_END) {
747 if (point->totkey) {
748 /* only do end keys */
749 key = point->keys + point->totkey - 1;
750
751 if (flag & PSEL_NEAREST) {
752 if (key_inside_circle(data, dist, KEY_WCO, &dist)) {
753 nearest_point = p;
754 nearest_key = point->totkey - 1;
755 }
756 }
757 else {
758 const bool is_inside = key_inside_test(data, KEY_WCO);
759 if (is_inside || (flag & PSEL_ALL_KEYS)) {
760 func(data, p, point->totkey - 1, is_inside);
761 }
762 }
763 }
764 }
765 else {
766 /* do all keys */
768 if (flag & PSEL_NEAREST) {
769 if (key_inside_circle(data, dist, KEY_WCO, &dist)) {
770 nearest_point = p;
771 nearest_key = k;
772 }
773 }
774 else {
775 const bool is_inside = key_inside_test(data, KEY_WCO);
776 if (is_inside || (flag & PSEL_ALL_KEYS)) {
777 func(data, p, k, is_inside);
778 }
779 }
780 }
781 }
782 }
783
784 /* do nearest only */
785 if (flag & PSEL_NEAREST) {
786 if (nearest_point != -1) {
787 func(data, nearest_point, nearest_key, true);
788 }
789 }
790}
791
792static void foreach_mouse_hit_point(PEData *data, ForHitPointFunc func, int selected)
793{
794 ParticleEditSettings *pset = PE_settings(data->scene);
795 PTCacheEdit *edit = data->edit;
796 POINT_P;
797 KEY_K;
798
799 /* all is selected in path mode */
800 if (pset->selectmode == SCE_SELECT_PATH) {
801 selected = 0;
802 }
803
805 if (pset->selectmode == SCE_SELECT_END) {
806 if (point->totkey) {
807 /* only do end keys */
808 key = point->keys + point->totkey - 1;
809
810 if (selected == 0 || key->flag & PEK_SELECT) {
811 float mouse_distance;
812 if (key_inside_circle(data, data->rad, KEY_WCO, &mouse_distance)) {
813 func(data, p, mouse_distance);
814 }
815 }
816 }
817 }
818 else {
819 /* do all keys */
821 if (selected == 0 || key->flag & PEK_SELECT) {
822 float mouse_distance;
823 if (key_inside_circle(data, data->rad, KEY_WCO, &mouse_distance)) {
824 func(data, p, mouse_distance);
825 break;
826 }
827 }
828 }
829 }
830 }
831}
832
839
840static void foreach_mouse_hit_key_iter(void *__restrict iter_data_v,
841 const int iter,
842 const TaskParallelTLS *__restrict /*tls*/)
843{
844 KeyIterData *iter_data = (KeyIterData *)iter_data_v;
845 PEData *data = iter_data->data;
846 PTCacheEdit *edit = data->edit;
847 PTCacheEditPoint *point = &edit->points[iter];
848 if (point->flag & PEP_HIDE) {
849 return;
850 }
851 ParticleSystem *psys = edit->psys;
852 ParticleSystemModifierData *psmd_eval = iter_data->edit->psmd_eval;
853 ParticleEditSettings *pset = PE_settings(data->scene);
854 const int selected = iter_data->selected;
855 float mat[4][4], imat[4][4];
856 unit_m4(mat);
857 unit_m4(imat);
858 if (pset->selectmode == SCE_SELECT_END) {
859 if (point->totkey) {
860 /* only do end keys */
861 PTCacheEditKey *key = point->keys + point->totkey - 1;
862
863 if (selected == 0 || key->flag & PEK_SELECT) {
864 float mouse_distance;
865 if (key_inside_circle(data, data->rad, KEY_WCO, &mouse_distance)) {
866 if (edit->psys && !(edit->psys->flag & PSYS_GLOBAL_HAIR)) {
868 data->ob, psmd_eval->mesh_final, psys->part->from, psys->particles + iter, mat);
869 invert_m4_m4(imat, mat);
870 }
871 iter_data->func(data, mat, imat, iter, point->totkey - 1, key, mouse_distance);
872 }
873 }
874 }
875 }
876 else {
877 /* do all keys */
878 PTCacheEditKey *key;
879 int k;
881 if (selected == 0 || key->flag & PEK_SELECT) {
882 float mouse_distance;
883 if (key_inside_circle(data, data->rad, KEY_WCO, &mouse_distance)) {
884 if (edit->psys && !(edit->psys->flag & PSYS_GLOBAL_HAIR)) {
886 data->ob, psmd_eval->mesh_final, psys->part->from, psys->particles + iter, mat);
887 invert_m4_m4(imat, mat);
888 }
889 iter_data->func(data, mat, imat, iter, k, key, mouse_distance);
890 }
891 }
892 }
893 }
894}
895
896static void foreach_mouse_hit_key(PEData *data, ForHitKeyMatFunc func, int selected)
897{
898 PTCacheEdit *edit = data->edit;
899 ParticleEditSettings *pset = PE_settings(data->scene);
900 /* all is selected in path mode */
901 if (pset->selectmode == SCE_SELECT_PATH) {
902 selected = 0;
903 }
904
905 KeyIterData iter_data;
906 iter_data.data = data;
907 iter_data.edit = edit;
908 iter_data.selected = selected;
909 iter_data.func = func;
910
911 TaskParallelSettings settings;
913 BLI_task_parallel_range(0, edit->totpoint, &iter_data, foreach_mouse_hit_key_iter, &settings);
914}
915
917{
918 PTCacheEdit *edit = data->edit;
919 POINT_P;
920
922 func(data, p);
923 }
924}
925
926static void foreach_selected_key(PEData *data, ForKeyFunc func)
927{
928 PTCacheEdit *edit = data->edit;
929 POINT_P;
930 KEY_K;
931
934 func(data, p, k, true);
935 }
936 }
937}
938
939static void foreach_point(PEData *data, ForPointFunc func)
940{
941 PTCacheEdit *edit = data->edit;
942 POINT_P;
943
945 func(data, p);
946 }
947}
948
949static int count_selected_keys(Scene *scene, PTCacheEdit *edit)
950{
951 ParticleEditSettings *pset = PE_settings(scene);
952 POINT_P;
953 KEY_K;
954 int sel = 0;
955
957 if (pset->selectmode == SCE_SELECT_POINT) {
959 sel++;
960 }
961 }
962 else if (pset->selectmode == SCE_SELECT_END) {
963 if (point->totkey) {
964 key = point->keys + point->totkey - 1;
965 if (key->flag & PEK_SELECT) {
966 sel++;
967 }
968 }
969 }
970 }
971
972 return sel;
973}
974
977/* -------------------------------------------------------------------- */
982{
983 PTCacheEdit *edit;
985 KDTree_3d *tree;
986 KDTreeNearest_3d nearest;
987 HairKey *key;
989 float mat[4][4], co[3];
990 int index, totpart;
991
992 edit = psys->edit;
993 psmd_eval = edit->psmd_eval;
994 totpart = psys->totpart;
995
996 if (!psmd_eval->mesh_final) {
997 return;
998 }
999
1000 tree = BLI_kdtree_3d_new(totpart);
1001
1002 /* Insert particles into KD-tree. */
1004 {
1005 key = pa->hair;
1006 psys_mat_hair_to_orco(ob, psmd_eval->mesh_final, psys->part->from, pa, mat);
1007 copy_v3_v3(co, key->co);
1008 mul_m4_v3(mat, co);
1009 BLI_kdtree_3d_insert(tree, p, co);
1010 }
1011
1012 BLI_kdtree_3d_balance(tree);
1013
1014 /* lookup particles and set in mirror cache */
1015 if (!edit->mirror_cache) {
1016 edit->mirror_cache = static_cast<int *>(MEM_callocN(sizeof(int) * totpart, "PE mirror cache"));
1017 }
1018
1020 {
1021 key = pa->hair;
1022 psys_mat_hair_to_orco(ob, psmd_eval->mesh_final, psys->part->from, pa, mat);
1023 copy_v3_v3(co, key->co);
1024 mul_m4_v3(mat, co);
1025 co[0] = -co[0];
1026
1027 index = BLI_kdtree_3d_find_nearest(tree, co, &nearest);
1028
1029 /* this needs a custom threshold still, duplicated for editmode mirror */
1030 if (index != -1 && index != p && (nearest.dist <= 0.0002f)) {
1031 edit->mirror_cache[p] = index;
1032 }
1033 else {
1034 edit->mirror_cache[p] = -1;
1035 }
1036 }
1037
1038 /* make sure mirrors are in two directions */
1040 {
1041 if (edit->mirror_cache[p]) {
1042 index = edit->mirror_cache[p];
1043 if (edit->mirror_cache[index] != p) {
1044 edit->mirror_cache[p] = -1;
1045 }
1046 }
1047 }
1048
1049 BLI_kdtree_3d_free(tree);
1050}
1051
1053 Object *ob, Mesh *mesh, ParticleSystem *psys, ParticleData *pa, ParticleData *mpa)
1054{
1055 HairKey *hkey, *mhkey;
1056 PTCacheEditPoint *point, *mpoint;
1057 PTCacheEditKey *key, *mkey;
1058 PTCacheEdit *edit;
1059 float mat[4][4], mmat[4][4], immat[4][4];
1060 int i, mi, k;
1061
1062 edit = psys->edit;
1063 i = pa - psys->particles;
1064
1065 /* find mirrored particle if needed */
1066 if (!mpa) {
1067 if (!edit->mirror_cache) {
1068 PE_update_mirror_cache(ob, psys);
1069 }
1070
1071 if (!edit->mirror_cache) {
1072 return; /* something went wrong! */
1073 }
1074
1075 mi = edit->mirror_cache[i];
1076 if (mi == -1) {
1077 return;
1078 }
1079 mpa = psys->particles + mi;
1080 }
1081 else {
1082 mi = mpa - psys->particles;
1083 }
1084
1085 point = edit->points + i;
1086 mpoint = edit->points + mi;
1087
1088 /* make sure they have the same amount of keys */
1089 if (pa->totkey != mpa->totkey) {
1090 if (mpa->hair) {
1091 MEM_freeN(mpa->hair);
1092 }
1093 if (mpoint->keys) {
1094 MEM_freeN(mpoint->keys);
1095 }
1096
1097 mpa->hair = static_cast<HairKey *>(MEM_dupallocN(pa->hair));
1098 mpa->totkey = pa->totkey;
1099 mpoint->keys = static_cast<PTCacheEditKey *>(MEM_dupallocN(point->keys));
1100 mpoint->totkey = point->totkey;
1101
1102 mhkey = mpa->hair;
1103 mkey = mpoint->keys;
1104 for (k = 0; k < mpa->totkey; k++, mkey++, mhkey++) {
1105 mkey->co = mhkey->co;
1106 mkey->time = &mhkey->time;
1107 mkey->flag &= ~PEK_SELECT;
1108 }
1109 }
1110
1111 /* mirror positions and tags */
1112 psys_mat_hair_to_orco(ob, mesh, psys->part->from, pa, mat);
1113 psys_mat_hair_to_orco(ob, mesh, psys->part->from, mpa, mmat);
1114 invert_m4_m4(immat, mmat);
1115
1116 hkey = pa->hair;
1117 mhkey = mpa->hair;
1118 key = point->keys;
1119 mkey = mpoint->keys;
1120 for (k = 0; k < pa->totkey; k++, hkey++, mhkey++, key++, mkey++) {
1121 copy_v3_v3(mhkey->co, hkey->co);
1122 mul_m4_v3(mat, mhkey->co);
1123 mhkey->co[0] = -mhkey->co[0];
1124 mul_m4_v3(immat, mhkey->co);
1125
1126 if (key->flag & PEK_TAG) {
1127 mkey->flag |= PEK_TAG;
1128 }
1129
1130 mkey->length = key->length;
1131 }
1132
1133 if (point->flag & PEP_TAG) {
1134 mpoint->flag |= PEP_TAG;
1135 }
1136 if (point->flag & PEP_EDIT_RECALC) {
1137 mpoint->flag |= PEP_EDIT_RECALC;
1138 }
1139}
1140
1142{
1143 PTCacheEdit *edit;
1144 ParticleSystemModifierData *psmd_eval;
1145 POINT_P;
1146
1147 if (!psys) {
1148 return;
1149 }
1150
1151 edit = psys->edit;
1152 psmd_eval = edit->psmd_eval;
1153
1154 if (psmd_eval == nullptr || psmd_eval->mesh_final == nullptr) {
1155 return;
1156 }
1157
1158 if (!edit->mirror_cache) {
1159 PE_update_mirror_cache(ob, psys);
1160 }
1161
1162 if (!edit->mirror_cache) {
1163 return; /* something went wrong */
1164 }
1165
1166 /* we delay settings the PARS_EDIT_RECALC for mirrored particles
1167 * to avoid doing mirror twice */
1168 LOOP_POINTS {
1169 if (point->flag & PEP_EDIT_RECALC) {
1170 PE_mirror_particle(ob, psmd_eval->mesh_final, psys, psys->particles + p, nullptr);
1171
1172 if (edit->mirror_cache[p] != -1) {
1173 edit->points[edit->mirror_cache[p]].flag &= ~PEP_EDIT_RECALC;
1174 }
1175 }
1176 }
1177
1178 LOOP_POINTS {
1179 if (point->flag & PEP_EDIT_RECALC) {
1180 if (edit->mirror_cache[p] != -1) {
1181 edit->points[edit->mirror_cache[p]].flag |= PEP_EDIT_RECALC;
1182 }
1183 }
1184 }
1185}
1186
1189/* -------------------------------------------------------------------- */
1200
1201static void deflect_emitter_iter(void *__restrict iter_data_v,
1202 const int iter,
1203 const TaskParallelTLS *__restrict /*tls*/)
1204{
1205 DeflectEmitterIter *iter_data = (DeflectEmitterIter *)iter_data_v;
1206 PTCacheEdit *edit = iter_data->edit;
1207 PTCacheEditPoint *point = &edit->points[iter];
1208 if ((point->flag & PEP_EDIT_RECALC) == 0) {
1209 return;
1210 }
1211 Object *object = iter_data->object;
1212 ParticleSystem *psys = iter_data->psys;
1213 ParticleSystemModifierData *psmd_eval = iter_data->edit->psmd_eval;
1214 PTCacheEditKey *key;
1215 int k;
1216 float hairimat[4][4], hairmat[4][4];
1217 int index;
1218 float *vec, *nor, dvec[3], dot, dist_1st = 0.0f;
1219 const float dist = iter_data->dist;
1220 const float emitterdist = iter_data->emitterdist;
1222 object, psmd_eval->mesh_final, psys->part->from, psys->particles + iter, hairmat);
1223
1224 LOOP_KEYS {
1225 mul_m4_v3(hairmat, key->co);
1226 }
1227
1228 LOOP_KEYS {
1229 if (k == 0) {
1230 dist_1st = len_v3v3((key + 1)->co, key->co);
1231 dist_1st *= dist * emitterdist;
1232 }
1233 else {
1234 index = BLI_kdtree_3d_find_nearest(edit->emitter_field, key->co, nullptr);
1235
1236 vec = edit->emitter_cosnos + index * 6;
1237 nor = vec + 3;
1238
1239 sub_v3_v3v3(dvec, key->co, vec);
1240
1241 dot = dot_v3v3(dvec, nor);
1242 copy_v3_v3(dvec, nor);
1243
1244 if (dot > 0.0f) {
1245 if (dot < dist_1st) {
1246 normalize_v3(dvec);
1247 mul_v3_fl(dvec, dist_1st - dot);
1248 add_v3_v3(key->co, dvec);
1249 }
1250 }
1251 else {
1252 normalize_v3(dvec);
1253 mul_v3_fl(dvec, dist_1st - dot);
1254 add_v3_v3(key->co, dvec);
1255 }
1256 if (k == 1) {
1257 dist_1st *= 1.3333f;
1258 }
1259 }
1260 }
1261
1262 invert_m4_m4(hairimat, hairmat);
1263
1264 LOOP_KEYS {
1265 mul_m4_v3(hairimat, key->co);
1266 }
1267}
1268
1269/* tries to stop edited particles from going through the emitter's surface */
1270static void pe_deflect_emitter(Scene *scene, Object *ob, PTCacheEdit *edit)
1271{
1272 ParticleEditSettings *pset = PE_settings(scene);
1273 ParticleSystem *psys;
1274 const float dist = ED_view3d_select_dist_px() * 0.01f;
1275
1276 if (edit == nullptr || edit->psys == nullptr || (pset->flag & PE_DEFLECT_EMITTER) == 0 ||
1277 (edit->psys->flag & PSYS_GLOBAL_HAIR))
1278 {
1279 return;
1280 }
1281
1282 psys = edit->psys;
1283
1284 if (edit->psmd_eval == nullptr || edit->psmd_eval->mesh_final == nullptr) {
1285 return;
1286 }
1287
1288 DeflectEmitterIter iter_data;
1289 iter_data.object = ob;
1290 iter_data.psys = psys;
1291 iter_data.edit = edit;
1292 iter_data.dist = dist;
1293 iter_data.emitterdist = pset->emitterdist;
1294
1295 TaskParallelSettings settings;
1297 BLI_task_parallel_range(0, edit->totpoint, &iter_data, deflect_emitter_iter, &settings);
1298}
1299
1303
1304static void apply_lengths_iter(void *__restrict iter_data_v,
1305 const int iter,
1306 const TaskParallelTLS *__restrict /*tls*/)
1307{
1308 ApplyLengthsIterData *iter_data = (ApplyLengthsIterData *)iter_data_v;
1309 PTCacheEdit *edit = iter_data->edit;
1310 PTCacheEditPoint *point = &edit->points[iter];
1311 if ((point->flag & PEP_EDIT_RECALC) == 0) {
1312 return;
1313 }
1314 PTCacheEditKey *key;
1315 int k;
1316 LOOP_KEYS {
1317 if (k) {
1318 float dv1[3];
1319 sub_v3_v3v3(dv1, key->co, (key - 1)->co);
1320 normalize_v3(dv1);
1321 mul_v3_fl(dv1, (key - 1)->length);
1322 add_v3_v3v3(key->co, (key - 1)->co, dv1);
1323 }
1324 }
1325}
1326
1327/* force set distances between neighboring keys */
1328static void PE_apply_lengths(Scene *scene, PTCacheEdit *edit)
1329{
1330 ParticleEditSettings *pset = PE_settings(scene);
1331
1332 if (edit == nullptr || (pset->flag & PE_KEEP_LENGTHS) == 0) {
1333 return;
1334 }
1335
1336 if (edit->psys && edit->psys->flag & PSYS_GLOBAL_HAIR) {
1337 return;
1338 }
1339
1340 ApplyLengthsIterData iter_data;
1341 iter_data.edit = edit;
1342
1343 TaskParallelSettings settings;
1345 BLI_task_parallel_range(0, edit->totpoint, &iter_data, apply_lengths_iter, &settings);
1346}
1347
1352
1353static void iterate_lengths_iter(void *__restrict iter_data_v,
1354 const int iter,
1355 const TaskParallelTLS *__restrict /*tls*/)
1356{
1357 IterateLengthsIterData *iter_data = (IterateLengthsIterData *)iter_data_v;
1358 PTCacheEdit *edit = iter_data->edit;
1359 PTCacheEditPoint *point = &edit->points[iter];
1360 if ((point->flag & PEP_EDIT_RECALC) == 0) {
1361 return;
1362 }
1363 ParticleEditSettings *pset = iter_data->pset;
1364 float tlen;
1365 float dv0[3] = {0.0f, 0.0f, 0.0f};
1366 float dv1[3] = {0.0f, 0.0f, 0.0f};
1367 float dv2[3] = {0.0f, 0.0f, 0.0f};
1368 for (int j = 1; j < point->totkey; j++) {
1369 PTCacheEditKey *key;
1370 int k;
1371 float mul = 1.0f / float(point->totkey);
1372 if (pset->flag & PE_LOCK_FIRST) {
1373 key = point->keys + 1;
1374 k = 1;
1375 dv1[0] = dv1[1] = dv1[2] = 0.0;
1376 }
1377 else {
1378 key = point->keys;
1379 k = 0;
1380 dv0[0] = dv0[1] = dv0[2] = 0.0;
1381 }
1382
1383 for (; k < point->totkey; k++, key++) {
1384 if (k) {
1385 sub_v3_v3v3(dv0, (key - 1)->co, key->co);
1386 tlen = normalize_v3(dv0);
1387 mul_v3_fl(dv0, (mul * (tlen - (key - 1)->length)));
1388 }
1389 if (k < point->totkey - 1) {
1390 sub_v3_v3v3(dv2, (key + 1)->co, key->co);
1391 tlen = normalize_v3(dv2);
1392 mul_v3_fl(dv2, mul * (tlen - key->length));
1393 }
1394 if (k) {
1395 add_v3_v3((key - 1)->co, dv1);
1396 }
1397 add_v3_v3v3(dv1, dv0, dv2);
1398 }
1399 }
1400}
1401
1402/* try to find a nice solution to keep distances between neighboring keys */
1403static void pe_iterate_lengths(Scene *scene, PTCacheEdit *edit)
1404{
1405 ParticleEditSettings *pset = PE_settings(scene);
1406 if (edit == nullptr || (pset->flag & PE_KEEP_LENGTHS) == 0) {
1407 return;
1408 }
1409 if (edit->psys && edit->psys->flag & PSYS_GLOBAL_HAIR) {
1410 return;
1411 }
1412
1413 IterateLengthsIterData iter_data;
1414 iter_data.edit = edit;
1415 iter_data.pset = pset;
1416
1417 TaskParallelSettings settings;
1419 BLI_task_parallel_range(0, edit->totpoint, &iter_data, iterate_lengths_iter, &settings);
1420}
1421
1423{
1424 POINT_P;
1425 KEY_K;
1426
1427 if (edit == nullptr) {
1428 return;
1429 }
1430
1432 key = point->keys;
1433 for (k = 0; k < point->totkey - 1; k++, key++) {
1434 key->length = len_v3v3(key->co, (key + 1)->co);
1435 }
1436 }
1437}
1438
1439void recalc_emitter_field(Depsgraph * /*depsgraph*/, Object * /*ob*/, ParticleSystem *psys)
1440{
1441 PTCacheEdit *edit = psys->edit;
1442 Mesh *mesh = edit->psmd_eval->mesh_final;
1443 float *vec, *nor;
1444 int i, totface;
1445
1446 if (!mesh) {
1447 return;
1448 }
1449
1450 if (edit->emitter_cosnos) {
1452 }
1453
1454 BLI_kdtree_3d_free(edit->emitter_field);
1455
1456 totface = mesh->totface_legacy;
1457 // int totvert = dm->getNumVerts(dm); /* UNUSED */
1458
1459 edit->emitter_cosnos = static_cast<float *>(
1460 MEM_callocN(sizeof(float[6]) * totface, "emitter cosnos"));
1461
1462 edit->emitter_field = BLI_kdtree_3d_new(totface);
1463
1464 vec = edit->emitter_cosnos;
1465 nor = vec + 3;
1466
1467 const blender::Span<blender::float3> positions = mesh->vert_positions();
1468 const blender::Span<blender::float3> vert_normals = mesh->vert_normals();
1469 const MFace *mfaces = (const MFace *)CustomData_get_layer(&mesh->fdata_legacy, CD_MFACE);
1470 for (i = 0; i < totface; i++, vec += 6, nor += 6) {
1471 const MFace *mface = &mfaces[i];
1472
1473 copy_v3_v3(vec, positions[mface->v1]);
1474 copy_v3_v3(nor, vert_normals[mface->v1]);
1475
1476 add_v3_v3v3(vec, vec, positions[mface->v2]);
1477 add_v3_v3(nor, vert_normals[mface->v2]);
1478
1479 add_v3_v3v3(vec, vec, positions[mface->v3]);
1480 add_v3_v3(nor, vert_normals[mface->v3]);
1481
1482 if (mface->v4) {
1483 add_v3_v3v3(vec, vec, positions[mface->v4]);
1484 add_v3_v3(nor, vert_normals[mface->v4]);
1485
1486 mul_v3_fl(vec, 0.25);
1487 }
1488 else {
1489 mul_v3_fl(vec, 1.0f / 3.0f);
1490 }
1491
1493
1494 BLI_kdtree_3d_insert(edit->emitter_field, i, vec);
1495 }
1496
1497 BLI_kdtree_3d_balance(edit->emitter_field);
1498}
1499
1500static void PE_update_selection(Depsgraph *depsgraph, Scene *scene, Object *ob, int useflag)
1501{
1502 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
1503 HairKey *hkey;
1504 POINT_P;
1505 KEY_K;
1506
1507 /* flag all particles to be updated if not using flag */
1508 if (!useflag) {
1509 LOOP_POINTS {
1510 point->flag |= PEP_EDIT_RECALC;
1511 }
1512 }
1513
1514 /* flush edit key flag to hair key flag to preserve selection
1515 * on save */
1516 if (edit->psys) {
1517 LOOP_POINTS {
1518 hkey = edit->psys->particles[p].hair;
1519 LOOP_KEYS {
1520 hkey->editflag = key->flag;
1521 hkey++;
1522 }
1523 }
1524 }
1525
1526 psys_cache_edit_paths(depsgraph, scene, ob, edit, scene->r.cfra, G.is_rendering);
1527
1528 /* disable update flag */
1529 LOOP_POINTS {
1530 point->flag &= ~PEP_EDIT_RECALC;
1531 }
1532
1534}
1535
1537{
1538 ParticleSystem *psys = edit->psys;
1539 ParticleSystemModifierData *psmd_eval = edit->psmd_eval;
1540 POINT_P;
1541 KEY_K;
1542 float hairmat[4][4];
1543
1544 if (psys == nullptr || psys->edit == nullptr || psmd_eval == nullptr ||
1545 psmd_eval->mesh_final == nullptr)
1546 {
1547 return;
1548 }
1549
1550 LOOP_POINTS {
1551 if (!(psys->flag & PSYS_GLOBAL_HAIR)) {
1553 ob, psmd_eval->mesh_final, psys->part->from, psys->particles + p, hairmat);
1554 }
1555
1556 LOOP_KEYS {
1557 copy_v3_v3(key->world_co, key->co);
1558 if (!(psys->flag & PSYS_GLOBAL_HAIR)) {
1559 mul_m4_v3(hairmat, key->world_co);
1560 }
1561 }
1562 }
1563}
1565{
1566 /* TODO: get frs_sec properly. */
1567 float vec1[3], vec2[3], frs_sec, dfra;
1568 POINT_P;
1569 KEY_K;
1570
1571 /* hair doesn't use velocities */
1572 if (edit->psys || !edit->points || !edit->points->keys->vel) {
1573 return;
1574 }
1575
1576 frs_sec = edit->pid.flag & PTCACHE_VEL_PER_SEC ? 25.0f : 1.0f;
1577
1579 LOOP_KEYS {
1580 if (k == 0) {
1581 dfra = *(key + 1)->time - *key->time;
1582
1583 if (dfra <= 0.0f) {
1584 continue;
1585 }
1586
1587 sub_v3_v3v3(key->vel, (key + 1)->co, key->co);
1588
1589 if (point->totkey > 2) {
1590 sub_v3_v3v3(vec1, (key + 1)->co, (key + 2)->co);
1591 project_v3_v3v3(vec2, vec1, key->vel);
1592 sub_v3_v3v3(vec2, vec1, vec2);
1593 madd_v3_v3fl(key->vel, vec2, 0.5f);
1594 }
1595 }
1596 else if (k == point->totkey - 1) {
1597 dfra = *key->time - *(key - 1)->time;
1598
1599 if (dfra <= 0.0f) {
1600 continue;
1601 }
1602
1603 sub_v3_v3v3(key->vel, key->co, (key - 1)->co);
1604
1605 if (point->totkey > 2) {
1606 sub_v3_v3v3(vec1, (key - 2)->co, (key - 1)->co);
1607 project_v3_v3v3(vec2, vec1, key->vel);
1608 sub_v3_v3v3(vec2, vec1, vec2);
1609 madd_v3_v3fl(key->vel, vec2, 0.5f);
1610 }
1611 }
1612 else {
1613 dfra = *(key + 1)->time - *(key - 1)->time;
1614
1615 if (dfra <= 0.0f) {
1616 continue;
1617 }
1618
1619 sub_v3_v3v3(key->vel, (key + 1)->co, (key - 1)->co);
1620 }
1621 mul_v3_fl(key->vel, frs_sec / dfra);
1622 }
1623 }
1624}
1625
1626void PE_update_object(Depsgraph *depsgraph, Scene *scene, Object *ob, int useflag)
1627{
1628 /* use this to do partial particle updates, not usable when adding or
1629 * removing, then a full redo is necessary and calling this may crash */
1630 ParticleEditSettings *pset = PE_settings(scene);
1631 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
1632 POINT_P;
1633
1634 if (!edit) {
1635 return;
1636 }
1637
1638 /* flag all particles to be updated if not using flag */
1639 if (!useflag) {
1640 LOOP_POINTS {
1641 point->flag |= PEP_EDIT_RECALC;
1642 }
1643 }
1644
1645 /* do post process on particle edit keys */
1646 pe_iterate_lengths(scene, edit);
1647 pe_deflect_emitter(scene, ob, edit);
1648 PE_apply_lengths(scene, edit);
1649 if (pe_x_mirror(ob)) {
1650 PE_apply_mirror(ob, edit->psys);
1651 }
1652 if (edit->psys) {
1653 update_world_cos(ob, edit);
1654 }
1655 if (pset->flag & PE_AUTO_VELOCITY) {
1656 update_velocities(edit);
1657 }
1658
1659 /* Only do this for emitter particles because drawing PE_FADE_TIME is not respected in 2.8 yet
1660 * and flagging with PEK_HIDE will prevent selection. This might get restored once this is
1661 * supported in drawing (but doesn't make much sense for hair anyways). */
1662 if (edit->psys && edit->psys->part->type == PART_EMITTER) {
1663 PE_hide_keys_time(scene, edit, scene->r.cfra);
1664 }
1665
1666 /* regenerate path caches */
1667 psys_cache_edit_paths(depsgraph, scene, ob, edit, scene->r.cfra, G.is_rendering);
1668
1669 /* disable update flag */
1670 LOOP_POINTS {
1671 point->flag &= ~PEP_EDIT_RECALC;
1672 }
1673
1674 if (edit->psys) {
1675 edit->psys->flag &= ~PSYS_HAIR_UPDATED;
1676 }
1677}
1678
1681/* -------------------------------------------------------------------- */
1685/*-----selection callbacks-----*/
1686
1687static void select_key(PEData *data, int point_index, int key_index, bool /*is_inside*/)
1688{
1689 PTCacheEdit *edit = data->edit;
1690 PTCacheEditPoint *point = edit->points + point_index;
1691 PTCacheEditKey *key = point->keys + key_index;
1692
1693 if (data->select) {
1694 key->flag |= PEK_SELECT;
1695 }
1696 else {
1697 key->flag &= ~PEK_SELECT;
1698 }
1699
1700 point->flag |= PEP_EDIT_RECALC;
1701 data->is_changed = true;
1702}
1703
1704static void select_key_op(PEData *data, int point_index, int key_index, bool is_inside)
1705{
1706 PTCacheEdit *edit = data->edit;
1707 PTCacheEditPoint *point = edit->points + point_index;
1708 PTCacheEditKey *key = point->keys + key_index;
1709 const bool is_select = key->flag & PEK_SELECT;
1710 const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside);
1711 if (sel_op_result != -1) {
1712 SET_FLAG_FROM_TEST(key->flag, sel_op_result, PEK_SELECT);
1713 point->flag |= PEP_EDIT_RECALC;
1714 data->is_changed = true;
1715 }
1716}
1717
1718static void select_keys(PEData *data, int point_index, int /*key_index*/, bool /*is_inside*/)
1719{
1720 PTCacheEdit *edit = data->edit;
1721 PTCacheEditPoint *point = edit->points + point_index;
1722 KEY_K;
1723
1724 LOOP_KEYS {
1725 if (data->select) {
1726 key->flag |= PEK_SELECT;
1727 }
1728 else {
1729 key->flag &= ~PEK_SELECT;
1730 }
1731 }
1732
1733 point->flag |= PEP_EDIT_RECALC;
1734}
1735
1738/* -------------------------------------------------------------------- */
1742static bool select_action_apply(PTCacheEditPoint *point, PTCacheEditKey *key, int action)
1743{
1744 bool changed = false;
1745 switch (action) {
1746 case SEL_SELECT:
1747 if ((key->flag & PEK_SELECT) == 0) {
1748 key->flag |= PEK_SELECT;
1749 point->flag |= PEP_EDIT_RECALC;
1750 changed = true;
1751 }
1752 break;
1753 case SEL_DESELECT:
1754 if (key->flag & PEK_SELECT) {
1755 key->flag &= ~PEK_SELECT;
1756 point->flag |= PEP_EDIT_RECALC;
1757 changed = true;
1758 }
1759 break;
1760 case SEL_INVERT:
1761 if ((key->flag & PEK_SELECT) == 0) {
1762 key->flag |= PEK_SELECT;
1763 point->flag |= PEP_EDIT_RECALC;
1764 changed = true;
1765 }
1766 else {
1767 key->flag &= ~PEK_SELECT;
1768 point->flag |= PEP_EDIT_RECALC;
1769 changed = true;
1770 }
1771 break;
1772 }
1773 return changed;
1774}
1775
1777{
1778 Scene *scene = CTX_data_scene(C);
1781 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
1782 POINT_P;
1783 KEY_K;
1784 int action = RNA_enum_get(op->ptr, "action");
1785
1786 if (action == SEL_TOGGLE) {
1787 action = SEL_SELECT;
1790 action = SEL_DESELECT;
1791 break;
1792 }
1793
1794 if (action == SEL_DESELECT) {
1795 break;
1796 }
1797 }
1798 }
1799
1800 bool changed = false;
1803 changed |= select_action_apply(point, key, action);
1804 }
1805 }
1806
1807 if (changed) {
1808 PE_update_selection(depsgraph, scene, ob, 1);
1810 }
1811 return OPERATOR_FINISHED;
1812}
1813
1815{
1816 /* identifiers */
1817 ot->name = "(De)select All";
1818 ot->idname = "PARTICLE_OT_select_all";
1819 ot->description = "(De)select all particles' keys";
1820
1821 /* api callbacks */
1823 ot->poll = PE_poll;
1824
1825 /* flags */
1827
1829}
1830
1833/* -------------------------------------------------------------------- */
1841
1842static void nearest_key_fn(PEData *data, int point_index, int key_index, bool /*is_inside*/)
1843{
1844 PTCacheEdit *edit = data->edit;
1845 PTCacheEditPoint *point = edit->points + point_index;
1846 PTCacheEditKey *key = point->keys + key_index;
1847
1848 NearestParticleData *user_data = static_cast<NearestParticleData *>(data->user_data);
1849 user_data->point = point;
1850 user_data->key = key;
1851 data->is_changed = true;
1852}
1853
1855 const int mval[2],
1856 PTCacheEditPoint **r_point,
1857 PTCacheEditKey **r_key)
1858{
1859 NearestParticleData user_data = {nullptr};
1860
1861 PEData data;
1862 PE_set_view3d_data(C, &data);
1863 data.mval = mval;
1864 data.rad = ED_view3d_select_dist_px();
1865
1866 data.user_data = &user_data;
1868 bool found = data.is_changed;
1869 PE_data_free(&data);
1870
1871 *r_point = user_data.point;
1872 *r_key = user_data.key;
1873 return found;
1874}
1875
1876bool PE_mouse_particles(bContext *C, const int mval[2], const SelectPick_Params *params)
1877{
1879 Scene *scene = CTX_data_scene(C);
1881
1882 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
1883
1884 if (!PE_start_edit(edit)) {
1885 return false;
1886 }
1887
1889 PTCacheEditKey *key;
1890
1891 bool changed = false;
1892 bool found = pe_nearest_point_and_key(C, mval, &point, &key);
1893
1894 if (params->sel_op == SEL_OP_SET) {
1895 if ((found && params->select_passthrough) && (key->flag & PEK_SELECT)) {
1896 found = false;
1897 }
1898 else if (found || params->deselect_all) {
1899 /* Deselect everything. */
1900 changed |= PE_deselect_all_visible_ex(edit);
1901 }
1902 }
1903
1904 if (found) {
1905 switch (params->sel_op) {
1906 case SEL_OP_ADD: {
1907 if ((key->flag & PEK_SELECT) == 0) {
1908 key->flag |= PEK_SELECT;
1909 point->flag |= PEP_EDIT_RECALC;
1910 changed = true;
1911 }
1912 break;
1913 }
1914 case SEL_OP_SUB: {
1915 if ((key->flag & PEK_SELECT) != 0) {
1916 key->flag &= ~PEK_SELECT;
1917 point->flag |= PEP_EDIT_RECALC;
1918 changed = true;
1919 }
1920 break;
1921 }
1922 case SEL_OP_XOR: {
1923 key->flag ^= PEK_SELECT;
1924 point->flag |= PEP_EDIT_RECALC;
1925 changed = true;
1926 break;
1927 }
1928 case SEL_OP_SET: {
1929 if ((key->flag & PEK_SELECT) == 0) {
1930 key->flag |= PEK_SELECT;
1931 point->flag |= PEP_EDIT_RECALC;
1932 changed = true;
1933 }
1934 break;
1935 }
1936 case SEL_OP_AND: {
1937 BLI_assert_unreachable(); /* Doesn't make sense for picking. */
1938 break;
1939 }
1940 }
1941 }
1942
1943 if (changed) {
1944 PE_update_selection(depsgraph, scene, ob, 1);
1946 }
1947
1948 return changed || found;
1949}
1950
1953/* -------------------------------------------------------------------- */
1957static void select_root(PEData *data, int point_index)
1958{
1959 PTCacheEditPoint *point = data->edit->points + point_index;
1960 PTCacheEditKey *key = point->keys;
1961
1962 if (point->flag & PEP_HIDE) {
1963 return;
1964 }
1965
1966 if (data->select_action != SEL_TOGGLE) {
1967 data->is_changed = select_action_apply(point, key, data->select_action);
1968 }
1969 else if (key->flag & PEK_SELECT) {
1970 data->select_toggle_action = SEL_DESELECT;
1971 }
1972}
1973
1975{
1976 PEData data;
1977 int action = RNA_enum_get(op->ptr, "action");
1978
1979 PE_set_data(C, &data);
1980
1981 if (action == SEL_TOGGLE) {
1982 data.select_action = SEL_TOGGLE;
1983 data.select_toggle_action = SEL_SELECT;
1984
1985 foreach_point(&data, select_root);
1986
1987 action = data.select_toggle_action;
1988 }
1989
1990 data.select_action = action;
1991 foreach_point(&data, select_root);
1992
1993 if (data.is_changed) {
1994 PE_update_selection(data.depsgraph, data.scene, data.ob, 1);
1996 }
1997 return OPERATOR_FINISHED;
1998}
1999
2001{
2002 /* identifiers */
2003 ot->name = "Select Roots";
2004 ot->idname = "PARTICLE_OT_select_roots";
2005 ot->description = "Select roots of all visible particles";
2006
2007 /* api callbacks */
2009 ot->poll = PE_poll;
2010
2011 /* flags */
2013
2014 /* properties */
2016}
2017
2020/* -------------------------------------------------------------------- */
2024static void select_tip(PEData *data, int point_index)
2025{
2026 PTCacheEditPoint *point = data->edit->points + point_index;
2027 PTCacheEditKey *key;
2028
2029 if (point->totkey == 0) {
2030 return;
2031 }
2032
2033 key = &point->keys[point->totkey - 1];
2034
2035 if (point->flag & PEP_HIDE) {
2036 return;
2037 }
2038
2039 if (data->select_action != SEL_TOGGLE) {
2040 data->is_changed = select_action_apply(point, key, data->select_action);
2041 }
2042 else if (key->flag & PEK_SELECT) {
2043 data->select_toggle_action = SEL_DESELECT;
2044 }
2045}
2046
2048{
2049 PEData data;
2050 int action = RNA_enum_get(op->ptr, "action");
2051
2052 PE_set_data(C, &data);
2053
2054 if (action == SEL_TOGGLE) {
2055 data.select_action = SEL_TOGGLE;
2056 data.select_toggle_action = SEL_SELECT;
2057
2058 foreach_point(&data, select_tip);
2059
2060 action = data.select_toggle_action;
2061 }
2062
2063 data.select_action = action;
2064 foreach_point(&data, select_tip);
2065
2066 if (data.is_changed) {
2067 PE_update_selection(data.depsgraph, data.scene, data.ob, 1);
2069
2070 return OPERATOR_FINISHED;
2071 }
2072 return OPERATOR_CANCELLED;
2073}
2074
2076{
2077 /* identifiers */
2078 ot->name = "Select Tips";
2079 ot->idname = "PARTICLE_OT_select_tips";
2080 ot->description = "Select tips of all visible particles";
2081
2082 /* api callbacks */
2084 ot->poll = PE_poll;
2085
2086 /* flags */
2088
2089 /* properties */
2091}
2092
2095/* -------------------------------------------------------------------- */
2100
2102 {RAN_HAIR, "HAIR", 0, "Hair", ""},
2103 {RAN_POINTS, "POINTS", 0, "Points", ""},
2104 {0, nullptr, 0, nullptr, nullptr},
2105};
2106
2108{
2109 PEData data;
2110 int type;
2111
2112 /* used by LOOP_VISIBLE_POINTS, LOOP_VISIBLE_KEYS and LOOP_KEYS */
2113 PTCacheEdit *edit;
2115 PTCacheEditKey *key;
2116 int p;
2117 int k;
2118
2119 const float randfac = RNA_float_get(op->ptr, "ratio");
2121 const bool select = (RNA_enum_get(op->ptr, "action") == SEL_SELECT);
2122 RNG *rng;
2123
2124 type = RNA_enum_get(op->ptr, "type");
2125
2126 PE_set_data(C, &data);
2127 data.select_action = SEL_SELECT;
2128 edit = PE_get_current(data.depsgraph, data.scene, data.ob);
2129
2131
2132 switch (type) {
2133 case RAN_HAIR:
2135 int flag = ((BLI_rng_get_float(rng) < randfac) == select) ? SEL_SELECT : SEL_DESELECT;
2136 LOOP_KEYS {
2137 data.is_changed |= select_action_apply(point, key, flag);
2138 }
2139 }
2140 break;
2141 case RAN_POINTS:
2144 int flag = ((BLI_rng_get_float(rng) < randfac) == select) ? SEL_SELECT : SEL_DESELECT;
2145 data.is_changed |= select_action_apply(point, key, flag);
2146 }
2147 }
2148 break;
2149 }
2150
2151 BLI_rng_free(rng);
2152
2153 if (data.is_changed) {
2154 PE_update_selection(data.depsgraph, data.scene, data.ob, 1);
2156 }
2157 return OPERATOR_FINISHED;
2158}
2159
2161{
2162 /* identifiers */
2163 ot->name = "Select Random";
2164 ot->idname = "PARTICLE_OT_select_random";
2165 ot->description = "Select a randomly distributed set of hair or points";
2166
2167 /* api callbacks */
2169 ot->poll = PE_poll;
2170
2171 /* flags */
2173
2174 /* properties */
2177 "type",
2179 RAN_HAIR,
2180 "Type",
2181 "Select either hair or points");
2182}
2183
2186/* -------------------------------------------------------------------- */
2191{
2192 PEData data;
2193 PE_set_data(C, &data);
2194 data.select = true;
2195
2197
2198 PE_update_selection(data.depsgraph, data.scene, data.ob, 1);
2200
2201 return OPERATOR_FINISHED;
2202}
2203
2205{
2206 /* identifiers */
2207 ot->name = "Select Linked All";
2208 ot->idname = "PARTICLE_OT_select_linked";
2209 ot->description = "Select all keys linked to already selected ones";
2210
2211 /* api callbacks */
2213 ot->poll = PE_poll;
2214
2215 /* flags */
2217
2218 /* properties */
2219}
2220
2222{
2223 PEData data;
2224 int mval[2];
2225 int location[2];
2226
2227 RNA_int_get_array(op->ptr, "location", location);
2228 mval[0] = location[0];
2229 mval[1] = location[1];
2230
2231 PE_set_view3d_data(C, &data);
2232 data.mval = mval;
2233 data.rad = 75.0f;
2234 data.select = !RNA_boolean_get(op->ptr, "deselect");
2235
2237 PE_update_selection(data.depsgraph, data.scene, data.ob, 1);
2239 PE_data_free(&data);
2240
2241 return OPERATOR_FINISHED;
2242}
2243
2244static int select_linked_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2245{
2246 RNA_int_set_array(op->ptr, "location", event->mval);
2247 return select_linked_pick_exec(C, op);
2248}
2249
2251{
2252 /* identifiers */
2253 ot->name = "Select Linked";
2254 ot->idname = "PARTICLE_OT_select_linked_pick";
2255 ot->description = "Select nearest particle from mouse pointer";
2256
2257 /* api callbacks */
2261
2262 /* flags */
2264
2265 /* properties */
2267 ot->srna, "deselect", false, "Deselect", "Deselect linked keys rather than selecting them");
2268 RNA_def_int_vector(ot->srna, "location", 2, nullptr, 0, INT_MAX, "Location", "", 0, 16384);
2269}
2270
2273/* -------------------------------------------------------------------- */
2278{
2279 bool changed = false;
2280 POINT_P;
2281 KEY_K;
2282
2285 if ((key->flag & PEK_SELECT) != 0) {
2286 key->flag &= ~PEK_SELECT;
2287 point->flag |= PEP_EDIT_RECALC;
2288 changed = true;
2289 }
2290 }
2291 }
2292 return changed;
2293}
2294
2296{
2297 Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
2298 Scene *scene = CTX_data_scene(C);
2300 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
2301 if (!PE_start_edit(edit)) {
2302 return false;
2303 }
2304 return PE_deselect_all_visible_ex(edit);
2305}
2306
2307bool PE_box_select(bContext *C, const rcti *rect, const int sel_op)
2308{
2309 Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
2310 Scene *scene = CTX_data_scene(C);
2312 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
2313 PEData data;
2314
2315 if (!PE_start_edit(edit)) {
2316 return false;
2317 }
2318
2319 PE_set_view3d_data(C, &data);
2320 data.rect = rect;
2321 data.sel_op = eSelectOp(sel_op);
2322
2323 if (SEL_OP_USE_PRE_DESELECT(sel_op)) {
2324 data.is_changed = PE_deselect_all_visible_ex(edit);
2325 }
2326
2327 if (BLI_rcti_is_empty(rect)) {
2328 /* pass */
2329 }
2330 else {
2332 }
2333
2334 bool is_changed = data.is_changed;
2335 PE_data_free(&data);
2336
2337 if (is_changed) {
2338 PE_update_selection(depsgraph, scene, ob, 1);
2340 }
2341 return is_changed;
2342}
2343
2346/* -------------------------------------------------------------------- */
2351{
2352 PE_data_free(static_cast<PEData *>(data));
2353 MEM_freeN(data);
2354}
2355
2357{
2358 PEData *data = static_cast<PEData *>(MEM_callocN(sizeof(*data), __func__));
2359 wm_userdata->data = data;
2361 wm_userdata->use_free = true;
2362 PE_set_view3d_data(C, data);
2363}
2364
2366 bContext *C, wmGenericUserData *wm_userdata, const int sel_op, const int mval[2], float rad)
2367{
2369 Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
2370 Scene *scene = CTX_data_scene(C);
2372 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
2373
2374 if (!PE_start_edit(edit)) {
2375 return false;
2376 }
2377
2378 if (wm_userdata->data == nullptr) {
2380 }
2381
2382 PEData *data = static_cast<PEData *>(wm_userdata->data);
2383 data->mval = mval;
2384 data->rad = rad;
2385 data->select = (sel_op != SEL_OP_SUB);
2386
2387 if (SEL_OP_USE_PRE_DESELECT(sel_op)) {
2388 data->is_changed = PE_deselect_all_visible_ex(edit);
2389 }
2391
2392 if (data->is_changed) {
2393 PE_update_selection(depsgraph, scene, ob, 1);
2395 }
2396 return data->is_changed;
2397}
2398
2401/* -------------------------------------------------------------------- */
2405int PE_lasso_select(bContext *C, const int mcoords[][2], const int mcoords_len, const int sel_op)
2406{
2407 Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
2408 Scene *scene = CTX_data_scene(C);
2410 ARegion *region = CTX_wm_region(C);
2411 ParticleEditSettings *pset = PE_settings(scene);
2412 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
2413 POINT_P;
2414 KEY_K;
2415 float co[3], mat[4][4];
2416 int screen_co[2];
2417
2418 PEData data;
2419
2420 unit_m4(mat);
2421
2422 if (!PE_start_edit(edit)) {
2423 return OPERATOR_CANCELLED;
2424 }
2425
2426 /* only for depths */
2427 PE_set_view3d_data(C, &data);
2428
2429 if (SEL_OP_USE_PRE_DESELECT(sel_op)) {
2430 data.is_changed |= PE_deselect_all_visible_ex(edit);
2431 }
2432
2433 ParticleSystem *psys = edit->psys;
2434 ParticleSystemModifierData *psmd_eval = edit->psmd_eval;
2436 if (edit->psys && !(psys->flag & PSYS_GLOBAL_HAIR)) {
2438 ob, psmd_eval->mesh_final, psys->part->from, psys->particles + p, mat);
2439 }
2440
2441 if (pset->selectmode == SCE_SELECT_POINT) {
2443 copy_v3_v3(co, key->co);
2444 mul_m4_v3(mat, co);
2445 const bool is_select = key->flag & PEK_SELECT;
2446 const bool is_inside =
2447 ((ED_view3d_project_int_global(region, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) ==
2448 V3D_PROJ_RET_OK) &&
2450 {reinterpret_cast<const blender::int2 *>(mcoords), mcoords_len},
2451 screen_co[0],
2452 screen_co[1],
2453 IS_CLIPPED) &&
2454 key_test_depth(&data, co, screen_co));
2455 const int sel_op_result = ED_select_op_action_deselected(
2456 eSelectOp(sel_op), is_select, is_inside);
2457 if (sel_op_result != -1) {
2458 SET_FLAG_FROM_TEST(key->flag, sel_op_result, PEK_SELECT);
2459 point->flag |= PEP_EDIT_RECALC;
2460 data.is_changed = true;
2461 }
2462 }
2463 }
2464 else if (pset->selectmode == SCE_SELECT_END) {
2465 if (point->totkey) {
2466 key = point->keys + point->totkey - 1;
2467 copy_v3_v3(co, key->co);
2468 mul_m4_v3(mat, co);
2469 const bool is_select = key->flag & PEK_SELECT;
2470 const bool is_inside =
2471 ((ED_view3d_project_int_global(region, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) ==
2472 V3D_PROJ_RET_OK) &&
2474 {reinterpret_cast<const blender::int2 *>(mcoords), mcoords_len},
2475 screen_co[0],
2476 screen_co[1],
2477 IS_CLIPPED) &&
2478 key_test_depth(&data, co, screen_co));
2479 const int sel_op_result = ED_select_op_action_deselected(
2480 eSelectOp(sel_op), is_select, is_inside);
2481 if (sel_op_result != -1) {
2482 SET_FLAG_FROM_TEST(key->flag, sel_op_result, PEK_SELECT);
2483 point->flag |= PEP_EDIT_RECALC;
2484 data.is_changed = true;
2485 }
2486 }
2487 }
2488 }
2489
2490 bool is_changed = data.is_changed;
2491 PE_data_free(&data);
2492
2493 if (is_changed) {
2494 PE_update_selection(depsgraph, scene, ob, 1);
2496 return OPERATOR_FINISHED;
2497 }
2498 return OPERATOR_CANCELLED;
2499}
2500
2503/* -------------------------------------------------------------------- */
2507static int hide_exec(bContext *C, wmOperator *op)
2508{
2510 Scene *scene = CTX_data_scene(C);
2512
2513 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
2514 POINT_P;
2515 KEY_K;
2516
2517 if (RNA_boolean_get(op->ptr, "unselected")) {
2519 point->flag |= PEP_HIDE;
2520 point->flag |= PEP_EDIT_RECALC;
2521
2522 LOOP_KEYS {
2523 key->flag &= ~PEK_SELECT;
2524 }
2525 }
2526 }
2527 else {
2529 point->flag |= PEP_HIDE;
2530 point->flag |= PEP_EDIT_RECALC;
2531
2532 LOOP_KEYS {
2533 key->flag &= ~PEK_SELECT;
2534 }
2535 }
2536 }
2537
2538 PE_update_selection(depsgraph, scene, ob, 1);
2540
2541 return OPERATOR_FINISHED;
2542}
2543
2545{
2546 /* identifiers */
2547 ot->name = "Hide Selected";
2548 ot->idname = "PARTICLE_OT_hide";
2549 ot->description = "Hide selected particles";
2550
2551 /* api callbacks */
2552 ot->exec = hide_exec;
2553 ot->poll = PE_poll;
2554
2555 /* flags */
2557
2558 /* props */
2560 ot->srna, "unselected", false, "Unselected", "Hide unselected rather than selected");
2561}
2562
2565/* -------------------------------------------------------------------- */
2570{
2572 Scene *scene = CTX_data_scene(C);
2574 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
2575 const bool select = RNA_boolean_get(op->ptr, "select");
2576 POINT_P;
2577 KEY_K;
2578
2579 LOOP_POINTS {
2580 if (point->flag & PEP_HIDE) {
2581 point->flag &= ~PEP_HIDE;
2582 point->flag |= PEP_EDIT_RECALC;
2583
2584 LOOP_KEYS {
2586 }
2587 }
2588 }
2589
2590 PE_update_selection(depsgraph, scene, ob, 1);
2592
2593 return OPERATOR_FINISHED;
2594}
2595
2597{
2598 /* identifiers */
2599 ot->name = "Reveal";
2600 ot->idname = "PARTICLE_OT_reveal";
2601 ot->description = "Show hidden particles";
2602
2603 /* api callbacks */
2604 ot->exec = reveal_exec;
2605 ot->poll = PE_poll;
2606
2607 /* flags */
2609
2610 /* props */
2611 RNA_def_boolean(ot->srna, "select", true, "Select", "");
2612}
2613
2616/* -------------------------------------------------------------------- */
2620static void select_less_keys(PEData *data, int point_index)
2621{
2622 PTCacheEdit *edit = data->edit;
2623 PTCacheEditPoint *point = edit->points + point_index;
2624 KEY_K;
2625
2627 if (k == 0) {
2628 if (((key + 1)->flag & PEK_SELECT) == 0) {
2629 key->flag |= PEK_TAG;
2630 }
2631 }
2632 else if (k == point->totkey - 1) {
2633 if (((key - 1)->flag & PEK_SELECT) == 0) {
2634 key->flag |= PEK_TAG;
2635 }
2636 }
2637 else {
2638 if ((((key - 1)->flag & (key + 1)->flag) & PEK_SELECT) == 0) {
2639 key->flag |= PEK_TAG;
2640 }
2641 }
2642 }
2643
2644 LOOP_KEYS {
2645 if ((key->flag & PEK_TAG) && (key->flag & PEK_SELECT)) {
2646 key->flag &= ~(PEK_TAG | PEK_SELECT);
2647 point->flag |= PEP_EDIT_RECALC; /* redraw selection only */
2648 data->is_changed = true;
2649 }
2650 }
2651}
2652
2653static int select_less_exec(bContext *C, wmOperator * /*op*/)
2654{
2655 PEData data;
2656
2657 PE_set_data(C, &data);
2659
2660 PE_update_selection(data.depsgraph, data.scene, data.ob, 1);
2662
2663 return OPERATOR_FINISHED;
2664}
2665
2667{
2668 /* identifiers */
2669 ot->name = "Select Less";
2670 ot->idname = "PARTICLE_OT_select_less";
2671 ot->description = "Deselect boundary selected keys of each particle";
2672
2673 /* api callbacks */
2675 ot->poll = PE_poll;
2676
2677 /* flags */
2679}
2680
2683/* -------------------------------------------------------------------- */
2687static void select_more_keys(PEData *data, int point_index)
2688{
2689 PTCacheEdit *edit = data->edit;
2690 PTCacheEditPoint *point = edit->points + point_index;
2691 KEY_K;
2692
2693 LOOP_KEYS {
2694 if (key->flag & PEK_SELECT) {
2695 continue;
2696 }
2697
2698 if (k == 0) {
2699 if ((key + 1)->flag & PEK_SELECT) {
2700 key->flag |= PEK_TAG;
2701 }
2702 }
2703 else if (k == point->totkey - 1) {
2704 if ((key - 1)->flag & PEK_SELECT) {
2705 key->flag |= PEK_TAG;
2706 }
2707 }
2708 else {
2709 if (((key - 1)->flag | (key + 1)->flag) & PEK_SELECT) {
2710 key->flag |= PEK_TAG;
2711 }
2712 }
2713 }
2714
2715 LOOP_KEYS {
2716 if ((key->flag & PEK_TAG) && (key->flag & PEK_SELECT) == 0) {
2717 key->flag &= ~PEK_TAG;
2718 key->flag |= PEK_SELECT;
2719 point->flag |= PEP_EDIT_RECALC; /* redraw selection only */
2720 data->is_changed = true;
2721 }
2722 }
2723}
2724
2725static int select_more_exec(bContext *C, wmOperator * /*op*/)
2726{
2727 PEData data;
2728
2729 PE_set_data(C, &data);
2731
2732 PE_update_selection(data.depsgraph, data.scene, data.ob, 1);
2734
2735 return OPERATOR_FINISHED;
2736}
2737
2739{
2740 /* identifiers */
2741 ot->name = "Select More";
2742 ot->idname = "PARTICLE_OT_select_more";
2743 ot->description = "Select keys linked to boundary selected keys of each particle";
2744
2745 /* api callbacks */
2747 ot->poll = PE_poll;
2748
2749 /* flags */
2751}
2752
2755/* -------------------------------------------------------------------- */
2759static void rekey_particle(PEData *data, int pa_index)
2760{
2761 PTCacheEdit *edit = data->edit;
2762 ParticleSystem *psys = edit->psys;
2763 ParticleSimulationData sim = {nullptr};
2764 ParticleData *pa = psys->particles + pa_index;
2765 PTCacheEditPoint *point = edit->points + pa_index;
2767 HairKey *key, *new_keys, *okey;
2768 PTCacheEditKey *ekey;
2769 float dval, sta, end;
2770 int k;
2771
2772 sim.depsgraph = data->depsgraph;
2773 sim.scene = data->scene;
2774 sim.ob = data->ob;
2775 sim.psys = edit->psys;
2776
2777 pa->flag |= PARS_REKEY;
2778
2779 key = new_keys = static_cast<HairKey *>(
2780 MEM_callocN(data->totrekey * sizeof(HairKey), "Hair re-key keys"));
2781
2782 okey = pa->hair;
2783 /* root and tip stay the same */
2784 copy_v3_v3(key->co, okey->co);
2785 copy_v3_v3((key + data->totrekey - 1)->co, (okey + pa->totkey - 1)->co);
2786
2787 sta = key->time = okey->time;
2788 end = (key + data->totrekey - 1)->time = (okey + pa->totkey - 1)->time;
2789 dval = (end - sta) / float(data->totrekey - 1);
2790
2791 /* interpolate new keys from old ones */
2792 for (k = 1, key++; k < data->totrekey - 1; k++, key++) {
2793 state.time = float(k) / float(data->totrekey - 1);
2794 psys_get_particle_on_path(&sim, pa_index, &state, false);
2795 copy_v3_v3(key->co, state.co);
2796 key->time = sta + k * dval;
2797 }
2798
2799 /* replace keys */
2800 if (pa->hair) {
2801 MEM_freeN(pa->hair);
2802 }
2803 pa->hair = new_keys;
2804
2805 point->totkey = pa->totkey = data->totrekey;
2806
2807 if (point->keys) {
2808 MEM_freeN(point->keys);
2809 }
2810 ekey = point->keys = static_cast<PTCacheEditKey *>(
2811 MEM_callocN(pa->totkey * sizeof(PTCacheEditKey), "Hair re-key edit keys"));
2812
2813 for (k = 0, key = pa->hair; k < pa->totkey; k++, key++, ekey++) {
2814 ekey->co = key->co;
2815 ekey->time = &key->time;
2816 ekey->flag |= PEK_SELECT;
2817 if (!(psys->flag & PSYS_GLOBAL_HAIR)) {
2818 ekey->flag |= PEK_USE_WCO;
2819 }
2820 }
2821
2822 pa->flag &= ~PARS_REKEY;
2823 point->flag |= PEP_EDIT_RECALC;
2824}
2825
2826static int rekey_exec(bContext *C, wmOperator *op)
2827{
2828 PEData data;
2829
2830 PE_set_data(C, &data);
2831
2832 data.dval = 1.0f / float(data.totrekey - 1);
2833 data.totrekey = RNA_int_get(op->ptr, "keys_number");
2834
2836
2837 recalc_lengths(data.edit);
2838 PE_update_object(data.depsgraph, data.scene, data.ob, 1);
2840
2841 return OPERATOR_FINISHED;
2842}
2843
2845{
2846 /* identifiers */
2847 ot->name = "Rekey";
2848 ot->idname = "PARTICLE_OT_rekey";
2849 ot->description = "Change the number of keys of selected particles (root and tip keys included)";
2850
2851 /* api callbacks */
2852 ot->exec = rekey_exec;
2854 ot->poll = PE_hair_poll;
2855
2856 /* flags */
2858
2859 /* properties */
2860 RNA_def_int(ot->srna, "keys_number", 2, 2, INT_MAX, "Number of Keys", "", 2, 100);
2861}
2862
2864 const bContext *C, Scene *scene, Object *ob, int pa_index, float path_time)
2865{
2866 Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
2867 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
2868 ParticleSystem *psys;
2869 ParticleSimulationData sim = {nullptr};
2870 ParticleData *pa;
2872 HairKey *new_keys, *key;
2873 PTCacheEditKey *ekey;
2874 int k;
2875
2876 if (!edit || !edit->psys) {
2877 return;
2878 }
2879
2880 psys = edit->psys;
2881
2882 sim.depsgraph = depsgraph;
2883 sim.scene = scene;
2884 sim.ob = ob;
2885 sim.psys = psys;
2886
2887 pa = psys->particles + pa_index;
2888
2889 pa->flag |= PARS_REKEY;
2890
2891 key = new_keys = static_cast<HairKey *>(MEM_dupallocN(pa->hair));
2892
2893 /* interpolate new keys from old ones (roots stay the same) */
2894 for (k = 1, key++; k < pa->totkey; k++, key++) {
2895 state.time = path_time * float(k) / float(pa->totkey - 1);
2896 psys_get_particle_on_path(&sim, pa_index, &state, false);
2897 copy_v3_v3(key->co, state.co);
2898 }
2899
2900 /* replace hair keys */
2901 if (pa->hair) {
2902 MEM_freeN(pa->hair);
2903 }
2904 pa->hair = new_keys;
2905
2906 /* update edit pointers */
2907 for (k = 0, key = pa->hair, ekey = edit->points[pa_index].keys; k < pa->totkey;
2908 k++, key++, ekey++)
2909 {
2910 ekey->co = key->co;
2911 ekey->time = &key->time;
2912 }
2913
2914 pa->flag &= ~PARS_REKEY;
2915}
2916
2919/* -------------------------------------------------------------------- */
2923static int remove_tagged_particles(Object *ob, ParticleSystem *psys, int mirror)
2924{
2925 PTCacheEdit *edit = psys->edit;
2926 ParticleData *pa, *npa = nullptr, *new_pars = nullptr;
2927 POINT_P;
2928 PTCacheEditPoint *npoint = nullptr, *new_points = nullptr;
2929 ParticleSystemModifierData *psmd_eval;
2930 int i, new_totpart = psys->totpart, removed = 0;
2931
2932 if (mirror) {
2933 /* mirror tags */
2934 psmd_eval = edit->psmd_eval;
2935
2937 PE_mirror_particle(ob, psmd_eval->mesh_final, psys, psys->particles + p, nullptr);
2938 }
2939 }
2940
2942 new_totpart--;
2943 removed++;
2944 }
2945
2946 if (new_totpart != psys->totpart) {
2947 if (new_totpart) {
2948 npa = new_pars = static_cast<ParticleData *>(
2949 MEM_callocN(new_totpart * sizeof(ParticleData), "ParticleData array"));
2950 npoint = new_points = static_cast<PTCacheEditPoint *>(
2951 MEM_callocN(new_totpart * sizeof(PTCacheEditPoint), "PTCacheEditKey array"));
2952
2953 if (ELEM(nullptr, new_pars, new_points)) {
2954 /* allocation error! */
2955 if (new_pars) {
2956 MEM_freeN(new_pars);
2957 }
2958 if (new_points) {
2959 MEM_freeN(new_points);
2960 }
2961 return 0;
2962 }
2963 }
2964
2965 pa = psys->particles;
2966 point = edit->points;
2967 for (i = 0; i < psys->totpart; i++, pa++, point++) {
2968 if (point->flag & PEP_TAG) {
2969 if (point->keys) {
2970 MEM_freeN(point->keys);
2971 }
2972 if (pa->hair) {
2973 MEM_freeN(pa->hair);
2974 }
2975 }
2976 else {
2977 memcpy(npa, pa, sizeof(ParticleData));
2978 memcpy(npoint, point, sizeof(PTCacheEditPoint));
2979 npa++;
2980 npoint++;
2981 }
2982 }
2983
2984 if (psys->particles) {
2985 MEM_freeN(psys->particles);
2986 }
2987 psys->particles = new_pars;
2988
2989 if (edit->points) {
2990 MEM_freeN(edit->points);
2991 }
2992 edit->points = new_points;
2993
2995
2996 if (psys->child) {
2997 MEM_freeN(psys->child);
2998 psys->child = nullptr;
2999 psys->totchild = 0;
3000 }
3001
3002 edit->totpoint = psys->totpart = new_totpart;
3003 }
3004
3005 return removed;
3006}
3007
3008static void remove_tagged_keys(Depsgraph *depsgraph, Object *ob, ParticleSystem *psys)
3009{
3010 PTCacheEdit *edit = psys->edit;
3011 ParticleData *pa;
3012 HairKey *hkey, *nhkey, *new_hkeys = nullptr;
3013 POINT_P;
3014 KEY_K;
3015 PTCacheEditKey *nkey, *new_keys;
3016 short new_totkey;
3017
3018 if (pe_x_mirror(ob)) {
3019 /* mirror key tags */
3023
3024 LOOP_POINTS {
3026 PE_mirror_particle(ob, psmd_eval->mesh_final, psys, psys->particles + p, nullptr);
3027 break;
3028 }
3029 }
3030 }
3031
3032 LOOP_POINTS {
3033 new_totkey = point->totkey;
3035 new_totkey--;
3036 }
3037 /* We can't have elements with less than two keys. */
3038 if (new_totkey < 2) {
3039 point->flag |= PEP_TAG;
3040 }
3041 }
3042 remove_tagged_particles(ob, psys, pe_x_mirror(ob));
3043
3044 LOOP_POINTS {
3045 pa = psys->particles + p;
3046 new_totkey = pa->totkey;
3047
3049 new_totkey--;
3050 }
3051
3052 if (new_totkey != pa->totkey) {
3053 nhkey = new_hkeys = static_cast<HairKey *>(
3054 MEM_callocN(new_totkey * sizeof(HairKey), "HairKeys"));
3055 nkey = new_keys = static_cast<PTCacheEditKey *>(
3056 MEM_callocN(new_totkey * sizeof(PTCacheEditKey), "particle edit keys"));
3057
3058 hkey = pa->hair;
3059 LOOP_KEYS {
3060 while (key->flag & PEK_TAG && hkey < pa->hair + pa->totkey) {
3061 key++;
3062 hkey++;
3063 }
3064
3065 if (hkey < pa->hair + pa->totkey) {
3066 copy_v3_v3(nhkey->co, hkey->co);
3067 nhkey->editflag = hkey->editflag;
3068 nhkey->time = hkey->time;
3069 nhkey->weight = hkey->weight;
3070
3071 nkey->co = nhkey->co;
3072 nkey->time = &nhkey->time;
3073 /* these can be copied from old edit keys */
3074 nkey->flag = key->flag;
3075 nkey->ftime = key->ftime;
3076 nkey->length = key->length;
3077 copy_v3_v3(nkey->world_co, key->world_co);
3078 }
3079 nkey++;
3080 nhkey++;
3081 hkey++;
3082 }
3083
3084 if (pa->hair) {
3085 MEM_freeN(pa->hair);
3086 }
3087
3088 if (point->keys) {
3089 MEM_freeN(point->keys);
3090 }
3091
3092 pa->hair = new_hkeys;
3093 point->keys = new_keys;
3094
3095 point->totkey = pa->totkey = new_totkey;
3096
3097 /* flag for recalculating length */
3098 point->flag |= PEP_EDIT_RECALC;
3099 }
3100 }
3101}
3102
3105/* -------------------------------------------------------------------- */
3109/* works like normal edit mode subdivide, inserts keys between neighboring selected keys */
3110static void subdivide_particle(PEData *data, int pa_index)
3111{
3112 PTCacheEdit *edit = data->edit;
3113 ParticleSystem *psys = edit->psys;
3114 ParticleSimulationData sim = {nullptr};
3115 ParticleData *pa = psys->particles + pa_index;
3116 PTCacheEditPoint *point = edit->points + pa_index;
3118 HairKey *key, *nkey, *new_keys;
3119 PTCacheEditKey *ekey, *nekey, *new_ekeys;
3120
3121 int k;
3122 short totnewkey = 0;
3123 float endtime;
3124
3125 sim.depsgraph = data->depsgraph;
3126 sim.scene = data->scene;
3127 sim.ob = data->ob;
3128 sim.psys = edit->psys;
3129
3130 for (k = 0, ekey = point->keys; k < pa->totkey - 1; k++, ekey++) {
3131 if (ekey->flag & PEK_SELECT && (ekey + 1)->flag & PEK_SELECT) {
3132 totnewkey++;
3133 }
3134 }
3135
3136 if (totnewkey == 0) {
3137 return;
3138 }
3139
3140 pa->flag |= PARS_REKEY;
3141
3142 nkey = new_keys = static_cast<HairKey *>(
3143 MEM_callocN((pa->totkey + totnewkey) * sizeof(HairKey), "Hair subdivide keys"));
3144 nekey = new_ekeys = static_cast<PTCacheEditKey *>(
3145 MEM_callocN((pa->totkey + totnewkey) * sizeof(PTCacheEditKey), "Hair subdivide edit keys"));
3146
3147 key = pa->hair;
3148 endtime = key[pa->totkey - 1].time;
3149
3150 for (k = 0, ekey = point->keys; k < pa->totkey - 1; k++, key++, ekey++) {
3151
3152 memcpy(nkey, key, sizeof(HairKey));
3153 memcpy(nekey, ekey, sizeof(PTCacheEditKey));
3154
3155 nekey->co = nkey->co;
3156 nekey->time = &nkey->time;
3157
3158 nkey++;
3159 nekey++;
3160
3161 if (ekey->flag & PEK_SELECT && (ekey + 1)->flag & PEK_SELECT) {
3162 nkey->time = (key->time + (key + 1)->time) * 0.5f;
3163 state.time = (endtime != 0.0f) ? nkey->time / endtime : 0.0f;
3164 psys_get_particle_on_path(&sim, pa_index, &state, false);
3165 copy_v3_v3(nkey->co, state.co);
3166
3167 nekey->co = nkey->co;
3168 nekey->time = &nkey->time;
3169 nekey->flag |= PEK_SELECT;
3170 if (!(psys->flag & PSYS_GLOBAL_HAIR)) {
3171 nekey->flag |= PEK_USE_WCO;
3172 }
3173
3174 nekey++;
3175 nkey++;
3176 }
3177 }
3178 /* Tip still not copied. */
3179 memcpy(nkey, key, sizeof(HairKey));
3180 memcpy(nekey, ekey, sizeof(PTCacheEditKey));
3181
3182 nekey->co = nkey->co;
3183 nekey->time = &nkey->time;
3184
3185 if (pa->hair) {
3186 MEM_freeN(pa->hair);
3187 }
3188 pa->hair = new_keys;
3189
3190 if (point->keys) {
3191 MEM_freeN(point->keys);
3192 }
3193 point->keys = new_ekeys;
3194
3195 point->totkey = pa->totkey = pa->totkey + totnewkey;
3196 point->flag |= PEP_EDIT_RECALC;
3197 pa->flag &= ~PARS_REKEY;
3198}
3199
3200static int subdivide_exec(bContext *C, wmOperator * /*op*/)
3201{
3202 PEData data;
3203
3204 PE_set_data(C, &data);
3206
3207 recalc_lengths(data.edit);
3208 PE_update_selection(data.depsgraph, data.scene, data.ob, 1);
3209 PE_update_object(data.depsgraph, data.scene, data.ob, 1);
3210 DEG_id_tag_update(&data.ob->id, ID_RECALC_SELECT);
3212
3213 return OPERATOR_FINISHED;
3214}
3215
3217{
3218 /* identifiers */
3219 ot->name = "Subdivide";
3220 ot->idname = "PARTICLE_OT_subdivide";
3221 ot->description = "Subdivide selected particles segments (adds keys)";
3222
3223 /* api callbacks */
3225 ot->poll = PE_hair_poll;
3226
3227 /* flags */
3229}
3230
3233/* -------------------------------------------------------------------- */
3238{
3239 Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
3240 Scene *scene = CTX_data_scene(C);
3242 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
3243 ParticleSystem *psys = edit->psys;
3244 ParticleSystemModifierData *psmd_eval;
3245 KDTree_3d *tree;
3246 KDTreeNearest_3d nearest[10];
3247 POINT_P;
3248 float mat[4][4], co[3], threshold = RNA_float_get(op->ptr, "threshold");
3249 int n, totn, removed, totremoved;
3250
3251 if (psys->flag & PSYS_GLOBAL_HAIR) {
3252 return OPERATOR_CANCELLED;
3253 }
3254
3255 edit = psys->edit;
3256 psmd_eval = edit->psmd_eval;
3257 totremoved = 0;
3258
3259 do {
3260 removed = 0;
3261
3262 tree = BLI_kdtree_3d_new(psys->totpart);
3263
3264 /* Insert particles into KD-tree. */
3267 ob, psmd_eval->mesh_final, psys->part->from, psys->particles + p, mat);
3268 copy_v3_v3(co, point->keys->co);
3269 mul_m4_v3(mat, co);
3270 BLI_kdtree_3d_insert(tree, p, co);
3271 }
3272
3273 BLI_kdtree_3d_balance(tree);
3274
3275 /* tag particles to be removed */
3278 ob, psmd_eval->mesh_final, psys->part->from, psys->particles + p, mat);
3279 copy_v3_v3(co, point->keys->co);
3280 mul_m4_v3(mat, co);
3281
3282 totn = BLI_kdtree_3d_find_nearest_n(tree, co, nearest, 10);
3283
3284 for (n = 0; n < totn; n++) {
3285 /* this needs a custom threshold still */
3286 if (nearest[n].index > p && nearest[n].dist < threshold) {
3287 if (!(point->flag & PEP_TAG)) {
3288 point->flag |= PEP_TAG;
3289 removed++;
3290 }
3291 }
3292 }
3293 }
3294
3295 BLI_kdtree_3d_free(tree);
3296
3297 /* remove tagged particles - don't do mirror here! */
3298 remove_tagged_particles(ob, psys, 0);
3299 totremoved += removed;
3300 } while (removed);
3301
3302 if (totremoved == 0) {
3303 return OPERATOR_CANCELLED;
3304 }
3305
3306 BKE_reportf(op->reports, RPT_INFO, "Removed %d double particle(s)", totremoved);
3307
3310
3311 return OPERATOR_FINISHED;
3312}
3313
3315{
3316 /* identifiers */
3317 ot->name = "Remove Doubles";
3318 ot->idname = "PARTICLE_OT_remove_doubles";
3319 ot->description = "Remove selected particles close enough of others";
3320
3321 /* api callbacks */
3323 ot->poll = PE_hair_poll;
3324
3325 /* flags */
3327
3328 /* properties */
3330 "threshold",
3331 0.0002f,
3332 0.0f,
3333 FLT_MAX,
3334 "Merge Distance",
3335 "Threshold distance within which particles are removed",
3336 0.00001f,
3337 0.1f);
3338}
3339
3341{
3342 Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
3343 Scene *scene = CTX_data_scene(C);
3344 ParticleEditSettings *pset = PE_settings(scene);
3346 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
3347 ParticleSystem *psys = edit->psys;
3348 POINT_P;
3349 KEY_K;
3350 HairKey *hkey;
3351 float weight;
3352 ParticleBrushData *brush = &pset->brush[pset->brushtype];
3353 float factor = RNA_float_get(op->ptr, "factor");
3354
3355 weight = brush->strength;
3356 edit = psys->edit;
3357
3359 ParticleData *pa = psys->particles + p;
3360
3362 hkey = pa->hair + k;
3363 hkey->weight = interpf(weight, hkey->weight, factor);
3364 }
3365 }
3366
3369
3370 return OPERATOR_FINISHED;
3371}
3372
3374{
3375 /* identifiers */
3376 ot->name = "Weight Set";
3377 ot->idname = "PARTICLE_OT_weight_set";
3378 ot->description = "Set the weight of selected keys";
3379
3380 /* api callbacks */
3382 ot->poll = PE_hair_poll;
3383
3384 /* flags */
3386
3388 "factor",
3389 1,
3390 0,
3391 1,
3392 "Factor",
3393 "Interpolation factor between current brush weight, and keys' weights",
3394 0,
3395 1);
3396}
3397
3400/* -------------------------------------------------------------------- */
3404static void brush_drawcursor(bContext *C, int x, int y, void * /*customdata*/)
3405{
3406 Scene *scene = CTX_data_scene(C);
3407 ParticleEditSettings *pset = PE_settings(scene);
3408 ParticleBrushData *brush;
3409
3411 return;
3412 }
3413
3414 brush = &pset->brush[pset->brushtype];
3415
3416 if (brush) {
3419
3420 immUniformColor4ub(255, 255, 255, 128);
3421
3422 GPU_line_smooth(true);
3424
3425 imm_draw_circle_wire_2d(pos, float(x), float(y), pe_brush_size_get(scene, brush), 40);
3426
3428 GPU_line_smooth(false);
3429
3431 }
3432}
3433
3434static void toggle_particle_cursor(Scene *scene, bool enable)
3435{
3436 ParticleEditSettings *pset = PE_settings(scene);
3437
3438 if (pset->paintcursor && !enable) {
3439 WM_paint_cursor_end(static_cast<wmPaintCursor *>(pset->paintcursor));
3440 pset->paintcursor = nullptr;
3441 }
3442 else if (enable) {
3445 }
3446}
3447
3450/* -------------------------------------------------------------------- */
3455
3457 {DEL_PARTICLE, "PARTICLE", 0, "Particle", ""},
3458 {DEL_KEY, "KEY", 0, "Key", ""},
3459 {0, nullptr, 0, nullptr, nullptr},
3460};
3461
3462static void set_delete_particle(PEData *data, int pa_index)
3463{
3464 PTCacheEdit *edit = data->edit;
3465
3466 edit->points[pa_index].flag |= PEP_TAG;
3467}
3468
3469static void set_delete_particle_key(PEData *data, int pa_index, int key_index, bool /*is_inside*/)
3470{
3471 PTCacheEdit *edit = data->edit;
3472
3473 edit->points[pa_index].keys[key_index].flag |= PEK_TAG;
3474}
3475
3477{
3478 PEData data;
3479 int type = RNA_enum_get(op->ptr, "type");
3480
3481 PE_set_data(C, &data);
3482
3483 if (type == DEL_KEY) {
3485 remove_tagged_keys(data.depsgraph, data.ob, data.edit->psys);
3486 recalc_lengths(data.edit);
3487 }
3488 else if (type == DEL_PARTICLE) {
3490 remove_tagged_particles(data.ob, data.edit->psys, pe_x_mirror(data.ob));
3491 recalc_lengths(data.edit);
3492 }
3493
3497
3498 return OPERATOR_FINISHED;
3499}
3500
3502{
3503 /* identifiers */
3504 ot->name = "Delete";
3505 ot->idname = "PARTICLE_OT_delete";
3506 ot->description = "Delete selected particles or keys";
3507
3508 /* api callbacks */
3509 ot->exec = delete_exec;
3511 ot->poll = PE_hair_poll;
3512
3513 /* flags */
3515
3516 /* properties */
3518 "type",
3521 "Type",
3522 "Delete a full particle or only keys");
3523}
3524
3527/* -------------------------------------------------------------------- */
3531static void PE_mirror_x(Depsgraph *depsgraph, Scene *scene, Object *ob, int tagged)
3532{
3533 Mesh *mesh = (Mesh *)(ob->data);
3534 ParticleSystemModifierData *psmd_eval;
3535 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
3536 ParticleSystem *psys = edit->psys;
3537 ParticleData *pa, *newpa, *new_pars;
3538 PTCacheEditPoint *newpoint, *new_points;
3539 POINT_P;
3540 KEY_K;
3541 HairKey *hkey;
3542 int *mirrorfaces = nullptr;
3543 int rotation, totpart, newtotpart;
3544
3545 if (psys->flag & PSYS_GLOBAL_HAIR) {
3546 return;
3547 }
3548
3549 psmd_eval = edit->psmd_eval;
3550 if (!psmd_eval->mesh_final) {
3551 return;
3552 }
3553
3554 const bool use_dm_final_indices = (psys->part->use_modifier_stack &&
3555 !psmd_eval->mesh_final->runtime->deformed_only);
3556
3557 /* NOTE: this is not nice to use tessfaces but hard to avoid since pa->num uses tessfaces */
3559
3560 /* NOTE: In case psys uses Mesh tessface indices, we mirror final Mesh itself, not orig mesh.
3561 * Avoids an (impossible) mesh -> orig -> mesh tessface indices conversion. */
3562 mirrorfaces = mesh_get_x_mirror_faces(
3563 ob, nullptr, use_dm_final_indices ? psmd_eval->mesh_final : nullptr);
3564
3565 if (!edit->mirror_cache) {
3566 PE_update_mirror_cache(ob, psys);
3567 }
3568
3569 totpart = psys->totpart;
3570 newtotpart = psys->totpart;
3572 pa = psys->particles + p;
3573
3574 if (!tagged) {
3575 if (point_is_selected(point)) {
3576 if (edit->mirror_cache[p] != -1) {
3577 /* already has a mirror, don't need to duplicate */
3578 PE_mirror_particle(ob, psmd_eval->mesh_final, psys, pa, nullptr);
3579 continue;
3580 }
3581 point->flag |= PEP_TAG;
3582 }
3583 }
3584
3585 if ((point->flag & PEP_TAG) && mirrorfaces[pa->num * 2] != -1) {
3586 newtotpart++;
3587 }
3588 }
3589
3590 if (newtotpart != psys->totpart) {
3591 const MFace *mtessface = use_dm_final_indices ?
3592 (const MFace *)CustomData_get_layer(
3593 &psmd_eval->mesh_final->fdata_legacy, CD_MFACE) :
3594 (const MFace *)CustomData_get_layer(&mesh->fdata_legacy,
3595 CD_MFACE);
3596
3597 /* allocate new arrays and copy existing */
3598 new_pars = static_cast<ParticleData *>(
3599 MEM_callocN(newtotpart * sizeof(ParticleData), "ParticleData new"));
3600 new_points = static_cast<PTCacheEditPoint *>(
3601 MEM_callocN(newtotpart * sizeof(PTCacheEditPoint), "PTCacheEditPoint new"));
3602
3603 if (psys->particles) {
3604 memcpy(new_pars, psys->particles, totpart * sizeof(ParticleData));
3605 MEM_freeN(psys->particles);
3606 }
3607 psys->particles = new_pars;
3608
3609 if (edit->points) {
3610 memcpy(new_points, edit->points, totpart * sizeof(PTCacheEditPoint));
3611 MEM_freeN(edit->points);
3612 }
3613 edit->points = new_points;
3614
3616
3617 edit->totpoint = psys->totpart = newtotpart;
3618
3619 /* create new elements */
3620 newpa = psys->particles + totpart;
3621 newpoint = edit->points + totpart;
3622
3623 for (p = 0, point = edit->points; p < totpart; p++, point++) {
3624 pa = psys->particles + p;
3625 const int pa_num = pa->num;
3626
3627 if (point->flag & PEP_HIDE) {
3628 continue;
3629 }
3630
3631 if (!(point->flag & PEP_TAG) || mirrorfaces[pa_num * 2] == -1) {
3632 continue;
3633 }
3634
3635 /* duplicate */
3636 *newpa = *pa;
3637 *newpoint = *point;
3638 if (pa->hair) {
3639 newpa->hair = static_cast<HairKey *>(MEM_dupallocN(pa->hair));
3640 }
3641 if (point->keys) {
3642 newpoint->keys = static_cast<PTCacheEditKey *>(MEM_dupallocN(point->keys));
3643 }
3644
3645 /* rotate weights according to vertex index rotation */
3646 rotation = mirrorfaces[pa_num * 2 + 1];
3647 newpa->fuv[0] = pa->fuv[2];
3648 newpa->fuv[1] = pa->fuv[1];
3649 newpa->fuv[2] = pa->fuv[0];
3650 newpa->fuv[3] = pa->fuv[3];
3651 while (rotation--) {
3652 if (mtessface[pa_num].v4) {
3653 SHIFT4(float, newpa->fuv[0], newpa->fuv[1], newpa->fuv[2], newpa->fuv[3]);
3654 }
3655 else {
3656 SHIFT3(float, newpa->fuv[0], newpa->fuv[1], newpa->fuv[2]);
3657 }
3658 }
3659
3660 /* assign face index */
3661 /* NOTE: mesh_get_x_mirror_faces generates -1 for non-found mirror,
3662 * same as DMCACHE_NOTFOUND. */
3663 newpa->num = mirrorfaces[pa_num * 2];
3664
3665 if (use_dm_final_indices) {
3667 }
3668 else {
3670 psmd_eval->mesh_final, psmd_eval->mesh_original, newpa->num, newpa->fuv, nullptr);
3671 }
3672
3673 /* update edit key pointers */
3674 key = newpoint->keys;
3675 for (k = 0, hkey = newpa->hair; k < newpa->totkey; k++, hkey++, key++) {
3676 key->co = hkey->co;
3677 key->time = &hkey->time;
3678 }
3679
3680 /* map key positions as mirror over x axis */
3681 PE_mirror_particle(ob, psmd_eval->mesh_final, psys, pa, newpa);
3682
3683 newpa++;
3684 newpoint++;
3685 }
3686 }
3687
3688 LOOP_POINTS {
3689 point->flag &= ~PEP_TAG;
3690 }
3691
3692 MEM_freeN(mirrorfaces);
3693}
3694
3695static int mirror_exec(bContext *C, wmOperator * /*op*/)
3696{
3697 Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
3698 Scene *scene = CTX_data_scene(C);
3700 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
3701
3702 PE_mirror_x(depsgraph, scene, ob, 0);
3703
3704 update_world_cos(ob, edit);
3705 psys_free_path_cache(nullptr, edit);
3706
3710
3711 return OPERATOR_FINISHED;
3712}
3713
3714static bool mirror_poll(bContext *C)
3715{
3716 if (!PE_hair_poll(C)) {
3717 return false;
3718 }
3719
3720 Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
3721 Scene *scene = CTX_data_scene(C);
3723 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
3724
3725 /* The operator only works for hairs emitted from faces. */
3726 return edit->psys->part->from == PART_FROM_FACE;
3727}
3728
3730{
3731 /* identifiers */
3732 ot->name = "Mirror";
3733 ot->idname = "PARTICLE_OT_mirror";
3734 ot->description = "Duplicate and mirror the selected particles along the local X axis";
3735
3736 /* api callbacks */
3737 ot->exec = mirror_exec;
3738 ot->poll = mirror_poll;
3739
3740 /* flags */
3742}
3743
3746/* -------------------------------------------------------------------- */
3750static void brush_comb(PEData *data,
3751 float[4][4] /*mat*/,
3752 float imat[4][4],
3753 int point_index,
3754 int key_index,
3755 PTCacheEditKey *key,
3756 float mouse_distance)
3757{
3758 ParticleEditSettings *pset = PE_settings(data->scene);
3759 float cvec[3], fac;
3760
3761 if (pset->flag & PE_LOCK_FIRST && key_index == 0) {
3762 return;
3763 }
3764
3765 fac = float(pow(double(1.0f - mouse_distance / data->rad), double(data->combfac)));
3766
3767 copy_v3_v3(cvec, data->dvec);
3768 mul_mat3_m4_v3(imat, cvec);
3769 mul_v3_fl(cvec, fac);
3770 add_v3_v3(key->co, cvec);
3771
3772 (data->edit->points + point_index)->flag |= PEP_EDIT_RECALC;
3773}
3774
3775static void brush_cut(PEData *data, int pa_index)
3776{
3777 PTCacheEdit *edit = data->edit;
3778 ARegion *region = data->vc.region;
3779 Object *ob = data->ob;
3780 ParticleEditSettings *pset = PE_settings(data->scene);
3781 ParticleCacheKey *key = edit->pathcache[pa_index];
3782 float rad2, cut_time = 1.0;
3783 float x0, x1, v0, v1, o0, o1, xo0, xo1, d, dv;
3784 int k, cut, keys = int(pow(2.0, double(pset->draw_step)));
3785 int screen_co[2];
3786
3787 BLI_assert(data->rng != nullptr);
3788 /* blunt scissors */
3789 if (BLI_rng_get_float(data->rng) > data->cutfac) {
3790 return;
3791 }
3792
3793 /* don't cut hidden */
3794 if (edit->points[pa_index].flag & PEP_HIDE) {
3795 return;
3796 }
3797
3798 if (ED_view3d_project_int_global(region, key->co, screen_co, V3D_PROJ_TEST_CLIP_NEAR) !=
3800 {
3801 return;
3802 }
3803
3804 rad2 = data->rad * data->rad;
3805
3806 cut = 0;
3807
3808 x0 = float(screen_co[0]);
3809 x1 = float(screen_co[1]);
3810
3811 o0 = float(data->mval[0]);
3812 o1 = float(data->mval[1]);
3813
3814 xo0 = x0 - o0;
3815 xo1 = x1 - o1;
3816
3817 /* check if root is inside circle */
3818 if (xo0 * xo0 + xo1 * xo1 < rad2 && key_test_depth(data, key->co, screen_co)) {
3819 cut_time = -1.0f;
3820 cut = 1;
3821 }
3822 else {
3823 /* calculate path time closest to root that was inside the circle */
3824 for (k = 1, key++; k <= keys; k++, key++) {
3825
3826 if ((ED_view3d_project_int_global(region, key->co, screen_co, V3D_PROJ_TEST_CLIP_NEAR) !=
3827 V3D_PROJ_RET_OK) ||
3828 key_test_depth(data, key->co, screen_co) == 0)
3829 {
3830 x0 = float(screen_co[0]);
3831 x1 = float(screen_co[1]);
3832
3833 xo0 = x0 - o0;
3834 xo1 = x1 - o1;
3835 continue;
3836 }
3837
3838 v0 = float(screen_co[0]) - x0;
3839 v1 = float(screen_co[1]) - x1;
3840
3841 dv = v0 * v0 + v1 * v1;
3842
3843 d = (v0 * xo1 - v1 * xo0);
3844
3845 d = dv * rad2 - d * d;
3846
3847 if (d > 0.0f) {
3848 d = sqrtf(d);
3849
3850 cut_time = -(v0 * xo0 + v1 * xo1 + d);
3851
3852 if (cut_time > 0.0f) {
3853 cut_time /= dv;
3854
3855 if (cut_time < 1.0f) {
3856 cut_time += float(k - 1);
3857 cut_time /= float(keys);
3858 cut = 1;
3859 break;
3860 }
3861 }
3862 }
3863
3864 x0 = float(screen_co[0]);
3865 x1 = float(screen_co[1]);
3866
3867 xo0 = x0 - o0;
3868 xo1 = x1 - o1;
3869 }
3870 }
3871
3872 if (cut) {
3873 if (cut_time < 0.0f) {
3874 edit->points[pa_index].flag |= PEP_TAG;
3875 }
3876 else {
3877 rekey_particle_to_time(data->context, data->scene, ob, pa_index, cut_time);
3878 edit->points[pa_index].flag |= PEP_EDIT_RECALC;
3879 }
3880 }
3881}
3882
3883static void brush_length(PEData *data, int point_index, float /*mouse_distance*/)
3884{
3885 PTCacheEdit *edit = data->edit;
3886 PTCacheEditPoint *point = edit->points + point_index;
3887 KEY_K;
3888 float dvec[3], pvec[3] = {0.0f, 0.0f, 0.0f};
3889
3890 LOOP_KEYS {
3891 if (k == 0) {
3892 copy_v3_v3(pvec, key->co);
3893 }
3894 else {
3895 sub_v3_v3v3(dvec, key->co, pvec);
3896 copy_v3_v3(pvec, key->co);
3897 mul_v3_fl(dvec, data->growfac);
3898 add_v3_v3v3(key->co, (key - 1)->co, dvec);
3899 }
3900 }
3901
3902 point->flag |= PEP_EDIT_RECALC;
3903}
3904
3905static void brush_puff(PEData *data, int point_index, float mouse_distance)
3906{
3907 PTCacheEdit *edit = data->edit;
3908 ParticleSystem *psys = edit->psys;
3909 PTCacheEditPoint *point = edit->points + point_index;
3910 KEY_K;
3911 float mat[4][4], imat[4][4];
3912
3913 float onor_prev[3]; /* previous normal (particle-space) */
3914 float ofs_prev[3]; /* accumulate offset for puff_volume (particle-space) */
3915 float co_root[3], no_root[3]; /* root location and normal (global-space) */
3916 float co_prev[3], co[3]; /* track key coords as we loop (global-space) */
3917 float fac = 0.0f, length_accum = 0.0f;
3918 bool puff_volume = false;
3919 bool changed = false;
3920
3921 zero_v3(ofs_prev);
3922
3923 {
3924 ParticleEditSettings *pset = PE_settings(data->scene);
3925 ParticleBrushData *brush = &pset->brush[pset->brushtype];
3926 puff_volume = (brush->flag & PE_BRUSH_DATA_PUFF_VOLUME) != 0;
3927 }
3928
3929 if (psys && !(psys->flag & PSYS_GLOBAL_HAIR)) {
3931 data->ob, data->mesh, psys->part->from, psys->particles + point_index, mat);
3932 invert_m4_m4(imat, mat);
3933 }
3934 else {
3935 unit_m4(mat);
3936 unit_m4(imat);
3937 }
3938
3939 LOOP_KEYS {
3940 float kco[3];
3941
3942 if (k == 0) {
3943 /* find root coordinate and normal on emitter */
3944 copy_v3_v3(co, key->co);
3945 mul_m4_v3(mat, co);
3946
3947 /* Use `kco` as the object space version of world-space `co`,
3948 * `ob->world_to_object` is set before calling. */
3949 mul_v3_m4v3(kco, data->ob->world_to_object().ptr(), co);
3950
3951 point_index = BLI_kdtree_3d_find_nearest(edit->emitter_field, kco, nullptr);
3952 if (point_index == -1) {
3953 return;
3954 }
3955
3956 copy_v3_v3(co_root, co);
3957 copy_v3_v3(no_root, &edit->emitter_cosnos[point_index * 6 + 3]);
3958 mul_mat3_m4_v3(data->ob->object_to_world().ptr(), no_root); /* normal into global-space */
3959 normalize_v3(no_root);
3960
3961 if (puff_volume) {
3962 copy_v3_v3(onor_prev, no_root);
3963 mul_mat3_m4_v3(imat, onor_prev); /* global-space into particle space */
3964 normalize_v3(onor_prev);
3965 }
3966
3967 fac = float(pow(double(1.0f - mouse_distance / data->rad), double(data->pufffac)));
3968 fac *= 0.025f;
3969 if (data->invert) {
3970 fac = -fac;
3971 }
3972 }
3973 else {
3974 /* Compute position as if hair was standing up straight. */
3975 float length;
3976 copy_v3_v3(co_prev, co);
3977 copy_v3_v3(co, key->co);
3978 mul_m4_v3(mat, co);
3979 length = len_v3v3(co_prev, co);
3980 length_accum += length;
3981
3982 if ((data->select == 0 || (key->flag & PEK_SELECT)) && !(key->flag & PEK_HIDE)) {
3983 float dco[3]; /* delta temp var */
3984
3985 madd_v3_v3v3fl(kco, co_root, no_root, length_accum);
3986
3987 /* blend between the current and straight position */
3988 sub_v3_v3v3(dco, kco, co);
3989 madd_v3_v3fl(co, dco, fac);
3990 /* keep the same distance from the root or we get glitches #35406. */
3991 dist_ensure_v3_v3fl(co, co_root, length_accum);
3992
3993 /* Re-use dco to compare before and after translation and add to the offset. */
3994 copy_v3_v3(dco, key->co);
3995
3996 mul_v3_m4v3(key->co, imat, co);
3997
3998 if (puff_volume) {
3999 /* accumulate the total distance moved to apply to unselected
4000 * keys that come after */
4001 sub_v3_v3v3(ofs_prev, key->co, dco);
4002 }
4003 changed = true;
4004 }
4005 else {
4006
4007 if (puff_volume) {
4008#if 0
4009 /* this is simple but looks bad, adds annoying kinks */
4010 add_v3_v3(key->co, ofs);
4011#else
4012 /* Translate (not rotate) the rest of the hair if its not selected. */
4013 {
4014/* NOLINTNEXTLINE: readability-redundant-preprocessor */
4015# if 0 /* Kind of works but looks worse than what's below. */
4016
4017 /* Move the unselected point on a vector based on the
4018 * hair direction and the offset */
4019 float c1[3], c2[3];
4020 sub_v3_v3v3(dco, lastco, co);
4021 mul_mat3_m4_v3(imat, dco); /* into particle space */
4022
4023 /* move the point along a vector perpendicular to the
4024 * hairs direction, reduces odd kinks, */
4025 cross_v3_v3v3(c1, ofs, dco);
4026 cross_v3_v3v3(c2, c1, dco);
4027 normalize_v3(c2);
4028 mul_v3_fl(c2, len_v3(ofs));
4029 add_v3_v3(key->co, c2);
4030# else
4031 /* Move the unselected point on a vector based on the
4032 * the normal of the closest geometry */
4033 float oco[3], onor[3];
4034 copy_v3_v3(oco, key->co);
4035 mul_m4_v3(mat, oco);
4036
4037 /* Use `kco` as the object space version of world-space `co`,
4038 * `ob->world_to_object` is set before calling. */
4039 mul_v3_m4v3(kco, data->ob->world_to_object().ptr(), oco);
4040
4041 point_index = BLI_kdtree_3d_find_nearest(edit->emitter_field, kco, nullptr);
4042 if (point_index != -1) {
4043 copy_v3_v3(onor, &edit->emitter_cosnos[point_index * 6 + 3]);
4044 mul_mat3_m4_v3(data->ob->object_to_world().ptr(),
4045 onor); /* Normal into world-space. */
4046 mul_mat3_m4_v3(imat, onor); /* World-space into particle-space. */
4047 normalize_v3(onor);
4048 }
4049 else {
4050 copy_v3_v3(onor, onor_prev);
4051 }
4052
4053 if (!is_zero_v3(ofs_prev)) {
4054 mul_v3_fl(onor, len_v3(ofs_prev));
4055
4056 add_v3_v3(key->co, onor);
4057 }
4058
4059 copy_v3_v3(onor_prev, onor);
4060# endif
4061 }
4062#endif
4063 }
4064 }
4065 }
4066 }
4067
4068 if (changed) {
4069 point->flag |= PEP_EDIT_RECALC;
4070 }
4071}
4072
4074 float[4][4] /*mat*/,
4075 float[4][4] /*imat*/,
4076 int point_index,
4077 int key_index,
4078 PTCacheEditKey * /*key*/,
4079 float /*mouse_distance*/)
4080{
4081 /* roots have full weight always */
4082 if (key_index) {
4083 PTCacheEdit *edit = data->edit;
4084 ParticleSystem *psys = edit->psys;
4085
4086 ParticleData *pa = psys->particles + point_index;
4087 pa->hair[key_index].weight = data->weightfac;
4088
4089 (data->edit->points + point_index)->flag |= PEP_EDIT_RECALC;
4090 }
4091}
4092
4093static void brush_smooth_get(PEData *data,
4094 float mat[4][4],
4095 float[4][4] /*imat*/,
4096 int /*point_index*/,
4097 int key_index,
4098 PTCacheEditKey *key,
4099 float /*mouse_distance*/)
4100{
4101 if (key_index) {
4102 float dvec[3];
4103
4104 sub_v3_v3v3(dvec, key->co, (key - 1)->co);
4105 mul_mat3_m4_v3(mat, dvec);
4106 add_v3_v3(data->vec, dvec);
4107 data->tot++;
4108 }
4109}
4110
4111static void brush_smooth_do(PEData *data,
4112 float[4][4] /*mat*/,
4113 float imat[4][4],
4114 int point_index,
4115 int key_index,
4116 PTCacheEditKey *key,
4117 float /*mouse_distance*/)
4118{
4119 float vec[3], dvec[3];
4120
4121 if (key_index) {
4122 copy_v3_v3(vec, data->vec);
4123 mul_mat3_m4_v3(imat, vec);
4124
4125 sub_v3_v3v3(dvec, key->co, (key - 1)->co);
4126
4127 sub_v3_v3v3(dvec, vec, dvec);
4128 mul_v3_fl(dvec, data->smoothfac);
4129
4130 add_v3_v3(key->co, dvec);
4131 }
4132
4133 (data->edit->points + point_index)->flag |= PEP_EDIT_RECALC;
4134}
4135
4136/* convert from triangle barycentric weights to quad mean value weights */
4138 const float v1[3], const float v2[3], const float v3[3], const float v4[3], float w[4])
4139{
4140 float co[3], vert[4][3];
4141
4142 copy_v3_v3(vert[0], v1);
4143 copy_v3_v3(vert[1], v2);
4144 copy_v3_v3(vert[2], v3);
4145 copy_v3_v3(vert[3], v4);
4146
4147 co[0] = v1[0] * w[0] + v2[0] * w[1] + v3[0] * w[2] + v4[0] * w[3];
4148 co[1] = v1[1] * w[0] + v2[1] * w[1] + v3[1] * w[2] + v4[1] * w[3];
4149 co[2] = v1[2] * w[0] + v2[2] * w[1] + v3[2] * w[2] + v4[2] * w[3];
4150
4151 interp_weights_poly_v3(w, vert, 4, co);
4152}
4153
4156 Scene * /*scene*/,
4157 Object *ob,
4158 Mesh *mesh,
4159 float *vert_cos,
4160 const float co1[3],
4161 const float co2[3],
4162 float *min_d,
4163 int *min_face,
4164 float *min_w,
4165 float *face_minmax,
4166 float *pa_minmax,
4167 float radius,
4168 float *ipoint)
4169{
4170 const MFace *mface = nullptr;
4171 int i, totface, intersect = 0;
4172 float cur_d;
4173 blender::float2 cur_uv;
4174 blender::float3 v1, v2, v3, v4, min, max, p_min, p_max;
4175 float cur_ipoint[3];
4176
4177 if (mesh == nullptr) {
4178 psys_disable_all(ob);
4179
4181 mesh = (Mesh *)BKE_object_get_evaluated_mesh(ob_eval);
4182 if (mesh == nullptr) {
4183 return 0;
4184 }
4185
4186 psys_enable_all(ob);
4187
4188 if (mesh == nullptr) {
4189 return 0;
4190 }
4191 }
4192
4193 /* BMESH_ONLY, deform dm may not have tessface */
4195
4196 if (pa_minmax == nullptr) {
4197 INIT_MINMAX(p_min, p_max);
4198 minmax_v3v3_v3(p_min, p_max, co1);
4199 minmax_v3v3_v3(p_min, p_max, co2);
4200 }
4201 else {
4202 copy_v3_v3(p_min, pa_minmax);
4203 copy_v3_v3(p_max, pa_minmax + 3);
4204 }
4205
4206 totface = mesh->totface_legacy;
4207 mface = (const MFace *)CustomData_get_layer(&mesh->fdata_legacy, CD_MFACE);
4208 blender::MutableSpan<blender::float3> positions = mesh->vert_positions_for_write();
4209
4210 /* lets intersect the faces */
4211 for (i = 0; i < totface; i++, mface++) {
4212 if (vert_cos) {
4213 copy_v3_v3(v1, vert_cos + 3 * mface->v1);
4214 copy_v3_v3(v2, vert_cos + 3 * mface->v2);
4215 copy_v3_v3(v3, vert_cos + 3 * mface->v3);
4216 if (mface->v4) {
4217 copy_v3_v3(v4, vert_cos + 3 * mface->v4);
4218 }
4219 }
4220 else {
4221 copy_v3_v3(v1, positions[mface->v1]);
4222 copy_v3_v3(v2, positions[mface->v2]);
4223 copy_v3_v3(v3, positions[mface->v3]);
4224 if (mface->v4) {
4225 copy_v3_v3(v4, positions[mface->v4]);
4226 }
4227 }
4228
4229 if (face_minmax == nullptr) {
4230 INIT_MINMAX(min, max);
4234 if (mface->v4) {
4236 }
4237 if (isect_aabb_aabb_v3(min, max, p_min, p_max) == 0) {
4238 continue;
4239 }
4240 }
4241 else {
4242 copy_v3_v3(min, face_minmax + 6 * i);
4243 copy_v3_v3(max, face_minmax + 6 * i + 3);
4244 if (isect_aabb_aabb_v3(min, max, p_min, p_max) == 0) {
4245 continue;
4246 }
4247 }
4248
4249 if (radius > 0.0f) {
4250 if (isect_sweeping_sphere_tri_v3(co1, co2, radius, v2, v3, v1, &cur_d, cur_ipoint)) {
4251 if (cur_d < *min_d) {
4252 *min_d = cur_d;
4253 copy_v3_v3(ipoint, cur_ipoint);
4254 *min_face = i;
4255 intersect = 1;
4256 }
4257 }
4258 if (mface->v4) {
4259 if (isect_sweeping_sphere_tri_v3(co1, co2, radius, v4, v1, v3, &cur_d, cur_ipoint)) {
4260 if (cur_d < *min_d) {
4261 *min_d = cur_d;
4262 copy_v3_v3(ipoint, cur_ipoint);
4263 *min_face = i;
4264 intersect = 1;
4265 }
4266 }
4267 }
4268 }
4269 else {
4270 if (isect_line_segment_tri_v3(co1, co2, v1, v2, v3, &cur_d, cur_uv)) {
4271 if (cur_d < *min_d) {
4272 *min_d = cur_d;
4273 min_w[0] = 1.0f - cur_uv[0] - cur_uv[1];
4274 min_w[1] = cur_uv[0];
4275 min_w[2] = cur_uv[1];
4276 min_w[3] = 0.0f;
4277 if (mface->v4) {
4278 intersect_dm_quad_weights(v1, v2, v3, v4, min_w);
4279 }
4280 *min_face = i;
4281 intersect = 1;
4282 }
4283 }
4284 if (mface->v4) {
4285 if (isect_line_segment_tri_v3(co1, co2, v1, v3, v4, &cur_d, cur_uv)) {
4286 if (cur_d < *min_d) {
4287 *min_d = cur_d;
4288 min_w[0] = 1.0f - cur_uv[0] - cur_uv[1];
4289 min_w[1] = 0.0f;
4290 min_w[2] = cur_uv[0];
4291 min_w[3] = cur_uv[1];
4292 intersect_dm_quad_weights(v1, v2, v3, v4, min_w);
4293 *min_face = i;
4294 intersect = 1;
4295 }
4296 }
4297 }
4298 }
4299 }
4300 return intersect;
4301}
4302
4314
4319
4320static void brush_add_count_iter(void *__restrict iter_data_v,
4321 const int iter,
4322 const TaskParallelTLS *__restrict tls_v)
4323{
4324 BrushAddCountIterData *iter_data = (BrushAddCountIterData *)iter_data_v;
4325 Depsgraph *depsgraph = iter_data->depsgraph;
4326 PEData *data = iter_data->data;
4327 PTCacheEdit *edit = data->edit;
4328 ParticleSystem *psys = edit->psys;
4329 ParticleSystemModifierData *psmd_eval = edit->psmd_eval;
4330 ParticleData *add_pars = iter_data->add_pars;
4331 BrushAddCountIterTLSData *tls = static_cast<BrushAddCountIterTLSData *>(tls_v->userdata_chunk);
4332 const int number = iter_data->number;
4333 const short size = iter_data->size;
4334 const int size2 = size * size;
4335 float dmx, dmy;
4336 if (number > 1) {
4337 dmx = size;
4338 dmy = size;
4339 if (tls->rng == nullptr) {
4340 tls->rng = BLI_rng_new_srandom(psys->seed + data->mval[0] + data->mval[1] +
4342 }
4343 /* rejection sampling to get points in circle */
4344 while (dmx * dmx + dmy * dmy > size2) {
4345 dmx = (2.0f * BLI_rng_get_float(tls->rng) - 1.0f) * size;
4346 dmy = (2.0f * BLI_rng_get_float(tls->rng) - 1.0f) * size;
4347 }
4348 }
4349 else {
4350 dmx = 0.0f;
4351 dmy = 0.0f;
4352 }
4353
4354 float mco[2];
4355 mco[0] = data->mval[0] + dmx;
4356 mco[1] = data->mval[1] + dmy;
4357
4358 float co1[3], co2[3];
4359 ED_view3d_win_to_segment_clipped(depsgraph, data->vc.region, data->vc.v3d, mco, co1, co2, true);
4360
4361 mul_m4_v3(iter_data->imat, co1);
4362 mul_m4_v3(iter_data->imat, co2);
4363 float min_d = 2.0;
4364
4365 /* warning, returns the derived mesh face */
4366 BLI_assert(iter_data->mesh != nullptr);
4368 iter_data->scene,
4369 iter_data->object,
4370 iter_data->mesh,
4371 nullptr,
4372 co1,
4373 co2,
4374 &min_d,
4375 &add_pars[iter].num_dmcache,
4376 add_pars[iter].fuv,
4377 nullptr,
4378 nullptr,
4379 0,
4380 nullptr))
4381 {
4382 if (psys->part->use_modifier_stack && !psmd_eval->mesh_final->runtime->deformed_only) {
4383 add_pars[iter].num = add_pars[iter].num_dmcache;
4384 add_pars[iter].num_dmcache = DMCACHE_ISCHILD;
4385 }
4386 else if (iter_data->mesh == psmd_eval->mesh_original) {
4387 /* Final DM is not same topology as orig mesh,
4388 * we have to map num_dmcache to real final dm. */
4389 add_pars[iter].num = add_pars[iter].num_dmcache;
4390 add_pars[iter].num_dmcache = psys_particle_dm_face_lookup(psmd_eval->mesh_final,
4391 psmd_eval->mesh_original,
4392 add_pars[iter].num,
4393 add_pars[iter].fuv,
4394 nullptr);
4395 }
4396 else {
4397 add_pars[iter].num = add_pars[iter].num_dmcache;
4398 }
4399 if (add_pars[iter].num != DMCACHE_NOTFOUND) {
4400 tls->num_added++;
4401 }
4402 }
4403}
4404
4405static void brush_add_count_iter_reduce(const void *__restrict /*userdata*/,
4406 void *__restrict join_v,
4407 void *__restrict chunk_v)
4408{
4411 join->num_added += tls->num_added;
4412}
4413
4414static void brush_add_count_iter_free(const void *__restrict /*userdata_v*/,
4415 void *__restrict chunk_v)
4416{
4418 if (tls->rng != nullptr) {
4419 BLI_rng_free(tls->rng);
4420 }
4421}
4422
4423static int brush_add(const bContext *C, PEData *data, short number)
4424{
4425 Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
4426 Scene *scene = data->scene;
4427 Object *ob = data->ob;
4428 Mesh *mesh;
4429 PTCacheEdit *edit = data->edit;
4430 ParticleSystem *psys = edit->psys;
4431 ParticleData *add_pars;
4432 ParticleSystemModifierData *psmd_eval = edit->psmd_eval;
4433 ParticleSimulationData sim = {nullptr};
4434 ParticleEditSettings *pset = PE_settings(scene);
4435 int i, k, n = 0, totpart = psys->totpart;
4436 float co1[3], imat[4][4];
4437 float framestep, timestep;
4438 short size = pset->brush[PE_BRUSH_ADD].size;
4439 RNG *rng;
4440
4441 invert_m4_m4(imat, ob->object_to_world().ptr());
4442
4443 if (psys->flag & PSYS_GLOBAL_HAIR) {
4444 return 0;
4445 }
4446
4447 add_pars = static_cast<ParticleData *>(
4448 MEM_callocN(number * sizeof(ParticleData), "ParticleData add"));
4449
4450 rng = BLI_rng_new_srandom(psys->seed + data->mval[0] + data->mval[1]);
4451
4452 sim.depsgraph = depsgraph;
4453 sim.scene = scene;
4454 sim.ob = ob;
4455 sim.psys = psys;
4456 sim.psmd = psmd_eval;
4457
4458 timestep = psys_get_timestep(&sim);
4459
4460 if (psys->part->use_modifier_stack || psmd_eval->mesh_final->runtime->deformed_only) {
4461 mesh = psmd_eval->mesh_final;
4462 }
4463 else {
4464 mesh = psmd_eval->mesh_original;
4465 }
4466 BLI_assert(mesh);
4467
4468 /* Calculate positions of new particles to add, based on brush intersection
4469 * with object. New particle data is assigned to a corresponding to check
4470 * index element of add_pars array. This means, that add_pars is a sparse
4471 * array.
4472 */
4473 BrushAddCountIterData iter_data;
4474 iter_data.depsgraph = depsgraph;
4475 iter_data.scene = scene;
4476 iter_data.object = ob;
4477 iter_data.mesh = mesh;
4478 iter_data.data = data;
4479 iter_data.number = number;
4480 iter_data.size = size;
4481 iter_data.add_pars = add_pars;
4482 copy_m4_m4(iter_data.imat, imat);
4483
4484 BrushAddCountIterTLSData tls = {nullptr};
4485
4486 TaskParallelSettings settings;
4488 settings.userdata_chunk = &tls;
4489 settings.userdata_chunk_size = sizeof(BrushAddCountIterTLSData);
4490 settings.func_reduce = brush_add_count_iter_reduce;
4491 settings.func_free = brush_add_count_iter_free;
4492 BLI_task_parallel_range(0, number, &iter_data, brush_add_count_iter, &settings);
4493
4494 /* Convert add_parse to a dense array, where all new particles are in the
4495 * beginning of the array.
4496 */
4497 n = tls.num_added;
4498 for (int current_iter = 0, new_index = 0; current_iter < number; current_iter++) {
4499 if (add_pars[current_iter].num == DMCACHE_NOTFOUND) {
4500 continue;
4501 }
4502 if (new_index != current_iter) {
4503 new_index++;
4504 continue;
4505 }
4506 memcpy(add_pars + new_index, add_pars + current_iter, sizeof(ParticleData));
4507 new_index++;
4508 }
4509
4510 /* TODO(sergey): Consider multi-threading this part as well. */
4511 if (n) {
4512 int newtotpart = totpart + n;
4513 float hairmat[4][4], cur_co[3];
4514 KDTree_3d *tree = nullptr;
4515 ParticleData *pa, *new_pars = static_cast<ParticleData *>(
4516 MEM_callocN(newtotpart * sizeof(ParticleData), "ParticleData new"));
4518 *new_points = static_cast<PTCacheEditPoint *>(
4519 MEM_callocN(newtotpart * sizeof(PTCacheEditPoint), "PTCacheEditPoint array new"));
4520 PTCacheEditKey *key;
4521 HairKey *hkey;
4522
4523 /* save existing elements */
4524 memcpy(new_pars, psys->particles, totpart * sizeof(ParticleData));
4525 memcpy(new_points, edit->points, totpart * sizeof(PTCacheEditPoint));
4526
4527 /* change old arrays to new ones */
4528 if (psys->particles) {
4529 MEM_freeN(psys->particles);
4530 }
4531 psys->particles = new_pars;
4532
4533 if (edit->points) {
4534 MEM_freeN(edit->points);
4535 }
4536 edit->points = new_points;
4537
4539
4540 /* create tree for interpolation */
4541 if (pset->flag & PE_INTERPOLATE_ADDED && psys->totpart) {
4542 tree = BLI_kdtree_3d_new(psys->totpart);
4543
4544 for (i = 0, pa = psys->particles; i < totpart; i++, pa++) {
4546 psys->part->from,
4547 pa->num,
4548 pa->num_dmcache,
4549 pa->fuv,
4550 pa->foffset,
4551 cur_co,
4552 nullptr,
4553 nullptr,
4554 nullptr,
4555 nullptr);
4556 BLI_kdtree_3d_insert(tree, i, cur_co);
4557 }
4558
4559 BLI_kdtree_3d_balance(tree);
4560 }
4561
4562 edit->totpoint = psys->totpart = newtotpart;
4563
4564 /* create new elements */
4565 pa = psys->particles + totpart;
4566 point = edit->points + totpart;
4567
4568 for (i = totpart; i < newtotpart; i++, pa++, point++) {
4569 memcpy(pa, add_pars + i - totpart, sizeof(ParticleData));
4570 pa->hair = static_cast<HairKey *>(
4571 MEM_callocN(pset->totaddkey * sizeof(HairKey), "BakeKey key add"));
4572 key = point->keys = static_cast<PTCacheEditKey *>(
4573 MEM_callocN(pset->totaddkey * sizeof(PTCacheEditKey), "PTCacheEditKey add"));
4574 point->totkey = pa->totkey = pset->totaddkey;
4575
4576 for (k = 0, hkey = pa->hair; k < pa->totkey; k++, hkey++, key++) {
4577 key->co = hkey->co;
4578 key->time = &hkey->time;
4579
4580 if (!(psys->flag & PSYS_GLOBAL_HAIR)) {
4581 key->flag |= PEK_USE_WCO;
4582 }
4583 }
4584
4585 pa->size = 1.0f;
4586 init_particle(&sim, pa);
4587 reset_particle(&sim, pa, 0.0, 1.0);
4588 point->flag |= PEP_EDIT_RECALC;
4589 if (pe_x_mirror(ob)) {
4590 point->flag |= PEP_TAG; /* signal for duplicate */
4591 }
4592
4593 framestep = pa->lifetime / float(pset->totaddkey - 1);
4594
4595 if (tree) {
4596 ParticleData *ppa;
4597 HairKey *thkey;
4598 ParticleKey key3[3];
4599 KDTreeNearest_3d ptn[3];
4600 int w, maxw;
4601 float maxd, totw = 0.0, weight[3];
4602
4604 psys->part->from,
4605 pa->num,
4606 pa->num_dmcache,
4607 pa->fuv,
4608 pa->foffset,
4609 co1,
4610 nullptr,
4611 nullptr,
4612 nullptr,
4613 nullptr);
4614 maxw = BLI_kdtree_3d_find_nearest_n(tree, co1, ptn, 3);
4615
4616 maxd = ptn[maxw - 1].dist;
4617
4618 for (w = 0; w < maxw; w++) {
4619 weight[w] = float(pow(2.0, double(-6.0f * ptn[w].dist / maxd)));
4620 totw += weight[w];
4621 }
4622 for (; w < 3; w++) {
4623 weight[w] = 0.0f;
4624 }
4625
4626 if (totw > 0.0f) {
4627 for (w = 0; w < maxw; w++) {
4628 weight[w] /= totw;
4629 }
4630 }
4631 else {
4632 for (w = 0; w < maxw; w++) {
4633 weight[w] = 1.0f / maxw;
4634 }
4635 }
4636
4637 ppa = psys->particles + ptn[0].index;
4638
4639 for (k = 0; k < pset->totaddkey; k++) {
4640 thkey = (HairKey *)pa->hair + k;
4641 thkey->time = pa->time + k * framestep;
4642
4643 key3[0].time = thkey->time / 100.0f;
4644 psys_get_particle_on_path(&sim, ptn[0].index, key3, false);
4645 mul_v3_fl(key3[0].co, weight[0]);
4646
4647 /* TODO: interpolating the weight would be nicer */
4648 thkey->weight = (ppa->hair + std::min(k, ppa->totkey - 1))->weight;
4649
4650 if (maxw > 1) {
4651 key3[1].time = key3[0].time;
4652 psys_get_particle_on_path(&sim, ptn[1].index, &key3[1], false);
4653 mul_v3_fl(key3[1].co, weight[1]);
4654 add_v3_v3(key3[0].co, key3[1].co);
4655
4656 if (maxw > 2) {
4657 key3[2].time = key3[0].time;
4658 psys_get_particle_on_path(&sim, ptn[2].index, &key3[2], false);
4659 mul_v3_fl(key3[2].co, weight[2]);
4660 add_v3_v3(key3[0].co, key3[2].co);
4661 }
4662 }
4663
4664 if (k == 0) {
4665 sub_v3_v3v3(co1, pa->state.co, key3[0].co);
4666 }
4667
4668 add_v3_v3v3(thkey->co, key3[0].co, co1);
4669
4670 thkey->time = key3[0].time;
4671 }
4672 }
4673 else {
4674 for (k = 0, hkey = pa->hair; k < pset->totaddkey; k++, hkey++) {
4675 madd_v3_v3v3fl(hkey->co, pa->state.co, pa->state.vel, k * framestep * timestep);
4676 hkey->time += k * framestep;
4677 hkey->weight = 1.0f - float(k) / float(pset->totaddkey - 1);
4678 }
4679 }
4680 for (k = 0, hkey = pa->hair; k < pset->totaddkey; k++, hkey++) {
4681 psys_mat_hair_to_global(ob, psmd_eval->mesh_final, psys->part->from, pa, hairmat);
4682 invert_m4_m4(imat, hairmat);
4683 mul_m4_v3(imat, hkey->co);
4684 }
4685 }
4686
4687 if (tree) {
4688 BLI_kdtree_3d_free(tree);
4689 }
4690 }
4691
4692 MEM_freeN(add_pars);
4693
4694 BLI_rng_free(rng);
4695
4696 return n;
4697}
4698
4701/* -------------------------------------------------------------------- */
4718
4720{
4721 Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
4722 Scene *scene = CTX_data_scene(C);
4723 ViewLayer *view_layer = CTX_data_view_layer(C);
4725 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
4726 ARegion *region = CTX_wm_region(C);
4727 BrushEdit *bedit;
4729
4730 /* set the 'distance factor' for grabbing (used in comb etc) */
4731 INIT_MINMAX(min, max);
4732 PE_minmax(depsgraph, scene, view_layer, min, max);
4733 mid_v3_v3v3(min, min, max);
4734
4735 bedit = static_cast<BrushEdit *>(MEM_callocN(sizeof(BrushEdit), "BrushEdit"));
4736 bedit->first = 1;
4737 op->customdata = bedit;
4738
4739 bedit->scene = scene;
4740 bedit->view_layer = view_layer;
4741 bedit->ob = ob;
4742 bedit->edit = edit;
4743
4744 bedit->zfac = ED_view3d_calc_zfac(static_cast<const RegionView3D *>(region->regiondata), min);
4745
4746 /* cache view depths and settings for re-use */
4747 PE_set_view3d_data(C, &bedit->data);
4749
4750 return 1;
4751}
4752
4753static void brush_edit_apply(bContext *C, wmOperator *op, PointerRNA *itemptr)
4754{
4755 BrushEdit *bedit = static_cast<BrushEdit *>(op->customdata);
4756 Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
4757 Scene *scene = bedit->scene;
4758 Object *ob = bedit->ob;
4759 PTCacheEdit *edit = bedit->edit;
4760 ParticleEditSettings *pset = PE_settings(scene);
4761 ParticleSystemModifierData *psmd_eval = edit->psmd_eval;
4762 ParticleBrushData *brush = &pset->brush[pset->brushtype];
4763 ARegion *region = CTX_wm_region(C);
4764 float vec[3], mousef[2];
4765 int mval[2];
4766 int flip, mouse[2], removed = 0, added = 0, selected = 0, tot_steps = 1, step = 1;
4767 float dx, dy, dmax;
4768 int lock_root = pset->flag & PE_LOCK_FIRST;
4769
4770 if (!PE_start_edit(edit)) {
4771 return;
4772 }
4773
4774 RNA_float_get_array(itemptr, "mouse", mousef);
4775 mouse[0] = mousef[0];
4776 mouse[1] = mousef[1];
4777 flip = RNA_boolean_get(itemptr, "pen_flip");
4778
4779 if (bedit->first) {
4780 bedit->lastmouse[0] = mouse[0];
4781 bedit->lastmouse[1] = mouse[1];
4782 }
4783
4784 dx = mouse[0] - bedit->lastmouse[0];
4785 dy = mouse[1] - bedit->lastmouse[1];
4786
4787 mval[0] = mouse[0];
4788 mval[1] = mouse[1];
4789
4790 /* Disable locking temporarily for disconnected hair. */
4791 if (edit->psys && edit->psys->flag & PSYS_GLOBAL_HAIR) {
4792 pset->flag &= ~PE_LOCK_FIRST;
4793 }
4794
4795 if (((pset->brushtype == PE_BRUSH_ADD) ?
4796 (sqrtf(dx * dx + dy * dy) > pset->brush[PE_BRUSH_ADD].step) :
4797 (dx != 0 || dy != 0)) ||
4798 bedit->first)
4799 {
4800 PEData data = bedit->data;
4801 data.context = C; /* TODO(mai): why isn't this set in bedit->data? */
4802
4804 selected = short(count_selected_keys(scene, edit));
4805
4806 dmax = max_ff(fabsf(dx), fabsf(dy));
4807 tot_steps = dmax / (0.2f * pe_brush_size_get(scene, brush)) + 1;
4808
4809 dx /= float(tot_steps);
4810 dy /= float(tot_steps);
4811
4812 for (step = 1; step <= tot_steps; step++) {
4813 mval[0] = bedit->lastmouse[0] + step * dx;
4814 mval[1] = bedit->lastmouse[1] + step * dy;
4815
4816 switch (pset->brushtype) {
4817 case PE_BRUSH_COMB: {
4818 const float xy_delta[2] = {dx, dy};
4819 data.mval = mval;
4820 data.rad = pe_brush_size_get(scene, brush);
4821
4822 data.combfac = (brush->strength - 0.5f) * 2.0f;
4823 if (data.combfac < 0.0f) {
4824 data.combfac = 1.0f - 9.0f * data.combfac;
4825 }
4826 else {
4827 data.combfac = 1.0f - data.combfac;
4828 }
4829
4830 invert_m4_m4(ob->runtime->world_to_object.ptr(), ob->object_to_world().ptr());
4831
4832 ED_view3d_win_to_delta(region, xy_delta, bedit->zfac, vec);
4833 data.dvec = vec;
4834
4835 foreach_mouse_hit_key(&data, brush_comb, selected);
4836 break;
4837 }
4838 case PE_BRUSH_CUT: {
4839 if (edit->psys && edit->pathcache) {
4840 data.mval = mval;
4841 data.rad = pe_brush_size_get(scene, brush);
4842 data.cutfac = brush->strength;
4843
4844 if (selected) {
4846 }
4847 else {
4848 foreach_point(&data, brush_cut);
4849 }
4850
4851 removed = remove_tagged_particles(ob, edit->psys, pe_x_mirror(ob));
4852 if (pset->flag & PE_KEEP_LENGTHS) {
4853 recalc_lengths(edit);
4854 }
4855 }
4856 else {
4857 removed = 0;
4858 }
4859
4860 break;
4861 }
4862 case PE_BRUSH_LENGTH: {
4863 data.mval = mval;
4864
4865 data.rad = pe_brush_size_get(scene, brush);
4866 data.growfac = brush->strength / 50.0f;
4867
4868 if (brush->invert ^ flip) {
4869 data.growfac = 1.0f - data.growfac;
4870 }
4871 else {
4872 data.growfac = 1.0f + data.growfac;
4873 }
4874
4875 foreach_mouse_hit_point(&data, brush_length, selected);
4876
4877 if (pset->flag & PE_KEEP_LENGTHS) {
4878 recalc_lengths(edit);
4879 }
4880 break;
4881 }
4882 case PE_BRUSH_PUFF: {
4883 if (edit->psys) {
4884 data.mesh = psmd_eval->mesh_final;
4885 data.mval = mval;
4886 data.rad = pe_brush_size_get(scene, brush);
4887 data.select = selected;
4888
4889 data.pufffac = (brush->strength - 0.5f) * 2.0f;
4890 if (data.pufffac < 0.0f) {
4891 data.pufffac = 1.0f - 9.0f * data.pufffac;
4892 }
4893 else {
4894 data.pufffac = 1.0f - data.pufffac;
4895 }
4896
4897 data.invert = (brush->invert ^ flip);
4898 invert_m4_m4(ob->runtime->world_to_object.ptr(), ob->object_to_world().ptr());
4899
4900 foreach_mouse_hit_point(&data, brush_puff, selected);
4901 }
4902 break;
4903 }
4904 case PE_BRUSH_ADD: {
4905 if (edit->psys && edit->psys->part->from == PART_FROM_FACE) {
4906 data.mval = mval;
4907
4908 added = brush_add(C, &data, brush->count);
4909
4910 if (pset->flag & PE_KEEP_LENGTHS) {
4911 recalc_lengths(edit);
4912 }
4913 }
4914 else {
4915 added = 0;
4916 }
4917 break;
4918 }
4919 case PE_BRUSH_SMOOTH: {
4920 data.mval = mval;
4921 data.rad = pe_brush_size_get(scene, brush);
4922
4923 data.vec[0] = data.vec[1] = data.vec[2] = 0.0f;
4924 data.tot = 0;
4925
4926 data.smoothfac = brush->strength;
4927
4928 invert_m4_m4(ob->runtime->world_to_object.ptr(), ob->object_to_world().ptr());
4929
4930 foreach_mouse_hit_key(&data, brush_smooth_get, selected);
4931
4932 if (data.tot) {
4933 mul_v3_fl(data.vec, 1.0f / float(data.tot));
4934 foreach_mouse_hit_key(&data, brush_smooth_do, selected);
4935 }
4936
4937 break;
4938 }
4939 case PE_BRUSH_WEIGHT: {
4940 if (edit->psys) {
4941 data.mesh = psmd_eval->mesh_final;
4942 data.mval = mval;
4943 data.rad = pe_brush_size_get(scene, brush);
4944
4945 data.weightfac = brush->strength; /* note that this will never be zero */
4946
4948 }
4949
4950 break;
4951 }
4952 }
4953 if ((pset->flag & PE_KEEP_LENGTHS) == 0) {
4954 recalc_lengths(edit);
4955 }
4956
4957 if (ELEM(pset->brushtype, PE_BRUSH_ADD, PE_BRUSH_CUT) && (added || removed)) {
4958 if (pset->brushtype == PE_BRUSH_ADD && pe_x_mirror(ob)) {
4959 PE_mirror_x(depsgraph, scene, ob, 1);
4960 }
4961
4962 update_world_cos(ob, edit);
4963 psys_free_path_cache(nullptr, edit);
4965 }
4966 else {
4967 PE_update_object(depsgraph, scene, ob, 1);
4968 }
4969 }
4970
4971 if (edit->psys) {
4975 }
4976 else {
4979 }
4980
4981 bedit->lastmouse[0] = mouse[0];
4982 bedit->lastmouse[1] = mouse[1];
4983 bedit->first = 0;
4984 }
4985
4986 pset->flag |= lock_root;
4987}
4988
4990{
4991 BrushEdit *bedit = static_cast<BrushEdit *>(op->customdata);
4992
4993 PE_data_free(&bedit->data);
4994 MEM_freeN(bedit);
4995}
4996
4998{
4999 if (!brush_edit_init(C, op)) {
5000 return OPERATOR_CANCELLED;
5001 }
5002
5003 RNA_BEGIN (op->ptr, itemptr, "stroke") {
5004 brush_edit_apply(C, op, &itemptr);
5005 }
5006 RNA_END;
5007
5008 brush_edit_exit(op);
5009
5010 return OPERATOR_FINISHED;
5011}
5012
5013static void brush_edit_apply_event(bContext *C, wmOperator *op, const wmEvent *event)
5014{
5015 PointerRNA itemptr;
5016 float mouse[2];
5017
5018 copy_v2fl_v2i(mouse, event->mval);
5019
5020 /* fill in stroke */
5021 RNA_collection_add(op->ptr, "stroke", &itemptr);
5022
5023 RNA_float_set_array(&itemptr, "mouse", mouse);
5024 RNA_boolean_set(&itemptr, "pen_flip", event->modifier & KM_SHIFT); /* XXX hardcoded */
5025
5026 /* apply */
5027 brush_edit_apply(C, op, &itemptr);
5028}
5029
5030static int brush_edit_invoke(bContext *C, wmOperator *op, const wmEvent *event)
5031{
5032 if (!brush_edit_init(C, op)) {
5033 return OPERATOR_CANCELLED;
5034 }
5035
5036 brush_edit_apply_event(C, op, event);
5037
5039
5041}
5042
5043static int brush_edit_modal(bContext *C, wmOperator *op, const wmEvent *event)
5044{
5045 switch (event->type) {
5046 case LEFTMOUSE:
5047 case MIDDLEMOUSE:
5048 case RIGHTMOUSE: /* XXX hardcoded */
5049 if (event->val == KM_RELEASE) {
5050 brush_edit_exit(op);
5051 return OPERATOR_FINISHED;
5052 }
5053 break;
5054 case MOUSEMOVE:
5055 brush_edit_apply_event(C, op, event);
5056 break;
5057 }
5058
5060}
5061
5062static void brush_edit_cancel(bContext * /*C*/, wmOperator *op)
5063{
5064 brush_edit_exit(op);
5065}
5066
5068{
5070}
5071
5073{
5074 /* identifiers */
5075 ot->name = "Brush Edit";
5076 ot->idname = "PARTICLE_OT_brush_edit";
5077 ot->description = "Apply a stroke of brush to the particles";
5078
5079 /* api callbacks */
5085
5086 /* flags */
5088
5089 /* properties */
5090 PropertyRNA *prop;
5091 prop = RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
5093}
5094
5097/* -------------------------------------------------------------------- */
5102{
5103 if (PE_hair_poll(C)) {
5104 Scene *scene = CTX_data_scene(C);
5105 ParticleEditSettings *pset = PE_settings(scene);
5106
5107 if (pset->shape_object && (pset->shape_object->type == OB_MESH)) {
5108 return true;
5109 }
5110 }
5111
5112 return false;
5113}
5114
5119
5120static void point_inside_bvh_cb(void *userdata,
5121 int index,
5122 const BVHTreeRay *ray,
5123 BVHTreeRayHit *hit)
5124{
5125 PointInsideBVH *data = static_cast<PointInsideBVH *>(userdata);
5126
5127 data->bvhdata.raycast_callback(&data->bvhdata, index, ray, hit);
5128
5129 if (hit->index != -1) {
5130 ++data->num_hits;
5131 }
5132}
5133
5134/* true if the point is inside the shape mesh */
5136{
5137 BVHTreeFromMesh *shape_bvh = &data->shape_bvh;
5138 const float dir[3] = {1.0f, 0.0f, 0.0f};
5139 PointInsideBVH userdata;
5140
5141 userdata.bvhdata = data->shape_bvh;
5142 userdata.num_hits = 0;
5143
5144 float co_shape[3];
5145 mul_v3_m4v3(co_shape, pset->shape_object->world_to_object().ptr(), key->co);
5146
5148 shape_bvh->tree, co_shape, dir, 0.0f, BVH_RAYCAST_DIST_MAX, point_inside_bvh_cb, &userdata);
5149
5150 /* for any point inside a watertight mesh the number of hits is uneven */
5151 return (userdata.num_hits % 2) == 1;
5152}
5153
5154static void shape_cut(PEData *data, int pa_index)
5155{
5156 PTCacheEdit *edit = data->edit;
5157 Object *ob = data->ob;
5158 ParticleEditSettings *pset = PE_settings(data->scene);
5159 ParticleCacheKey *key;
5160
5161 bool cut;
5162 float cut_time = 1.0;
5163 int k, totkeys = 1 << pset->draw_step;
5164
5165 /* don't cut hidden */
5166 if (edit->points[pa_index].flag & PEP_HIDE) {
5167 return;
5168 }
5169
5170 cut = false;
5171
5172 /* check if root is inside the cut shape */
5173 key = edit->pathcache[pa_index];
5174 if (!shape_cut_test_point(data, pset, key)) {
5175 cut_time = -1.0f;
5176 cut = true;
5177 }
5178 else {
5179 for (k = 0; k < totkeys; k++, key++) {
5180 BVHTreeRayHit hit;
5181
5182 float co_curr_shape[3], co_next_shape[3];
5183 float dir_shape[3];
5184 float len_shape;
5185
5186 mul_v3_m4v3(co_curr_shape, pset->shape_object->world_to_object().ptr(), key->co);
5187 mul_v3_m4v3(co_next_shape, pset->shape_object->world_to_object().ptr(), (key + 1)->co);
5188
5189 sub_v3_v3v3(dir_shape, co_next_shape, co_curr_shape);
5190 len_shape = normalize_v3(dir_shape);
5191
5192 memset(&hit, 0, sizeof(hit));
5193 hit.index = -1;
5194 hit.dist = len_shape;
5195 BLI_bvhtree_ray_cast(data->shape_bvh.tree,
5196 co_curr_shape,
5197 dir_shape,
5198 0.0f,
5199 &hit,
5200 data->shape_bvh.raycast_callback,
5201 &data->shape_bvh);
5202 if (hit.index >= 0) {
5203 if (hit.dist < len_shape) {
5204 cut_time = ((hit.dist / len_shape) + float(k)) / float(totkeys);
5205 cut = true;
5206 break;
5207 }
5208 }
5209 }
5210 }
5211
5212 if (cut) {
5213 if (cut_time < 0.0f) {
5214 edit->points[pa_index].flag |= PEP_TAG;
5215 }
5216 else {
5217 rekey_particle_to_time(data->context, data->scene, ob, pa_index, cut_time);
5218 edit->points[pa_index].flag |= PEP_EDIT_RECALC;
5219 }
5220 }
5221}
5222
5223static int shape_cut_exec(bContext *C, wmOperator * /*op*/)
5224{
5226 Scene *scene = CTX_data_scene(C);
5228 ParticleEditSettings *pset = PE_settings(scene);
5229 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
5230 Object *shapeob = pset->shape_object;
5231 int selected = count_selected_keys(scene, edit);
5232 int lock_root = pset->flag & PE_LOCK_FIRST;
5233
5234 if (!PE_start_edit(edit)) {
5235 return OPERATOR_CANCELLED;
5236 }
5237
5238 /* Disable locking temporarily for disconnected hair. */
5239 if (edit->psys && edit->psys->flag & PSYS_GLOBAL_HAIR) {
5240 pset->flag &= ~PE_LOCK_FIRST;
5241 }
5242
5243 if (edit->psys && edit->pathcache) {
5244 PEData data;
5245 int removed;
5246
5247 PE_set_data(C, &data);
5248 if (!PE_create_shape_tree(&data, shapeob)) {
5249 /* shapeob may not have faces... */
5250 return OPERATOR_CANCELLED;
5251 }
5252
5253 if (selected) {
5255 }
5256 else {
5257 foreach_point(&data, shape_cut);
5258 }
5259
5260 removed = remove_tagged_particles(ob, edit->psys, pe_x_mirror(ob));
5261 recalc_lengths(edit);
5262
5263 if (removed) {
5264 update_world_cos(ob, edit);
5265 psys_free_path_cache(nullptr, edit);
5267 }
5268 else {
5269 PE_update_object(data.depsgraph, scene, ob, 1);
5270 }
5271
5272 if (edit->psys) {
5276 }
5277 else {
5280 }
5281
5282 PE_free_shape_tree(&data);
5283 }
5284
5285 pset->flag |= lock_root;
5286
5287 return OPERATOR_FINISHED;
5288}
5289
5291{
5292 /* identifiers */
5293 ot->name = "Shape Cut";
5294 ot->idname = "PARTICLE_OT_shape_cut";
5295 ot->description = "Cut hair to conform to the set shape object";
5296
5297 /* api callbacks */
5300
5301 /* flags */
5303}
5304
5307/* -------------------------------------------------------------------- */
5312 Depsgraph *depsgraph, Scene *scene, Object *ob, PointCache *cache, ParticleSystem *psys)
5313{
5315 PTCacheEdit *edit;
5316 ParticleSystemModifierData *psmd = (psys) ? psys_get_modifier(ob, psys) : nullptr;
5317 ParticleSystemModifierData *psmd_eval = nullptr;
5318 POINT_P;
5319 KEY_K;
5320 ParticleData *pa = nullptr;
5321 HairKey *hkey;
5322 int totpoint;
5323
5324 if (psmd != nullptr) {
5326 psmd->modifier.name);
5327 }
5328
5329 /* no psmd->dm happens in case particle system modifier is not enabled */
5330 if (!(psys && psmd && psmd_eval->mesh_final) && !cache) {
5331 return;
5332 }
5333
5334 if (cache && cache->flag & PTCACHE_DISK_CACHE) {
5335 return;
5336 }
5337
5338 if (psys == nullptr && (cache && BLI_listbase_is_empty(&cache->mem_cache))) {
5339 return;
5340 }
5341
5342 edit = (psys) ? psys->edit : cache->edit;
5343
5344 if (!edit) {
5345 ParticleSystem *psys_eval = nullptr;
5346 if (psys) {
5347 psys_eval = psys_eval_get(depsgraph, ob, psys);
5348 psys_copy_particles(psys, psys_eval);
5349 }
5350
5351 totpoint = psys ? psys->totpart : int(((PTCacheMem *)cache->mem_cache.first)->totpoint);
5352
5353 edit = static_cast<PTCacheEdit *>(MEM_callocN(sizeof(PTCacheEdit), "PE_create_particle_edit"));
5354 edit->points = static_cast<PTCacheEditPoint *>(
5355 MEM_callocN(totpoint * sizeof(PTCacheEditPoint), "PTCacheEditPoints"));
5356 edit->totpoint = totpoint;
5357
5358 if (psys && !cache) {
5359 edit->psmd = psmd;
5360 edit->psmd_eval = psmd_eval;
5361 psys->edit = edit;
5362 edit->psys = psys;
5363 edit->psys_eval = psys_eval;
5364
5366
5367 edit->pathcache = nullptr;
5369
5370 pa = psys->particles;
5371 LOOP_POINTS {
5372 point->totkey = pa->totkey;
5373 point->keys = static_cast<PTCacheEditKey *>(
5374 MEM_callocN(point->totkey * sizeof(PTCacheEditKey), "ParticleEditKeys"));
5375 point->flag |= PEP_EDIT_RECALC;
5376
5377 hkey = pa->hair;
5378 LOOP_KEYS {
5379 key->co = hkey->co;
5380 key->time = &hkey->time;
5381 key->flag = hkey->editflag;
5382 if (!(psys->flag & PSYS_GLOBAL_HAIR)) {
5383 key->flag |= PEK_USE_WCO;
5384 hkey->editflag |= PEK_USE_WCO;
5385 }
5386
5387 hkey++;
5388 }
5389 pa++;
5390 }
5391 update_world_cos(ob, edit);
5392 }
5393 else {
5394 int totframe = 0;
5395
5396 cache->edit = edit;
5398 edit->psys = nullptr;
5399
5400 LISTBASE_FOREACH (PTCacheMem *, pm, &cache->mem_cache) {
5401 totframe++;
5402 }
5403
5404 LISTBASE_FOREACH (PTCacheMem *, pm, &cache->mem_cache) {
5405 LOOP_POINTS {
5406 void *cur[BPHYS_TOT_DATA];
5407 if (BKE_ptcache_mem_pointers_seek(p, pm, cur) == 0) {
5408 continue;
5409 }
5410
5411 if (!point->totkey) {
5412 key = point->keys = static_cast<PTCacheEditKey *>(
5413 MEM_callocN(totframe * sizeof(PTCacheEditKey), "ParticleEditKeys"));
5414 point->flag |= PEP_EDIT_RECALC;
5415 }
5416 else {
5417 key = point->keys + point->totkey;
5418 }
5419
5420 key->co = static_cast<float *>(cur[BPHYS_DATA_LOCATION]);
5421 key->vel = static_cast<float *>(cur[BPHYS_DATA_VELOCITY]);
5422 key->rot = static_cast<float *>(cur[BPHYS_DATA_ROTATION]);
5423 key->ftime = float(pm->frame);
5424 key->time = &key->ftime;
5426
5427 point->totkey++;
5428 }
5429 }
5430 psys = nullptr;
5431 }
5432
5433 recalc_lengths(edit);
5434 if (psys && !cache) {
5436 }
5437
5438 PE_update_object(depsgraph, scene, ob, 1);
5439 }
5440}
5441
5443{
5445
5446 if (ob == nullptr || ob->type != OB_MESH) {
5447 return false;
5448 }
5449 if (!ob->data || !ID_IS_EDITABLE(ob->data) || ID_IS_OVERRIDE_LIBRARY(ob->data)) {
5450 return false;
5451 }
5452
5454}
5455
5456static void free_all_psys_edit(Object *object)
5457{
5458 for (ParticleSystem *psys = static_cast<ParticleSystem *>(object->particlesystem.first);
5459 psys != nullptr;
5460 psys = psys->next)
5461 {
5462 if (psys->edit != nullptr) {
5463 BLI_assert(psys->free_edit != nullptr);
5464 psys->free_edit(psys->edit);
5465 psys->free_edit = nullptr;
5466 psys->edit = nullptr;
5467 }
5468 }
5469}
5470
5476
5478{
5479 /* Needed so #ParticleSystemModifierData.mesh_final is set. */
5481
5482 PTCacheEdit *edit;
5483
5485
5486 edit = PE_create_current(depsgraph, scene, ob);
5487
5488 /* Mesh may have changed since last entering editmode.
5489 * NOTE: this may have run before if the edit data was just created,
5490 * so could avoid this and speed up a little. */
5491 if (edit && edit->psys) {
5492 /* Make sure pointer to the evaluated modifier data is up to date,
5493 * with possible changes applied when object was outside of the
5494 * edit mode. */
5495 Object *object_eval = DEG_get_evaluated_object(depsgraph, ob);
5497 object_eval, edit->psmd->modifier.name);
5499 }
5500
5501 toggle_particle_cursor(scene, true);
5504}
5505
5513
5515{
5516 ob->mode &= ~OB_MODE_PARTICLE_EDIT;
5517 toggle_particle_cursor(scene, false);
5519
5522}
5523
5530
5532{
5533 wmMsgBus *mbus = CTX_wm_message_bus(C);
5534 Scene *scene = CTX_data_scene(C);
5536 const int mode_flag = OB_MODE_PARTICLE_EDIT;
5537 const bool is_mode_set = (ob->mode & mode_flag) != 0;
5538
5539 if (!is_mode_set) {
5540 if (!blender::ed::object::mode_compat_set(C, ob, eObjectMode(mode_flag), op->reports)) {
5541 return OPERATOR_CANCELLED;
5542 }
5543 }
5544
5545 if (!is_mode_set) {
5546 Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
5548 }
5549 else {
5551 }
5552
5553 WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode);
5554
5556
5557 return OPERATOR_FINISHED;
5558}
5559
5561{
5562 /* identifiers */
5563 ot->name = "Particle Edit Toggle";
5564 ot->idname = "PARTICLE_OT_particle_edit_toggle";
5565 ot->description = "Toggle particle edit mode";
5566
5567 /* api callbacks */
5570
5571 /* flags */
5573}
5574
5577/* -------------------------------------------------------------------- */
5581static int clear_edited_exec(bContext *C, wmOperator * /*op*/)
5582{
5584 ParticleSystem *psys = psys_get_current(ob);
5585
5586 if (psys->edit) {
5587 if (/*psys->edit->edited ||*/ true) {
5589
5590 psys->edit = nullptr;
5591 psys->free_edit = nullptr;
5592
5594 psys->flag &= ~PSYS_GLOBAL_HAIR;
5595 psys->flag &= ~PSYS_EDITED;
5596
5601 }
5602 }
5603 else { /* some operation might have protected hair from editing so let's clear the flag */
5605 psys->flag &= ~PSYS_GLOBAL_HAIR;
5606 psys->flag &= ~PSYS_EDITED;
5609 }
5610
5611 return OPERATOR_FINISHED;
5612}
5613
5615{
5616 /* identifiers */
5617 ot->name = "Clear Edited";
5618 ot->idname = "PARTICLE_OT_edited_clear";
5619 ot->description = "Undo all edition performed on the particle system";
5620
5621 /* api callbacks */
5624
5625 /* flags */
5627}
5628
5631/* -------------------------------------------------------------------- */
5636{
5637 float length = 0.0f;
5638 KEY_K;
5639 LOOP_KEYS {
5640 if (k > 0) {
5641 length += len_v3v3((key - 1)->co, key->co);
5642 }
5643 }
5644 return length;
5645}
5646
5648{
5649 int num_selected = 0;
5650 float total_length = 0;
5651 POINT_P;
5653 total_length += calculate_point_length(point);
5654 num_selected++;
5655 }
5656 if (num_selected == 0) {
5657 return 0.0f;
5658 }
5659 return total_length / num_selected;
5660}
5661
5662static void scale_point_factor(PTCacheEditPoint *point, float factor)
5663{
5664 float orig_prev_co[3], prev_co[3];
5665 KEY_K;
5666 LOOP_KEYS {
5667 if (k == 0) {
5668 copy_v3_v3(orig_prev_co, key->co);
5669 copy_v3_v3(prev_co, key->co);
5670 }
5671 else {
5672 float new_co[3];
5673 float delta[3];
5674
5675 sub_v3_v3v3(delta, key->co, orig_prev_co);
5676 mul_v3_fl(delta, factor);
5677 add_v3_v3v3(new_co, prev_co, delta);
5678
5679 copy_v3_v3(orig_prev_co, key->co);
5680 copy_v3_v3(key->co, new_co);
5681 copy_v3_v3(prev_co, key->co);
5682 }
5683 }
5684 point->flag |= PEP_EDIT_RECALC;
5685}
5686
5687static void scale_point_to_length(PTCacheEditPoint *point, float length)
5688{
5689 const float point_length = calculate_point_length(point);
5690 if (point_length != 0.0f) {
5691 const float factor = length / point_length;
5692 scale_point_factor(point, factor);
5693 }
5694}
5695
5696static void scale_points_to_length(PTCacheEdit *edit, float length)
5697{
5698 POINT_P;
5700 scale_point_to_length(point, length);
5701 }
5702 recalc_lengths(edit);
5703}
5704
5705static int unify_length_exec(bContext *C, wmOperator * /*op*/)
5706{
5708 Scene *scene = CTX_data_scene(C);
5709 Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
5710
5711 PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
5712 float average_length = calculate_average_length(edit);
5713
5714 if (average_length == 0.0f) {
5715 return OPERATOR_CANCELLED;
5716 }
5717 scale_points_to_length(edit, average_length);
5718
5719 PE_update_object(depsgraph, scene, ob, 1);
5720 if (edit->psys) {
5722 }
5723 else {
5726 }
5727
5728 return OPERATOR_FINISHED;
5729}
5730
5732{
5733 /* identifiers */
5734 ot->name = "Unify Length";
5735 ot->idname = "PARTICLE_OT_unify_length";
5736 ot->description = "Make selected hair the same length";
5737
5738 /* api callbacks */
5741
5742 /* flags */
5744}
5745
void free_bvhtree_from_mesh(BVHTreeFromMesh *data)
Definition bvhutils.cc:1160
BVHTree * BKE_bvhtree_from_mesh_get(BVHTreeFromMesh *data, const Mesh *mesh, BVHCacheType bvh_cache_type, int tree_type)
Definition bvhutils.cc:899
@ BVHTREE_FROM_CORNER_TRIS
Depsgraph * CTX_data_ensure_evaluated_depsgraph(const bContext *C)
ScrArea * CTX_wm_area(const bContext *C)
Depsgraph * CTX_data_depsgraph_pointer(const bContext *C)
Object * CTX_data_active_object(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
Main * CTX_data_main(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
wmMsgBus * CTX_wm_message_bus(const bContext *C)
ViewLayer * CTX_data_view_layer(const bContext *C)
CustomData interface, see also DNA_customdata_types.h.
const void * CustomData_get_layer(const CustomData *data, eCustomDataType type)
#define G_MAIN
void BKE_view_layer_synced_ensure(const Scene *scene, ViewLayer *view_layer)
Object * BKE_view_layer_active_object_get(const ViewLayer *view_layer)
void BKE_mesh_tessface_ensure(Mesh *mesh)
ModifierData * BKE_modifiers_findby_type(const Object *ob, ModifierType type)
ModifierData * BKE_modifiers_findby_name(const Object *ob, const char *name)
ModifierData * BKE_modifier_get_evaluated(Depsgraph *depsgraph, Object *object, ModifierData *md)
General operations, lookup, etc. for blender objects.
Mesh * BKE_object_get_evaluated_mesh(const Object *object_eval)
void BKE_object_minmax(Object *ob, float r_min[3], float r_max[3])
void psys_get_particle_on_path(struct ParticleSimulationData *sim, int pa_num, struct ParticleKey *state, bool vel)
Definition particle.cc:4599
struct ParticleSystemModifierData * psys_get_modifier(struct Object *ob, struct ParticleSystem *psys)
Definition particle.cc:2150
#define DMCACHE_NOTFOUND
void psys_mat_hair_to_orco(struct Object *ob, struct Mesh *mesh, short from, struct ParticleData *pa, float hairmat[4][4])
Definition particle.cc:3877
void psys_disable_all(struct Object *ob)
Definition particle.cc:639
void psys_mat_hair_to_global(struct Object *ob, struct Mesh *mesh, short from, struct ParticleData *pa, float hairmat[4][4])
Definition particle.cc:3907
void reset_particle(struct ParticleSimulationData *sim, struct ParticleData *pa, float dtime, float cfra)
void psys_cache_edit_paths(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob, struct PTCacheEdit *edit, float cfra, bool use_render_params)
Definition particle.cc:3662
void psys_reset(struct ParticleSystem *psys, int mode)
void psys_mat_hair_to_object(struct Object *ob, struct Mesh *mesh, short from, struct ParticleData *pa, float hairmat[4][4])
Definition particle.cc:3851
float psys_get_timestep(struct ParticleSimulationData *sim)
Definition particle.cc:4474
void psys_particle_on_dm(struct Mesh *mesh_final, int from, int index, int index_dmcache, const float fw[4], float foffset, float vec[3], float nor[3], float utan[3], float vtan[3], float orco[3])
Definition particle.cc:2017
void psys_enable_all(struct Object *ob)
Definition particle.cc:647
#define LOOP_PARTICLES
void init_particle(struct ParticleSimulationData *sim, struct ParticleData *pa)
struct ParticleSystem * psys_get_current(struct Object *ob)
Definition particle.cc:534
struct ParticleSystem * psys_eval_get(struct Depsgraph *depsgraph, struct Object *object, struct ParticleSystem *psys)
Definition particle.cc:664
void psys_free_path_cache(struct ParticleSystem *psys, struct PTCacheEdit *edit)
Definition particle.cc:904
#define DMCACHE_ISCHILD
int psys_particle_dm_face_lookup(struct Mesh *mesh_final, struct Mesh *mesh_original, int findex_orig, const float fw[4], struct LinkNode **poly_nodes)
Definition particle.cc:1830
void BKE_particle_batch_cache_dirty_tag(struct ParticleSystem *psys, int mode)
Definition particle.cc:5295
#define PSYS_RESET_DEPSGRAPH
void psys_copy_particles(struct ParticleSystem *psys_dst, struct ParticleSystem *psys_src)
Definition particle.cc:1053
#define PARTICLE_P
@ BKE_PARTICLE_BATCH_DIRTY_ALL
void BKE_ptcache_mem_pointers_incr(void *cur[BPHYS_TOT_DATA])
@ PT_CACHE_EDIT_UPDATE_PARTICLE_FROM_EVAL
void BKE_ptcache_ids_from_object(struct ListBase *lb, struct Object *ob, struct Scene *scene, int duplis)
#define PTCACHE_VEL_PER_SEC
#define PEK_HIDE
#define PEP_EDIT_RECALC
#define PTCACHE_TYPE_CLOTH
#define PEP_HIDE
int BKE_ptcache_mem_pointers_seek(int point_index, struct PTCacheMem *pm, void *cur[BPHYS_TOT_DATA])
#define PTCACHE_TYPE_PARTICLES
#define PTCACHE_TYPE_SOFTBODY
#define PEP_TAG
#define PEK_SELECT
#define PEK_USE_WCO
#define PEK_TAG
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
void BKE_scene_graph_evaluated_ensure(Depsgraph *depsgraph, Main *bmain)
Definition scene.cc:2573
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
#define BVH_RAYCAST_DIST_MAX
Definition BLI_kdopbvh.h:92
void BLI_bvhtree_ray_cast_all(const BVHTree *tree, const float co[3], const float dir[3], float radius, float hit_dist, BVHTree_RayCastCallback callback, void *userdata)
int BLI_bvhtree_ray_cast(const BVHTree *tree, const float co[3], const float dir[3], float radius, BVHTreeRayHit *hit, BVHTree_RayCastCallback callback, void *userdata)
A KD-tree for nearest neighbor search.
bool BLI_lasso_is_point_inside(blender::Span< blender::int2 > mcoords, int sx, int sy, int error_value)
BLI_INLINE bool BLI_listbase_is_empty(const struct ListBase *lb)
#define LISTBASE_FOREACH(type, var, list)
BLI_INLINE void BLI_listbase_clear(struct ListBase *lb)
void void BLI_freelistN(struct ListBase *listbase) ATTR_NONNULL(1)
Definition listbase.cc:496
void void BLI_INLINE bool BLI_listbase_is_single(const struct ListBase *lb)
MINLINE float max_ff(float a, float b)
MINLINE float interpf(float target, float origin, float t)
bool isect_aabb_aabb_v3(const float min1[3], const float max1[3], const float min2[3], const float max2[3])
bool isect_sweeping_sphere_tri_v3(const float p1[3], const float p2[3], float radius, const float v0[3], const float v1[3], const float v2[3], float *r_lambda, float ipoint[3])
bool isect_line_segment_tri_v3(const float p1[3], const float p2[3], const float v0[3], const float v1[3], const float v2[3], float *r_lambda, float r_uv[2])
void interp_weights_poly_v3(float w[], float v[][3], int n, const float co[3])
void unit_m4(float m[4][4])
Definition rct.c:1127
void mul_m4_v3(const float M[4][4], float r[3])
void copy_m4_m4(float m1[4][4], const float m2[4][4])
void mul_v3_m4v3(float r[3], const float mat[4][4], const float vec[3])
bool invert_m4_m4(float inverse[4][4], const float mat[4][4])
void mul_mat3_m4_v3(const float mat[4][4], float r[3])
MINLINE void copy_v2fl_v2i(float r[2], const int a[2])
MINLINE float len_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT
void minmax_v3v3_v3(float min[3], float max[3], const float vec[3])
MINLINE void madd_v3_v3fl(float r[3], const float a[3], float f)
MINLINE void sub_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE void mul_v3_fl(float r[3], float f)
MINLINE void copy_v3_v3(float r[3], const float a[3])
void project_v3_v3v3(float out[3], const float p[3], const float v_proj[3])
MINLINE float dot_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT
MINLINE void add_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE void cross_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE bool is_zero_v3(const float v[3]) ATTR_WARN_UNUSED_RESULT
void mid_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE void madd_v3_v3v3fl(float r[3], const float a[3], const float b[3], float f)
MINLINE void zero_v3(float r[3])
MINLINE void add_v3_v3(float r[3], const float a[3])
void dist_ensure_v3_v3fl(float v1[3], const float v2[3], float dist)
MINLINE float normalize_v3(float n[3])
MINLINE float len_v3(const float a[3]) ATTR_WARN_UNUSED_RESULT
Random number functions.
struct RNG * BLI_rng_new(unsigned int seed)
Definition rand.cc:39
void BLI_rng_free(struct RNG *rng) ATTR_NONNULL(1)
Definition rand.cc:58
struct RNG * BLI_rng_new_srandom(unsigned int seed)
Definition rand.cc:46
float BLI_rng_get_float(struct RNG *rng) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition rand.cc:93
bool BLI_rcti_is_empty(const struct rcti *rect)
unsigned int uint
void BLI_task_parallel_range(int start, int stop, void *userdata, TaskParallelRangeFunc func, const TaskParallelSettings *settings)
Definition task_range.cc:99
int BLI_task_parallel_thread_id(const TaskParallelTLS *tls)
BLI_INLINE void BLI_parallel_range_settings_defaults(TaskParallelSettings *settings)
Definition BLI_task.h:230
long int BLI_time_now_seconds_i(void)
Definition time.c:75
Utility defines for timing/benchmarks.
#define SHIFT4(type, a, b, c, d)
#define INIT_MINMAX(min, max)
#define POINTER_AS_UINT(i)
#define SHIFT3(type, a, b, c)
#define SET_FLAG_FROM_TEST(value, test, flag)
#define ELEM(...)
void DEG_id_tag_update(ID *id, unsigned int flags)
bool DEG_is_active(const Depsgraph *depsgraph)
Definition depsgraph.cc:318
Object * DEG_get_evaluated_object(const Depsgraph *depsgraph, Object *object)
@ ID_RECALC_SELECT
Definition DNA_ID.h:1068
@ ID_RECALC_PSYS_REDO
Definition DNA_ID.h:1048
@ ID_RECALC_SYNC_TO_EVAL
Definition DNA_ID.h:1085
@ ID_RECALC_PSYS_RESET
Definition DNA_ID.h:1050
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:1041
#define ID_IS_EDITABLE(_id)
Definition DNA_ID.h:658
#define ID_IS_OVERRIDE_LIBRARY(_id)
Definition DNA_ID.h:683
@ ME_SYMMETRY_X
@ eModifierType_Cloth
@ eModifierType_Softbody
eObjectMode
@ OB_MODE_PARTICLE_EDIT
@ OB_MESH
@ PART_FROM_FACE
@ PART_EMITTER
@ PART_HAIR
@ PARS_REKEY
@ PSYS_CURRENT
@ PSYS_HAIR_DYNAMICS
@ PSYS_EDITED
@ PSYS_HAIR_DONE
@ PSYS_GLOBAL_HAIR
@ PTCACHE_BAKED
@ PTCACHE_DISK_CACHE
@ BPHYS_DATA_VELOCITY
@ BPHYS_DATA_LOCATION
@ BPHYS_DATA_ROTATION
#define BPHYS_TOT_DATA
@ UNIFIED_PAINT_SIZE
@ SCE_SELECT_PATH
@ SCE_SELECT_POINT
@ SCE_SELECT_END
@ PE_BRUSH_DATA_PUFF_VOLUME
@ PE_TYPE_CLOTH
@ PE_TYPE_PARTICLES
@ PE_TYPE_SOFTBODY
@ PE_LOCK_FIRST
@ PE_FADE_TIME
@ PE_INTERPOLATE_ADDED
@ PE_DEFLECT_EMITTER
@ PE_KEEP_LENGTHS
@ PE_AUTO_VELOCITY
@ PE_BRUSH_COMB
@ PE_BRUSH_CUT
@ PE_BRUSH_PUFF
@ PE_BRUSH_LENGTH
@ PE_BRUSH_WEIGHT
@ PE_BRUSH_ADD
@ PE_BRUSH_SMOOTH
@ RGN_TYPE_WINDOW
@ SPACE_VIEW3D
@ OPERATOR_RUNNING_MODAL
int * mesh_get_x_mirror_faces(Object *ob, BMEditMesh *em, Mesh *mesh_eval)
int ED_select_op_action_deselected(eSelectOp sel_op, bool is_select, bool is_inside)
#define SEL_OP_USE_PRE_DESELECT(sel_op)
@ SEL_SELECT
@ SEL_INVERT
@ SEL_DESELECT
@ SEL_TOGGLE
eSelectOp
@ SEL_OP_ADD
@ SEL_OP_SUB
@ SEL_OP_SET
@ SEL_OP_AND
@ SEL_OP_XOR
float ED_view3d_select_dist_px()
#define XRAY_ENABLED(v3d)
@ V3D_PROJ_TEST_CLIP_NEAR
Definition ED_view3d.hh:269
@ V3D_PROJ_TEST_CLIP_WIN
Definition ED_view3d.hh:268
@ V3D_PROJ_TEST_CLIP_BB
Definition ED_view3d.hh:267
@ V3D_PROJ_RET_OK
Definition ED_view3d.hh:243
ViewContext ED_view3d_viewcontext_init(bContext *C, Depsgraph *depsgraph)
void ED_view3d_depth_override(Depsgraph *depsgraph, ARegion *region, View3D *v3d, Object *obact, eV3DDepthOverrideMode mode, ViewDepths **r_depths)
void ED_view3d_win_to_delta(const ARegion *region, const float xy_delta[2], float zfac, float r_out[3])
bool ED_view3d_win_to_segment_clipped(const Depsgraph *depsgraph, const ARegion *region, const View3D *v3d, const float mval[2], float r_ray_start[3], float r_ray_end[3], bool do_clip_planes)
#define IS_CLIPPED
Definition ED_view3d.hh:239
void view3d_operator_needs_opengl(const bContext *C)
float ED_view3d_calc_zfac(const RegionView3D *rv3d, const float co[3])
void ED_view3d_depths_free(ViewDepths *depths)
@ V3D_DEPTH_OBJECT_ONLY
Definition ED_view3d.hh:192
void ED_view3d_project_v3(const ARegion *region, const float world[3], float r_region_co[3])
eV3DProjStatus ED_view3d_project_int_global(const ARegion *region, const float co[3], int r_co[2], eV3DProjTest flag)
void immUnbindProgram()
void immBindBuiltinProgram(eGPUBuiltinShader shader_id)
void immUniformColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
GPUVertFormat * immVertexFormat()
void imm_draw_circle_wire_2d(uint shdr_pos, float x, float y, float radius, int nsegments)
@ GPU_SHADER_3D_UNIFORM_COLOR
@ GPU_BLEND_NONE
Definition GPU_state.hh:85
@ GPU_BLEND_ALPHA
Definition GPU_state.hh:87
void GPU_blend(eGPUBlend blend)
Definition gpu_state.cc:42
void GPU_line_smooth(bool enable)
Definition gpu_state.cc:78
@ GPU_FETCH_FLOAT
uint GPU_vertformat_attr_add(GPUVertFormat *, const char *name, GPUVertCompType, uint comp_len, GPUVertFetchMode)
@ GPU_COMP_F32
Read Guarded memory(de)allocation.
#define MEM_SAFE_FREE(v)
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block Image Sample an image file as a texture Gabor Generate Gabor noise Gradient Generate interpolated color and intensity values based on the input vector Magic Generate a psychedelic color texture Voronoi Generate Worley noise based on the distance to random points Typically used to generate textures such as or biological cells Brick Generate a procedural texture producing bricks Texture Retrieve multiple types of texture coordinates nTypically used as inputs for texture nodes Vector Convert a point
#define RNA_BEGIN(sptr, itemptr, propname)
#define RNA_END
@ PROP_SKIP_SAVE
Definition RNA_types.hh:245
@ PROP_HIDDEN
Definition RNA_types.hh:239
#define C
Definition RandGen.cpp:29
@ OPTYPE_BLOCKING
Definition WM_types.hh:164
@ OPTYPE_UNDO
Definition WM_types.hh:162
@ OPTYPE_REGISTER
Definition WM_types.hh:160
@ KM_RELEASE
Definition WM_types.hh:285
#define NS_MODE_PARTICLE
Definition WM_types.hh:536
#define ND_MODE
Definition WM_types.hh:412
#define NC_SCENE
Definition WM_types.hh:345
#define ND_MODIFIER
Definition WM_types.hh:429
#define NA_EDITED
Definition WM_types.hh:550
#define ND_PARTICLE
Definition WM_types.hh:432
#define NS_MODE_OBJECT
Definition WM_types.hh:526
@ KM_SHIFT
Definition WM_types.hh:255
#define NC_OBJECT
Definition WM_types.hh:346
#define NA_SELECTED
Definition WM_types.hh:555
ATTR_WARN_UNUSED_RESULT const BMVert * v2
__forceinline BoundBox intersect(const BoundBox &a, const BoundBox &b)
Definition boundbox.h:178
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
static void mul(btAlignedObjectArray< T > &items, const Q &value)
static unsigned long seed
Definition btSoftBody.h:39
SIMD_FORCE_INLINE btScalar length() const
Return the length of the vector.
Definition btVector3.h:257
pow(value.r - subtrahend, 2.0)") .do_static_compilation(true)
additional_info("compositor_sum_squared_difference_float_shared") .push_constant(Type output_img float dot(value.rgb, luminance_coefficients)") .define("LOAD(value)"
const Depsgraph * depsgraph
#define fabsf(x)
#define sqrtf(x)
KDTree_3d * tree
draw_view in_light_buf[] float
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
static bool is_inside(int x, int y, int cols, int rows)
Definition filesel.cc:768
#define UINT_MAX
Definition hash_md5.cc:44
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
void *(* MEM_callocN)(size_t len, const char *str)
Definition mallocn.cc:42
void *(* MEM_dupallocN)(const void *vmemh)
Definition mallocn.cc:39
ccl_device_inline float4 select(const int4 mask, const float4 a, const float4 b)
static ulong state[N]
#define G(x, y, z)
bool mode_compat_set(bContext *C, Object *ob, eObjectMode mode, ReportList *reports)
void min_max(const T &value, T &min, T &max)
Frequency::GEOMETRY nor[]
static void rekey_particle_to_time(const bContext *C, Scene *scene, Object *ob, int pa_index, float path_time)
static void free_all_psys_edit(Object *object)
static void PE_set_data(bContext *C, PEData *data)
eParticleSelectFlag
@ PSEL_NEAREST
@ PSEL_ALL_KEYS
static int hide_exec(bContext *C, wmOperator *op)
static void PE_mirror_x(Depsgraph *depsgraph, Scene *scene, Object *ob, int tagged)
static void brush_edit_exit(wmOperator *op)
static const EnumPropertyItem select_random_type_items[]
static int brush_edit_init(bContext *C, wmOperator *op)
static bool key_inside_rect(PEData *data, const float co[3])
static int select_random_exec(bContext *C, wmOperator *op)
bool ED_object_particle_edit_mode_supported(const Object *ob)
static int select_more_exec(bContext *C, wmOperator *)
void(*)(PEData *data, int point_index) ForPointFunc
bool PE_hair_poll(bContext *C)
static int pe_x_mirror(Object *ob)
PTCacheEdit * PE_get_current_from_psys(ParticleSystem *psys)
void PARTICLE_OT_select_roots(wmOperatorType *ot)
static void foreach_mouse_hit_key_iter(void *__restrict iter_data_v, const int iter, const TaskParallelTLS *__restrict)
bool PE_poll(bContext *C)
bool PE_mouse_particles(bContext *C, const int mval[2], const SelectPick_Params *params)
bool PE_deselect_all_visible_ex(PTCacheEdit *edit)
static bool key_inside_circle(const PEData *data, float rad, const float co[3], float *distance)
static bool key_test_depth(const PEData *data, const float co[3], const int screen_co[2])
static void scale_point_to_length(PTCacheEditPoint *point, float length)
static int select_less_exec(bContext *C, wmOperator *)
static int brush_add(const bContext *C, PEData *data, short number)
static bool pe_nearest_point_and_key(bContext *C, const int mval[2], PTCacheEditPoint **r_point, PTCacheEditKey **r_key)
void PARTICLE_OT_weight_set(wmOperatorType *ot)
void PARTICLE_OT_remove_doubles(wmOperatorType *ot)
static void brush_comb(PEData *data, float[4][4], float imat[4][4], int point_index, int key_index, PTCacheEditKey *key, float mouse_distance)
static void pe_deflect_emitter(Scene *scene, Object *ob, PTCacheEdit *edit)
static void deflect_emitter_iter(void *__restrict iter_data_v, const int iter, const TaskParallelTLS *__restrict)
static void set_delete_particle(PEData *data, int pa_index)
void PARTICLE_OT_shape_cut(wmOperatorType *ot)
static int remove_doubles_exec(bContext *C, wmOperator *op)
void ED_object_particle_edit_mode_exit_ex(Scene *scene, Object *ob)
static int select_linked_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static void brush_add_count_iter_reduce(const void *__restrict, void *__restrict join_v, void *__restrict chunk_v)
void PE_current_changed(Depsgraph *depsgraph, Scene *scene, Object *ob)
static void foreach_mouse_hit_point(PEData *data, ForHitPointFunc func, int selected)
void recalc_emitter_field(Depsgraph *, Object *, ParticleSystem *psys)
static void PE_apply_lengths(Scene *scene, PTCacheEdit *edit)
static void PE_set_view3d_data(bContext *C, PEData *data)
static float calculate_point_length(PTCacheEditPoint *point)
static void foreach_selected_key(PEData *data, ForKeyFunc func)
void PARTICLE_OT_delete(wmOperatorType *ot)
static int particle_edit_toggle_exec(bContext *C, wmOperator *op)
static void point_inside_bvh_cb(void *userdata, int index, const BVHTreeRay *ray, BVHTreeRayHit *hit)
static bool shape_cut_test_point(PEData *data, ParticleEditSettings *pset, ParticleCacheKey *key)
static void select_tip(PEData *data, int point_index)
void PARTICLE_OT_select_less(wmOperatorType *ot)
static void pe_select_cache_free_generic_userdata(void *data)
int PE_minmax(Depsgraph *depsgraph, Scene *scene, ViewLayer *view_layer, blender::float3 &min, blender::float3 &max)
void PE_update_object(Depsgraph *depsgraph, Scene *scene, Object *ob, int useflag)
static void PE_create_random_generator(PEData *data)
void PARTICLE_OT_edited_clear(wmOperatorType *ot)
void(*)(PEData *data, int point_index, float mouse_distance) ForHitPointFunc
static bool shape_cut_poll(bContext *C)
static void select_more_keys(PEData *data, int point_index)
void(*)(PEData *data, float mat[4][4], float imat[4][4], int point_index, int key_index, PTCacheEditKey *key, float mouse_distance) ForHitKeyMatFunc
void PARTICLE_OT_mirror(wmOperatorType *ot)
static bool point_is_selected(PTCacheEditPoint *point)
static void pe_iterate_lengths(Scene *scene, PTCacheEdit *edit)
static void nearest_key_fn(PEData *data, int point_index, int key_index, bool)
void ED_object_particle_edit_mode_enter(bContext *C)
static void pe_update_hair_particle_edit_pointers(PTCacheEdit *edit)
static int brush_edit_exec(bContext *C, wmOperator *op)
static void BKE_brush_weight_get(PEData *data, float[4][4], float[4][4], int point_index, int key_index, PTCacheEditKey *, float)
static void for_mouse_hit_keys(PEData *data, ForKeyFunc func, const enum eParticleSelectFlag flag)
void PARTICLE_OT_select_random(wmOperatorType *ot)
bool PE_poll_view3d(bContext *C)
static float calculate_average_length(PTCacheEdit *edit)
static void update_velocities(PTCacheEdit *edit)
void PARTICLE_OT_select_linked(wmOperatorType *ot)
void PARTICLE_OT_reveal(wmOperatorType *ot)
static int particle_intersect_mesh(Depsgraph *depsgraph, Scene *, Object *ob, Mesh *mesh, float *vert_cos, const float co1[3], const float co2[3], float *min_d, int *min_face, float *min_w, float *face_minmax, float *pa_minmax, float radius, float *ipoint)
static bool key_inside_test(PEData *data, const float co[3])
void PE_free_ptcache_edit(PTCacheEdit *edit)
static void foreach_point(PEData *data, ForPointFunc func)
static bool mirror_poll(bContext *C)
static void select_key(PEData *data, int point_index, int key_index, bool)
static void set_delete_particle_key(PEData *data, int pa_index, int key_index, bool)
void PARTICLE_OT_subdivide(wmOperatorType *ot)
static void brush_edit_apply(bContext *C, wmOperator *op, PointerRNA *itemptr)
static void PE_data_free(PEData *data)
void PE_hide_keys_time(Scene *scene, PTCacheEdit *edit, float cfra)
void PE_create_particle_edit(Depsgraph *depsgraph, Scene *scene, Object *ob, PointCache *cache, ParticleSystem *psys)
static void pe_select_cache_init_with_generic_userdata(bContext *C, wmGenericUserData *wm_userdata)
static void brush_puff(PEData *data, int point_index, float mouse_distance)
static bool PE_create_shape_tree(PEData *data, Object *shapeob)
static int shape_cut_exec(bContext *C, wmOperator *)
void update_world_cos(Object *ob, PTCacheEdit *edit)
static int select_linked_pick_exec(bContext *C, wmOperator *op)
ParticleEditSettings * PE_settings(Scene *scene)
static void apply_lengths_iter(void *__restrict iter_data_v, const int iter, const TaskParallelTLS *__restrict)
static int unify_length_exec(bContext *C, wmOperator *)
void PARTICLE_OT_select_more(wmOperatorType *ot)
static int reveal_exec(bContext *C, wmOperator *op)
static int mirror_exec(bContext *C, wmOperator *)
void PARTICLE_OT_rekey(wmOperatorType *ot)
static int rekey_exec(bContext *C, wmOperator *op)
static void rekey_particle(PEData *data, int pa_index)
static void PE_free_shape_tree(PEData *data)
static int count_selected_keys(Scene *scene, PTCacheEdit *edit)
static const EnumPropertyItem delete_type_items[]
static void brush_edit_cancel(bContext *, wmOperator *op)
static int brush_edit_modal(bContext *C, wmOperator *op, const wmEvent *event)
static void subdivide_particle(PEData *data, int pa_index)
static void select_keys(PEData *data, int point_index, int, bool)
void PARTICLE_OT_select_all(wmOperatorType *ot)
void recalc_lengths(PTCacheEdit *edit)
static int select_roots_exec(bContext *C, wmOperator *op)
static float pe_brush_size_get(const Scene *, ParticleBrushData *brush)
void PARTICLE_OT_particle_edit_toggle(wmOperatorType *ot)
static void brush_add_count_iter(void *__restrict iter_data_v, const int iter, const TaskParallelTLS *__restrict tls_v)
PTCacheEdit * PE_get_current(Depsgraph *depsgraph, Scene *scene, Object *ob)
static void brush_drawcursor(bContext *C, int x, int y, void *)
void PARTICLE_OT_select_tips(wmOperatorType *ot)
@ RAN_HAIR
@ RAN_POINTS
static void select_less_keys(PEData *data, int point_index)
static bool brush_edit_poll(bContext *C)
static void brush_smooth_get(PEData *data, float mat[4][4], float[4][4], int, int key_index, PTCacheEditKey *key, float)
void ED_object_particle_edit_mode_exit(bContext *C)
static void brush_length(PEData *data, int point_index, float)
int PE_lasso_select(bContext *C, const int mcoords[][2], const int mcoords_len, const int sel_op)
static void brush_smooth_do(PEData *data, float[4][4], float imat[4][4], int point_index, int key_index, PTCacheEditKey *key, float)
static int brush_edit_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static void PE_apply_mirror(Object *ob, ParticleSystem *psys)
static void toggle_particle_cursor(Scene *scene, bool enable)
static bool select_action_apply(PTCacheEditPoint *point, PTCacheEditKey *key, int action)
static void shape_cut(PEData *data, int pa_index)
int PE_start_edit(PTCacheEdit *edit)
static int delete_exec(bContext *C, wmOperator *op)
bool PE_circle_select(bContext *C, wmGenericUserData *wm_userdata, const int sel_op, const int mval[2], float rad)
static int weight_set_exec(bContext *C, wmOperator *op)
static void scale_points_to_length(PTCacheEdit *edit, float length)
static void brush_edit_apply_event(bContext *C, wmOperator *op, const wmEvent *event)
static int clear_edited_exec(bContext *C, wmOperator *)
static void select_root(PEData *data, int point_index)
static void foreach_mouse_hit_key(PEData *data, ForHitKeyMatFunc func, int selected)
static void scale_point_factor(PTCacheEditPoint *point, float factor)
void ED_object_particle_edit_mode_enter_ex(Depsgraph *depsgraph, Scene *scene, Object *ob)
static bool particle_edit_toggle_poll(bContext *C)
static void brush_add_count_iter_free(const void *__restrict, void *__restrict chunk_v)
static int select_linked_exec(bContext *C, wmOperator *)
static void brush_cut(PEData *data, int pa_index)
bool PE_box_select(bContext *C, const rcti *rect, const int sel_op)
static void remove_tagged_keys(Depsgraph *depsgraph, Object *ob, ParticleSystem *psys)
void(*)(PEData *data, int point_index, int key_index, bool is_inside) ForKeyFunc
bool PE_deselect_all_visible(bContext *C)
@ DEL_KEY
@ DEL_PARTICLE
static void PE_mirror_particle(Object *ob, Mesh *mesh, ParticleSystem *psys, ParticleData *pa, ParticleData *mpa)
static int pe_select_all_exec(bContext *C, wmOperator *op)
static int subdivide_exec(bContext *C, wmOperator *)
static int remove_tagged_particles(Object *ob, ParticleSystem *psys, int mirror)
void PARTICLE_OT_unify_length(wmOperatorType *ot)
void PARTICLE_OT_hide(wmOperatorType *ot)
void PARTICLE_OT_select_linked_pick(wmOperatorType *ot)
static void select_key_op(PEData *data, int point_index, int key_index, bool is_inside)
static void PE_free_random_generator(PEData *data)
static PTCacheEdit * pe_get_current(Depsgraph *depsgraph, Scene *scene, Object *ob, bool create)
void(*)(PEData *data, const float mat[4][4], const float imat[4][4], int point_index, int key_index, PTCacheEditKey *key) ForKeyMatFunc
static void iterate_lengths_iter(void *__restrict iter_data_v, const int iter, const TaskParallelTLS *__restrict)
static int select_tips_exec(bContext *C, wmOperator *op)
static void intersect_dm_quad_weights(const float v1[3], const float v2[3], const float v3[3], const float v4[3], float w[4])
static void PE_update_mirror_cache(Object *ob, ParticleSystem *psys)
static void foreach_selected_point(PEData *data, ForPointFunc func)
void PARTICLE_OT_brush_edit(wmOperatorType *ot)
static void PE_update_selection(Depsgraph *depsgraph, Scene *scene, Object *ob, int useflag)
PTCacheEdit * PE_create_current(Depsgraph *depsgraph, Scene *scene, Object *ob)
#define KEY_WCO
#define LOOP_TAGGED_KEYS
#define LOOP_UNSELECTED_POINTS
#define LOOP_SELECTED_POINTS
#define POINT_P
#define LOOP_KEYS
#define LOOP_SELECTED_KEYS
#define KEY_K
#define LOOP_TAGGED_POINTS
#define LOOP_EDITED_POINTS
#define LOOP_VISIBLE_KEYS
#define LOOP_POINTS
#define LOOP_VISIBLE_POINTS
void RNA_int_set_array(PointerRNA *ptr, const char *name, const int *values)
void RNA_int_get_array(PointerRNA *ptr, const char *name, int *values)
void RNA_boolean_set(PointerRNA *ptr, const char *name, bool value)
void RNA_float_get_array(PointerRNA *ptr, const char *name, float *values)
void RNA_collection_add(PointerRNA *ptr, const char *name, PointerRNA *r_value)
int RNA_int_get(PointerRNA *ptr, const char *name)
float RNA_float_get(PointerRNA *ptr, const char *name)
bool RNA_boolean_get(PointerRNA *ptr, const char *name)
void RNA_float_set_array(PointerRNA *ptr, const char *name, const float *values)
int RNA_enum_get(PointerRNA *ptr, const char *name)
PropertyRNA * RNA_def_float(StructOrFunctionRNA *cont_, const char *identifier, const float default_value, const float hardmin, const float hardmax, const char *ui_name, const char *ui_description, const float softmin, const float softmax)
PropertyRNA * RNA_def_enum(StructOrFunctionRNA *cont_, const char *identifier, const EnumPropertyItem *items, const int default_value, const char *ui_name, const char *ui_description)
PropertyRNA * RNA_def_int_vector(StructOrFunctionRNA *cont_, const char *identifier, const int len, const int *default_value, const int hardmin, const int hardmax, const char *ui_name, const char *ui_description, const int softmin, const int softmax)
PropertyRNA * RNA_def_collection_runtime(StructOrFunctionRNA *cont_, const char *identifier, StructRNA *type, const char *ui_name, const char *ui_description)
PropertyRNA * RNA_def_boolean(StructOrFunctionRNA *cont_, const char *identifier, const bool default_value, const char *ui_name, const char *ui_description)
void RNA_def_property_flag(PropertyRNA *prop, PropertyFlag flag)
PropertyRNA * RNA_def_int(StructOrFunctionRNA *cont_, const char *identifier, const int default_value, const int hardmin, const int hardmax, const char *ui_name, const char *ui_description, const int softmin, const int softmax)
#define min(a, b)
Definition sort.c:32
#define FLT_MAX
Definition stdcycles.h:14
BVHTree_RayCastCallback raycast_callback
ViewLayer * view_layer
PTCacheEdit * edit
ParticleSystem * psys
ParticleEditSettings * pset
ForHitKeyMatFunc func
PTCacheEdit * edit
void * first
unsigned int v2
unsigned int v1
unsigned int v4
unsigned int v3
MeshRuntimeHandle * runtime
CustomData fdata_legacy
PTCacheEditPoint * point
PTCacheEditKey * key
ListBase particlesystem
ObjectRuntimeHandle * runtime
ViewDepths * depths
ViewContext vc
float pufffac
float combfac
Depsgraph * depsgraph
float * dvec
eSelectOp sel_op
float weightfac
float vec[3]
bool is_changed
const rcti * rect
const int * mval
float cutfac
PTCacheEdit * edit
int select_action
Scene * scene
BVHTreeFromMesh shape_bvh
Mesh * mesh
int select_toggle_action
ViewLayer * view_layer
Object * ob
float smoothfac
const bContext * context
void * user_data
Main * bmain
float growfac
struct PTCacheEditKey * keys
ListBase pathcachebufs
struct ParticleSystemModifierData * psmd
struct ParticleCacheKey ** pathcache
float * emitter_cosnos
PTCacheEditPoint * points
struct ParticleSystem * psys
struct ParticleSystemModifierData * psmd_eval
struct KDTree_3d * emitter_field
struct PTCacheID pid
struct ParticleSystem * psys_eval
void * calldata
unsigned int type
unsigned int flag
struct PTCacheID * next
struct PointCache * cache
ParticleKey state
ParticleBrushData brush[7]
struct Object * shape_object
struct Depsgraph * depsgraph
struct ParticleSystemModifierData * psmd
struct Scene * scene
struct ParticleSystem * psys
struct Object * ob
ChildParticle * child
struct PTCacheEdit * edit
ParticleData * particles
ParticleSettings * part
struct PointCache * pointcache
void(* free_edit)(struct PTCacheEdit *edit)
struct ListBase mem_cache
struct PTCacheEdit * edit
void(* free_edit)(struct PTCacheEdit *edit)
BVHTreeFromMesh bvhdata
Definition rand.cc:33
unsigned short w
Definition ED_view3d.hh:82
float * depths
Definition ED_view3d.hh:85
unsigned short h
Definition ED_view3d.hh:82
short val
Definition WM_types.hh:724
int mval[2]
Definition WM_types.hh:728
uint8_t modifier
Definition WM_types.hh:739
short type
Definition WM_types.hh:722
wmGenericUserDataFreeFn free_fn
Definition WM_types.hh:143
const char * name
Definition WM_types.hh:990
bool(* poll)(bContext *C) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1042
const char * idname
Definition WM_types.hh:992
int(* modal)(bContext *C, wmOperator *op, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1036
int(* invoke)(bContext *C, wmOperator *op, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1022
int(* exec)(bContext *C, wmOperator *op) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1006
const char * description
Definition WM_types.hh:996
PropertyRNA * prop
Definition WM_types.hh:1092
StructRNA * srna
Definition WM_types.hh:1080
void(* cancel)(bContext *C, wmOperator *op)
Definition WM_types.hh:1028
struct ReportList * reports
struct PointerRNA * ptr
float max
void WM_main_add_notifier(uint type, void *reference)
wmEventHandler_Op * WM_event_add_modal_handler(bContext *C, wmOperator *op)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
@ RIGHTMOUSE
@ MOUSEMOVE
@ LEFTMOUSE
@ MIDDLEMOUSE
wmOperatorType * ot
Definition wm_files.cc:4125
#define WM_msg_publish_rna_prop(mbus, id_, data_, type_, prop_)
int WM_operator_properties_select_random_seed_increment_get(wmOperator *op)
void WM_operator_properties_select_action(wmOperatorType *ot, int default_action, bool hide_gui)
void WM_operator_properties_select_random(wmOperatorType *ot)
void WM_operator_properties_select_all(wmOperatorType *ot)
int WM_menu_invoke(bContext *C, wmOperator *op, const wmEvent *)
bool WM_paint_cursor_end(wmPaintCursor *handle)
int WM_operator_props_popup(bContext *C, wmOperator *op, const wmEvent *)
wmPaintCursor * WM_paint_cursor_activate(short space_type, short region_type, bool(*poll)(bContext *C), wmPaintCursorDraw draw, void *customdata)
bool WM_toolsystem_active_tool_is_brush(const bContext *C)
void WM_toolsystem_update_from_context_view3d(bContext *C)
uint8_t flag
Definition wm_window.cc:138