The hair mirroring code seems to expect that hair is emitted from faces. The PE_mirror_x contains the following expression: mirrorfaces[pa->num * 2]. This only makes sense when pa->num is a face index. The simplest short term solution is to disable the mirror operator when the particles haven't been emitted from faces. Diffferential Revision: https://developer.blender.org/D10002
		
			
				
	
	
		
			5537 lines
		
	
	
		
			145 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			5537 lines
		
	
	
		
			145 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * This program is free software; you can redistribute it and/or
 | |
|  * modify it under the terms of the GNU General Public License
 | |
|  * as published by the Free Software Foundation; either version 2
 | |
|  * of the License, or (at your option) any later version.
 | |
|  *
 | |
|  * This program is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|  * GNU General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU General Public License
 | |
|  * along with this program; if not, write to the Free Software Foundation,
 | |
|  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 | |
|  *
 | |
|  * The Original Code is Copyright (C) 2007 by Janne Karhu.
 | |
|  * All rights reserved.
 | |
|  */
 | |
| 
 | |
| /** \file
 | |
|  * \ingroup edphys
 | |
|  */
 | |
| 
 | |
| #include <math.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| 
 | |
| #include "MEM_guardedalloc.h"
 | |
| 
 | |
| #include "DNA_mesh_types.h"
 | |
| #include "DNA_meshdata_types.h"
 | |
| #include "DNA_scene_types.h"
 | |
| #include "DNA_screen_types.h"
 | |
| #include "DNA_space_types.h"
 | |
| #include "DNA_view3d_types.h"
 | |
| 
 | |
| #include "BLI_kdtree.h"
 | |
| #include "BLI_lasso_2d.h"
 | |
| #include "BLI_listbase.h"
 | |
| #include "BLI_math.h"
 | |
| #include "BLI_rand.h"
 | |
| #include "BLI_rect.h"
 | |
| #include "BLI_task.h"
 | |
| #include "BLI_utildefines.h"
 | |
| 
 | |
| #include "BKE_bvhutils.h"
 | |
| #include "BKE_context.h"
 | |
| #include "BKE_global.h"
 | |
| #include "BKE_main.h"
 | |
| #include "BKE_mesh.h"
 | |
| #include "BKE_mesh_runtime.h"
 | |
| #include "BKE_modifier.h"
 | |
| #include "BKE_object.h"
 | |
| #include "BKE_particle.h"
 | |
| #include "BKE_pointcache.h"
 | |
| #include "BKE_report.h"
 | |
| #include "BKE_scene.h"
 | |
| 
 | |
| #include "DEG_depsgraph.h"
 | |
| 
 | |
| #include "ED_mesh.h"
 | |
| #include "ED_object.h"
 | |
| #include "ED_particle.h"
 | |
| #include "ED_physics.h"
 | |
| #include "ED_screen.h"
 | |
| #include "ED_select_utils.h"
 | |
| #include "ED_view3d.h"
 | |
| 
 | |
| #include "GPU_immediate.h"
 | |
| #include "GPU_immediate_util.h"
 | |
| #include "GPU_state.h"
 | |
| 
 | |
| #include "UI_resources.h"
 | |
| 
 | |
| #include "WM_api.h"
 | |
| #include "WM_message.h"
 | |
| #include "WM_toolsystem.h"
 | |
| #include "WM_types.h"
 | |
| 
 | |
| #include "RNA_access.h"
 | |
| #include "RNA_define.h"
 | |
| 
 | |
| #include "DEG_depsgraph_query.h"
 | |
| 
 | |
| #include "PIL_time_utildefines.h"
 | |
| 
 | |
| #include "physics_intern.h"
 | |
| 
 | |
| #include "particle_edit_utildefines.h"
 | |
| 
 | |
| /**************************** utilities *******************************/
 | |
| 
 | |
| bool PE_poll(bContext *C)
 | |
| {
 | |
|   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
| 
 | |
|   if (!scene || !ob || !(ob->mode & OB_MODE_PARTICLE_EDIT)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
|   if (edit == NULL) {
 | |
|     return false;
 | |
|   }
 | |
|   if (edit->psmd_eval == NULL || edit->psmd_eval->mesh_final == NULL) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool PE_hair_poll(bContext *C)
 | |
| {
 | |
|   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
| 
 | |
|   if (!scene || !ob || !(ob->mode & OB_MODE_PARTICLE_EDIT)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
|   if (edit == NULL || edit->psys == NULL) {
 | |
|     return false;
 | |
|   }
 | |
|   if (edit->psmd_eval == NULL || edit->psmd_eval->mesh_final == NULL) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool PE_poll_view3d(bContext *C)
 | |
| {
 | |
|   ScrArea *area = CTX_wm_area(C);
 | |
|   ARegion *region = CTX_wm_region(C);
 | |
| 
 | |
|   return (PE_poll(C) && (area && area->spacetype == SPACE_VIEW3D) &&
 | |
|           (region && region->regiontype == RGN_TYPE_WINDOW));
 | |
| }
 | |
| 
 | |
| void PE_free_ptcache_edit(PTCacheEdit *edit)
 | |
| {
 | |
|   POINT_P;
 | |
| 
 | |
|   if (edit == 0) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (edit->points) {
 | |
|     LOOP_POINTS {
 | |
|       if (point->keys) {
 | |
|         MEM_freeN(point->keys);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     MEM_freeN(edit->points);
 | |
|   }
 | |
| 
 | |
|   if (edit->mirror_cache) {
 | |
|     MEM_freeN(edit->mirror_cache);
 | |
|   }
 | |
| 
 | |
|   if (edit->emitter_cosnos) {
 | |
|     MEM_freeN(edit->emitter_cosnos);
 | |
|     edit->emitter_cosnos = 0;
 | |
|   }
 | |
| 
 | |
|   if (edit->emitter_field) {
 | |
|     BLI_kdtree_3d_free(edit->emitter_field);
 | |
|     edit->emitter_field = 0;
 | |
|   }
 | |
| 
 | |
|   psys_free_path_cache(edit->psys, edit);
 | |
| 
 | |
|   MEM_freeN(edit);
 | |
| }
 | |
| 
 | |
| /************************************************/
 | |
| /*          Edit Mode Helpers                   */
 | |
| /************************************************/
 | |
| 
 | |
| int PE_start_edit(PTCacheEdit *edit)
 | |
| {
 | |
|   if (edit) {
 | |
|     edit->edited = 1;
 | |
|     if (edit->psys) {
 | |
|       edit->psys->flag |= PSYS_EDITED;
 | |
|     }
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| ParticleEditSettings *PE_settings(Scene *scene)
 | |
| {
 | |
|   return scene->toolsettings ? &scene->toolsettings->particle : NULL;
 | |
| }
 | |
| 
 | |
| static float pe_brush_size_get(const Scene *UNUSED(scene), ParticleBrushData *brush)
 | |
| {
 | |
| #if 0 /* TODO: Here we can enable unified brush size, needs more work. */
 | |
|   UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
 | |
|   float size = (ups->flag & UNIFIED_PAINT_SIZE) ? ups->size : brush->size;
 | |
| #endif
 | |
| 
 | |
|   return brush->size;
 | |
| }
 | |
| 
 | |
| PTCacheEdit *PE_get_current_from_psys(ParticleSystem *psys)
 | |
| {
 | |
|   if (psys->part && psys->part->type == PART_HAIR) {
 | |
|     if ((psys->flag & PSYS_HAIR_DYNAMICS) != 0 && (psys->pointcache->flag & PTCACHE_BAKED) != 0) {
 | |
|       return psys->pointcache->edit;
 | |
|     }
 | |
|     return psys->edit;
 | |
|   }
 | |
|   if (psys->pointcache->flag & PTCACHE_BAKED) {
 | |
|     return psys->pointcache->edit;
 | |
|   }
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| /* NOTE: Similar to creation of edit, but only updates pointers in the
 | |
|  * existing struct.
 | |
|  */
 | |
| static void pe_update_hair_particle_edit_pointers(PTCacheEdit *edit)
 | |
| {
 | |
|   ParticleSystem *psys = edit->psys;
 | |
|   ParticleData *pa = psys->particles;
 | |
|   for (int p = 0; p < edit->totpoint; p++) {
 | |
|     PTCacheEditPoint *point = &edit->points[p];
 | |
|     HairKey *hair_key = pa->hair;
 | |
|     for (int k = 0; k < point->totkey; k++) {
 | |
|       PTCacheEditKey *key = &point->keys[k];
 | |
|       key->co = hair_key->co;
 | |
|       key->time = &hair_key->time;
 | |
|       key->flag = hair_key->editflag;
 | |
|       if (!(psys->flag & PSYS_GLOBAL_HAIR)) {
 | |
|         key->flag |= PEK_USE_WCO;
 | |
|         hair_key->editflag |= PEK_USE_WCO;
 | |
|       }
 | |
|       hair_key++;
 | |
|     }
 | |
|     pa++;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* always gets at least the first particlesystem even if PSYS_CURRENT flag is not set
 | |
|  *
 | |
|  * note: this function runs on poll, therefore it can runs many times a second
 | |
|  * keep it fast! */
 | |
| static PTCacheEdit *pe_get_current(Depsgraph *depsgraph, Scene *scene, Object *ob, int create)
 | |
| {
 | |
|   ParticleEditSettings *pset = PE_settings(scene);
 | |
|   PTCacheEdit *edit = NULL;
 | |
|   ListBase pidlist;
 | |
|   PTCacheID *pid;
 | |
| 
 | |
|   if (pset == NULL || ob == NULL) {
 | |
|     return NULL;
 | |
|   }
 | |
| 
 | |
|   pset->scene = scene;
 | |
|   pset->object = ob;
 | |
| 
 | |
|   BKE_ptcache_ids_from_object(&pidlist, ob, NULL, 0);
 | |
| 
 | |
|   /* in the case of only one editable thing, set pset->edittype accordingly */
 | |
|   if (BLI_listbase_is_single(&pidlist)) {
 | |
|     pid = pidlist.first;
 | |
|     switch (pid->type) {
 | |
|       case PTCACHE_TYPE_PARTICLES:
 | |
|         pset->edittype = PE_TYPE_PARTICLES;
 | |
|         break;
 | |
|       case PTCACHE_TYPE_SOFTBODY:
 | |
|         pset->edittype = PE_TYPE_SOFTBODY;
 | |
|         break;
 | |
|       case PTCACHE_TYPE_CLOTH:
 | |
|         pset->edittype = PE_TYPE_CLOTH;
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   for (pid = pidlist.first; pid; pid = pid->next) {
 | |
|     if (pset->edittype == PE_TYPE_PARTICLES && pid->type == PTCACHE_TYPE_PARTICLES) {
 | |
|       ParticleSystem *psys = pid->calldata;
 | |
| 
 | |
|       if (psys->flag & PSYS_CURRENT) {
 | |
|         if (psys->part && psys->part->type == PART_HAIR) {
 | |
|           if (psys->flag & PSYS_HAIR_DYNAMICS && psys->pointcache->flag & PTCACHE_BAKED) {
 | |
|             if (create && !psys->pointcache->edit) {
 | |
|               PE_create_particle_edit(depsgraph, scene, ob, pid->cache, NULL);
 | |
|             }
 | |
|             edit = pid->cache->edit;
 | |
|           }
 | |
|           else {
 | |
|             if (create && !psys->edit) {
 | |
|               if (psys->flag & PSYS_HAIR_DONE) {
 | |
|                 PE_create_particle_edit(depsgraph, scene, ob, NULL, psys);
 | |
|               }
 | |
|             }
 | |
|             edit = psys->edit;
 | |
|           }
 | |
|         }
 | |
|         else {
 | |
|           if (create && pid->cache->flag & PTCACHE_BAKED && !pid->cache->edit) {
 | |
|             PE_create_particle_edit(depsgraph, scene, ob, pid->cache, psys);
 | |
|           }
 | |
|           edit = pid->cache->edit;
 | |
|         }
 | |
| 
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|     else if (pset->edittype == PE_TYPE_SOFTBODY && pid->type == PTCACHE_TYPE_SOFTBODY) {
 | |
|       if (create && pid->cache->flag & PTCACHE_BAKED && !pid->cache->edit) {
 | |
|         pset->flag |= PE_FADE_TIME;
 | |
|         // NICE TO HAVE but doesn't work: pset->brushtype = PE_BRUSH_COMB;
 | |
|         PE_create_particle_edit(depsgraph, scene, ob, pid->cache, NULL);
 | |
|       }
 | |
|       edit = pid->cache->edit;
 | |
|       break;
 | |
|     }
 | |
|     else if (pset->edittype == PE_TYPE_CLOTH && pid->type == PTCACHE_TYPE_CLOTH) {
 | |
|       if (create && pid->cache->flag & PTCACHE_BAKED && !pid->cache->edit) {
 | |
|         pset->flag |= PE_FADE_TIME;
 | |
|         // NICE TO HAVE but doesn't work: pset->brushtype = PE_BRUSH_COMB;
 | |
|         PE_create_particle_edit(depsgraph, scene, ob, pid->cache, NULL);
 | |
|       }
 | |
|       edit = pid->cache->edit;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Don't consider inactive or render dependency graphs, since they might be evaluated for a
 | |
|    * different number of children. or have different pointer to evaluated particle system or
 | |
|    * modifier which will also cause troubles. */
 | |
|   if (edit && DEG_is_active(depsgraph)) {
 | |
|     edit->pid = *pid;
 | |
|     if (edit->flags & PT_CACHE_EDIT_UPDATE_PARTICLE_FROM_EVAL) {
 | |
|       if (edit->psys != NULL && edit->psys_eval != NULL) {
 | |
|         psys_copy_particles(edit->psys, edit->psys_eval);
 | |
|         pe_update_hair_particle_edit_pointers(edit);
 | |
|       }
 | |
|       edit->flags &= ~PT_CACHE_EDIT_UPDATE_PARTICLE_FROM_EVAL;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   BLI_freelistN(&pidlist);
 | |
| 
 | |
|   return edit;
 | |
| }
 | |
| 
 | |
| PTCacheEdit *PE_get_current(Depsgraph *depsgraph, Scene *scene, Object *ob)
 | |
| {
 | |
|   return pe_get_current(depsgraph, scene, ob, 0);
 | |
| }
 | |
| 
 | |
| PTCacheEdit *PE_create_current(Depsgraph *depsgraph, Scene *scene, Object *ob)
 | |
| {
 | |
|   return pe_get_current(depsgraph, scene, ob, 1);
 | |
| }
 | |
| 
 | |
| void PE_current_changed(Depsgraph *depsgraph, Scene *scene, Object *ob)
 | |
| {
 | |
|   if (ob->mode == OB_MODE_PARTICLE_EDIT) {
 | |
|     PE_create_current(depsgraph, scene, ob);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void PE_hide_keys_time(Scene *scene, PTCacheEdit *edit, float cfra)
 | |
| {
 | |
|   ParticleEditSettings *pset = PE_settings(scene);
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
| 
 | |
|   if (pset->flag & PE_FADE_TIME && pset->selectmode == SCE_SELECT_POINT) {
 | |
|     LOOP_POINTS {
 | |
|       LOOP_KEYS {
 | |
|         if (fabsf(cfra - *key->time) < pset->fade_frames) {
 | |
|           key->flag &= ~PEK_HIDE;
 | |
|         }
 | |
|         else {
 | |
|           key->flag |= PEK_HIDE;
 | |
|           // key->flag &= ~PEK_SELECT;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   else {
 | |
|     LOOP_POINTS {
 | |
|       LOOP_KEYS {
 | |
|         key->flag &= ~PEK_HIDE;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static int pe_x_mirror(Object *ob)
 | |
| {
 | |
|   if (ob->type == OB_MESH) {
 | |
|     return (((Mesh *)ob->data)->symmetry & ME_SYMMETRY_X);
 | |
|   }
 | |
| 
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /****************** common struct passed to callbacks ******************/
 | |
| 
 | |
| typedef struct PEData {
 | |
|   ViewContext vc;
 | |
| 
 | |
|   const bContext *context;
 | |
|   Main *bmain;
 | |
|   Scene *scene;
 | |
|   ViewLayer *view_layer;
 | |
|   Object *ob;
 | |
|   Mesh *mesh;
 | |
|   PTCacheEdit *edit;
 | |
|   BVHTreeFromMesh shape_bvh;
 | |
|   Depsgraph *depsgraph;
 | |
| 
 | |
|   RNG *rng;
 | |
| 
 | |
|   const int *mval;
 | |
|   const rcti *rect;
 | |
|   float rad;
 | |
|   float dval;
 | |
|   int select;
 | |
|   eSelectOp sel_op;
 | |
| 
 | |
|   float *dvec;
 | |
|   float combfac;
 | |
|   float pufffac;
 | |
|   float cutfac;
 | |
|   float smoothfac;
 | |
|   float weightfac;
 | |
|   float growfac;
 | |
|   int totrekey;
 | |
| 
 | |
|   int invert;
 | |
|   int tot;
 | |
|   float vec[3];
 | |
| 
 | |
|   int select_action;
 | |
|   int select_toggle_action;
 | |
|   bool is_changed;
 | |
| } PEData;
 | |
| 
 | |
| static void PE_set_data(bContext *C, PEData *data)
 | |
| {
 | |
|   memset(data, 0, sizeof(*data));
 | |
| 
 | |
|   data->context = C;
 | |
|   data->bmain = CTX_data_main(C);
 | |
|   data->scene = CTX_data_scene(C);
 | |
|   data->view_layer = CTX_data_view_layer(C);
 | |
|   data->ob = CTX_data_active_object(C);
 | |
|   data->depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
 | |
|   data->edit = PE_get_current(data->depsgraph, data->scene, data->ob);
 | |
| }
 | |
| 
 | |
| static void PE_set_view3d_data(bContext *C, PEData *data)
 | |
| {
 | |
|   PE_set_data(C, data);
 | |
| 
 | |
|   ED_view3d_viewcontext_init(C, &data->vc, data->depsgraph);
 | |
| 
 | |
|   if (!XRAY_ENABLED(data->vc.v3d)) {
 | |
|     if (data->vc.v3d->flag & V3D_INVALID_BACKBUF) {
 | |
|       /* needed or else the draw matrix can be incorrect */
 | |
|       view3d_operator_needs_opengl(C);
 | |
| 
 | |
|       ED_view3d_backbuf_depth_validate(&data->vc);
 | |
|       /* we may need to force an update here by setting the rv3d as dirty
 | |
|        * for now it seems ok, but take care!:
 | |
|        * rv3d->depths->dirty = 1; */
 | |
|       ED_view3d_depth_update(data->vc.region);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool PE_create_shape_tree(PEData *data, Object *shapeob)
 | |
| {
 | |
|   Object *shapeob_eval = DEG_get_evaluated_object(data->depsgraph, shapeob);
 | |
|   Mesh *mesh = BKE_object_get_evaluated_mesh(shapeob_eval);
 | |
| 
 | |
|   memset(&data->shape_bvh, 0, sizeof(data->shape_bvh));
 | |
| 
 | |
|   if (!mesh) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return (BKE_bvhtree_from_mesh_get(&data->shape_bvh, mesh, BVHTREE_FROM_LOOPTRI, 4) != NULL);
 | |
| }
 | |
| 
 | |
| static void PE_free_shape_tree(PEData *data)
 | |
| {
 | |
|   free_bvhtree_from_mesh(&data->shape_bvh);
 | |
| }
 | |
| 
 | |
| static void PE_create_random_generator(PEData *data)
 | |
| {
 | |
|   uint rng_seed = (uint)(PIL_check_seconds_timer_i() & UINT_MAX);
 | |
|   rng_seed ^= POINTER_AS_UINT(data->ob);
 | |
|   rng_seed ^= POINTER_AS_UINT(data->edit);
 | |
|   data->rng = BLI_rng_new(rng_seed);
 | |
| }
 | |
| 
 | |
| static void PE_free_random_generator(PEData *data)
 | |
| {
 | |
|   if (data->rng != NULL) {
 | |
|     BLI_rng_free(data->rng);
 | |
|     data->rng = NULL;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*************************** selection utilities *******************************/
 | |
| 
 | |
| static bool key_test_depth(const PEData *data, const float co[3], const int screen_co[2])
 | |
| {
 | |
|   View3D *v3d = data->vc.v3d;
 | |
|   ViewDepths *vd = data->vc.rv3d->depths;
 | |
|   float depth;
 | |
| 
 | |
|   /* nothing to do */
 | |
|   if (XRAY_ENABLED(v3d)) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   /* used to calculate here but all callers have  the screen_co already, so pass as arg */
 | |
| #if 0
 | |
|   if (ED_view3d_project_int_global(data->vc.region,
 | |
|                                    co,
 | |
|                                    screen_co,
 | |
|                                    V3D_PROJ_TEST_CLIP_BB | V3D_PROJ_TEST_CLIP_WIN |
 | |
|                                        V3D_PROJ_TEST_CLIP_NEAR) != V3D_PROJ_RET_OK) {
 | |
|     return 0;
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   /* check if screen_co is within bounds because brush_cut uses out of screen coords */
 | |
|   if (screen_co[0] >= 0 && screen_co[0] < vd->w && screen_co[1] >= 0 && screen_co[1] < vd->h) {
 | |
|     BLI_assert(vd && vd->depths);
 | |
|     /* we know its not clipped */
 | |
|     depth = vd->depths[screen_co[1] * vd->w + screen_co[0]];
 | |
|   }
 | |
|   else {
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   float win[3];
 | |
|   ED_view3d_project(data->vc.region, co, win);
 | |
| 
 | |
|   if (win[2] - 0.00001f > depth) {
 | |
|     return 0;
 | |
|   }
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| static bool key_inside_circle(const PEData *data, float rad, const float co[3], float *distance)
 | |
| {
 | |
|   float dx, dy, dist;
 | |
|   int screen_co[2];
 | |
| 
 | |
|   /* TODO, should this check V3D_PROJ_TEST_CLIP_BB too? */
 | |
|   if (ED_view3d_project_int_global(data->vc.region, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) !=
 | |
|       V3D_PROJ_RET_OK) {
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   dx = data->mval[0] - screen_co[0];
 | |
|   dy = data->mval[1] - screen_co[1];
 | |
|   dist = sqrtf(dx * dx + dy * dy);
 | |
| 
 | |
|   if (dist > rad) {
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   if (key_test_depth(data, co, screen_co)) {
 | |
|     if (distance) {
 | |
|       *distance = dist;
 | |
|     }
 | |
| 
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static bool key_inside_rect(PEData *data, const float co[3])
 | |
| {
 | |
|   int screen_co[2];
 | |
| 
 | |
|   if (ED_view3d_project_int_global(data->vc.region, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) !=
 | |
|       V3D_PROJ_RET_OK) {
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   if (screen_co[0] > data->rect->xmin && screen_co[0] < data->rect->xmax &&
 | |
|       screen_co[1] > data->rect->ymin && screen_co[1] < data->rect->ymax) {
 | |
|     return key_test_depth(data, co, screen_co);
 | |
|   }
 | |
| 
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static bool key_inside_test(PEData *data, const float co[3])
 | |
| {
 | |
|   if (data->mval) {
 | |
|     return key_inside_circle(data, data->rad, co, NULL);
 | |
|   }
 | |
|   return key_inside_rect(data, co);
 | |
| }
 | |
| 
 | |
| static bool point_is_selected(PTCacheEditPoint *point)
 | |
| {
 | |
|   KEY_K;
 | |
| 
 | |
|   if (point->flag & PEP_HIDE) {
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   LOOP_SELECTED_KEYS {
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*************************** iterators *******************************/
 | |
| 
 | |
| typedef void (*ForPointFunc)(PEData *data, int point_index);
 | |
| typedef void (*ForHitPointFunc)(PEData *data, int point_index, float mouse_distance);
 | |
| 
 | |
| typedef void (*ForKeyFunc)(PEData *data, int point_index, int key_index, bool is_inside);
 | |
| 
 | |
| typedef void (*ForKeyMatFunc)(PEData *data,
 | |
|                               const float mat[4][4],
 | |
|                               const float imat[4][4],
 | |
|                               int point_index,
 | |
|                               int key_index,
 | |
|                               PTCacheEditKey *key);
 | |
| typedef void (*ForHitKeyMatFunc)(PEData *data,
 | |
|                                  float mat[4][4],
 | |
|                                  float imat[4][4],
 | |
|                                  int point_index,
 | |
|                                  int key_index,
 | |
|                                  PTCacheEditKey *key,
 | |
|                                  float mouse_distance);
 | |
| 
 | |
| enum eParticleSelectFlag {
 | |
|   PSEL_NEAREST = (1 << 0),
 | |
|   PSEL_ALL_KEYS = (1 << 1),
 | |
| };
 | |
| 
 | |
| static void for_mouse_hit_keys(PEData *data, ForKeyFunc func, const enum eParticleSelectFlag flag)
 | |
| {
 | |
|   ParticleEditSettings *pset = PE_settings(data->scene);
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
|   int nearest_point, nearest_key;
 | |
|   float dist = data->rad;
 | |
| 
 | |
|   /* in path select mode we have no keys */
 | |
|   if (pset->selectmode == SCE_SELECT_PATH) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nearest_point = -1;
 | |
|   nearest_key = -1;
 | |
| 
 | |
|   LOOP_VISIBLE_POINTS {
 | |
|     if (pset->selectmode == SCE_SELECT_END) {
 | |
|       if (point->totkey) {
 | |
|         /* only do end keys */
 | |
|         key = point->keys + point->totkey - 1;
 | |
| 
 | |
|         if (flag & PSEL_NEAREST) {
 | |
|           if (key_inside_circle(data, dist, KEY_WCO, &dist)) {
 | |
|             nearest_point = p;
 | |
|             nearest_key = point->totkey - 1;
 | |
|           }
 | |
|         }
 | |
|         else {
 | |
|           const bool is_inside = key_inside_test(data, KEY_WCO);
 | |
|           if (is_inside || (flag & PSEL_ALL_KEYS)) {
 | |
|             func(data, p, point->totkey - 1, is_inside);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     else {
 | |
|       /* do all keys */
 | |
|       LOOP_VISIBLE_KEYS {
 | |
|         if (flag & PSEL_NEAREST) {
 | |
|           if (key_inside_circle(data, dist, KEY_WCO, &dist)) {
 | |
|             nearest_point = p;
 | |
|             nearest_key = k;
 | |
|           }
 | |
|         }
 | |
|         else {
 | |
|           const bool is_inside = key_inside_test(data, KEY_WCO);
 | |
|           if (is_inside || (flag & PSEL_ALL_KEYS)) {
 | |
|             func(data, p, k, is_inside);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* do nearest only */
 | |
|   if (flag & PSEL_NEAREST) {
 | |
|     if (nearest_point != -1) {
 | |
|       func(data, nearest_point, nearest_key, true);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void foreach_mouse_hit_point(PEData *data, ForHitPointFunc func, int selected)
 | |
| {
 | |
|   ParticleEditSettings *pset = PE_settings(data->scene);
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
| 
 | |
|   /* all is selected in path mode */
 | |
|   if (pset->selectmode == SCE_SELECT_PATH) {
 | |
|     selected = 0;
 | |
|   }
 | |
| 
 | |
|   LOOP_VISIBLE_POINTS {
 | |
|     if (pset->selectmode == SCE_SELECT_END) {
 | |
|       if (point->totkey) {
 | |
|         /* only do end keys */
 | |
|         key = point->keys + point->totkey - 1;
 | |
| 
 | |
|         if (selected == 0 || key->flag & PEK_SELECT) {
 | |
|           float mouse_distance;
 | |
|           if (key_inside_circle(data, data->rad, KEY_WCO, &mouse_distance)) {
 | |
|             func(data, p, mouse_distance);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     else {
 | |
|       /* do all keys */
 | |
|       LOOP_VISIBLE_KEYS {
 | |
|         if (selected == 0 || key->flag & PEK_SELECT) {
 | |
|           float mouse_distance;
 | |
|           if (key_inside_circle(data, data->rad, KEY_WCO, &mouse_distance)) {
 | |
|             func(data, p, mouse_distance);
 | |
|             break;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| typedef struct KeyIterData {
 | |
|   PEData *data;
 | |
|   PTCacheEdit *edit;
 | |
|   int selected;
 | |
|   ForHitKeyMatFunc func;
 | |
| } KeyIterData;
 | |
| 
 | |
| static void foreach_mouse_hit_key_iter(void *__restrict iter_data_v,
 | |
|                                        const int iter,
 | |
|                                        const TaskParallelTLS *__restrict UNUSED(tls))
 | |
| {
 | |
|   KeyIterData *iter_data = (KeyIterData *)iter_data_v;
 | |
|   PEData *data = iter_data->data;
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   PTCacheEditPoint *point = &edit->points[iter];
 | |
|   if (point->flag & PEP_HIDE) {
 | |
|     return;
 | |
|   }
 | |
|   ParticleSystem *psys = edit->psys;
 | |
|   ParticleSystemModifierData *psmd_eval = iter_data->edit->psmd_eval;
 | |
|   ParticleEditSettings *pset = PE_settings(data->scene);
 | |
|   const int selected = iter_data->selected;
 | |
|   float mat[4][4], imat[4][4];
 | |
|   unit_m4(mat);
 | |
|   unit_m4(imat);
 | |
|   if (pset->selectmode == SCE_SELECT_END) {
 | |
|     if (point->totkey) {
 | |
|       /* only do end keys */
 | |
|       PTCacheEditKey *key = point->keys + point->totkey - 1;
 | |
| 
 | |
|       if (selected == 0 || key->flag & PEK_SELECT) {
 | |
|         float mouse_distance;
 | |
|         if (key_inside_circle(data, data->rad, KEY_WCO, &mouse_distance)) {
 | |
|           if (edit->psys && !(edit->psys->flag & PSYS_GLOBAL_HAIR)) {
 | |
|             psys_mat_hair_to_global(
 | |
|                 data->ob, psmd_eval->mesh_final, psys->part->from, psys->particles + iter, mat);
 | |
|             invert_m4_m4(imat, mat);
 | |
|           }
 | |
|           iter_data->func(data, mat, imat, iter, point->totkey - 1, key, mouse_distance);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   else {
 | |
|     /* do all keys */
 | |
|     PTCacheEditKey *key;
 | |
|     int k;
 | |
|     LOOP_VISIBLE_KEYS {
 | |
|       if (selected == 0 || key->flag & PEK_SELECT) {
 | |
|         float mouse_distance;
 | |
|         if (key_inside_circle(data, data->rad, KEY_WCO, &mouse_distance)) {
 | |
|           if (edit->psys && !(edit->psys->flag & PSYS_GLOBAL_HAIR)) {
 | |
|             psys_mat_hair_to_global(
 | |
|                 data->ob, psmd_eval->mesh_final, psys->part->from, psys->particles + iter, mat);
 | |
|             invert_m4_m4(imat, mat);
 | |
|           }
 | |
|           iter_data->func(data, mat, imat, iter, k, key, mouse_distance);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void foreach_mouse_hit_key(PEData *data, ForHitKeyMatFunc func, int selected)
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   ParticleEditSettings *pset = PE_settings(data->scene);
 | |
|   /* all is selected in path mode */
 | |
|   if (pset->selectmode == SCE_SELECT_PATH) {
 | |
|     selected = 0;
 | |
|   }
 | |
| 
 | |
|   KeyIterData iter_data;
 | |
|   iter_data.data = data;
 | |
|   iter_data.edit = edit;
 | |
|   iter_data.selected = selected;
 | |
|   iter_data.func = func;
 | |
| 
 | |
|   TaskParallelSettings settings;
 | |
|   BLI_parallel_range_settings_defaults(&settings);
 | |
|   BLI_task_parallel_range(0, edit->totpoint, &iter_data, foreach_mouse_hit_key_iter, &settings);
 | |
| }
 | |
| 
 | |
| static void foreach_selected_point(PEData *data, ForPointFunc func)
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   POINT_P;
 | |
| 
 | |
|   LOOP_SELECTED_POINTS {
 | |
|     func(data, p);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void foreach_selected_key(PEData *data, ForKeyFunc func)
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
| 
 | |
|   LOOP_VISIBLE_POINTS {
 | |
|     LOOP_SELECTED_KEYS {
 | |
|       func(data, p, k, true);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void foreach_point(PEData *data, ForPointFunc func)
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   POINT_P;
 | |
| 
 | |
|   LOOP_POINTS {
 | |
|     func(data, p);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static int count_selected_keys(Scene *scene, PTCacheEdit *edit)
 | |
| {
 | |
|   ParticleEditSettings *pset = PE_settings(scene);
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
|   int sel = 0;
 | |
| 
 | |
|   LOOP_VISIBLE_POINTS {
 | |
|     if (pset->selectmode == SCE_SELECT_POINT) {
 | |
|       LOOP_SELECTED_KEYS {
 | |
|         sel++;
 | |
|       }
 | |
|     }
 | |
|     else if (pset->selectmode == SCE_SELECT_END) {
 | |
|       if (point->totkey) {
 | |
|         key = point->keys + point->totkey - 1;
 | |
|         if (key->flag & PEK_SELECT) {
 | |
|           sel++;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return sel;
 | |
| }
 | |
| 
 | |
| /************************************************/
 | |
| /*          Particle Edit Mirroring             */
 | |
| /************************************************/
 | |
| 
 | |
| static void PE_update_mirror_cache(Object *ob, ParticleSystem *psys)
 | |
| {
 | |
|   PTCacheEdit *edit;
 | |
|   ParticleSystemModifierData *psmd_eval;
 | |
|   KDTree_3d *tree;
 | |
|   KDTreeNearest_3d nearest;
 | |
|   HairKey *key;
 | |
|   PARTICLE_P;
 | |
|   float mat[4][4], co[3];
 | |
|   int index, totpart;
 | |
| 
 | |
|   edit = psys->edit;
 | |
|   psmd_eval = edit->psmd_eval;
 | |
|   totpart = psys->totpart;
 | |
| 
 | |
|   if (!psmd_eval->mesh_final) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   tree = BLI_kdtree_3d_new(totpart);
 | |
| 
 | |
|   /* insert particles into kd tree */
 | |
|   LOOP_PARTICLES
 | |
|   {
 | |
|     key = pa->hair;
 | |
|     psys_mat_hair_to_orco(ob, psmd_eval->mesh_final, psys->part->from, pa, mat);
 | |
|     copy_v3_v3(co, key->co);
 | |
|     mul_m4_v3(mat, co);
 | |
|     BLI_kdtree_3d_insert(tree, p, co);
 | |
|   }
 | |
| 
 | |
|   BLI_kdtree_3d_balance(tree);
 | |
| 
 | |
|   /* lookup particles and set in mirror cache */
 | |
|   if (!edit->mirror_cache) {
 | |
|     edit->mirror_cache = MEM_callocN(sizeof(int) * totpart, "PE mirror cache");
 | |
|   }
 | |
| 
 | |
|   LOOP_PARTICLES
 | |
|   {
 | |
|     key = pa->hair;
 | |
|     psys_mat_hair_to_orco(ob, psmd_eval->mesh_final, psys->part->from, pa, mat);
 | |
|     copy_v3_v3(co, key->co);
 | |
|     mul_m4_v3(mat, co);
 | |
|     co[0] = -co[0];
 | |
| 
 | |
|     index = BLI_kdtree_3d_find_nearest(tree, co, &nearest);
 | |
| 
 | |
|     /* this needs a custom threshold still, duplicated for editmode mirror */
 | |
|     if (index != -1 && index != p && (nearest.dist <= 0.0002f)) {
 | |
|       edit->mirror_cache[p] = index;
 | |
|     }
 | |
|     else {
 | |
|       edit->mirror_cache[p] = -1;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* make sure mirrors are in two directions */
 | |
|   LOOP_PARTICLES
 | |
|   {
 | |
|     if (edit->mirror_cache[p]) {
 | |
|       index = edit->mirror_cache[p];
 | |
|       if (edit->mirror_cache[index] != p) {
 | |
|         edit->mirror_cache[p] = -1;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   BLI_kdtree_3d_free(tree);
 | |
| }
 | |
| 
 | |
| static void PE_mirror_particle(
 | |
|     Object *ob, Mesh *mesh, ParticleSystem *psys, ParticleData *pa, ParticleData *mpa)
 | |
| {
 | |
|   HairKey *hkey, *mhkey;
 | |
|   PTCacheEditPoint *point, *mpoint;
 | |
|   PTCacheEditKey *key, *mkey;
 | |
|   PTCacheEdit *edit;
 | |
|   float mat[4][4], mmat[4][4], immat[4][4];
 | |
|   int i, mi, k;
 | |
| 
 | |
|   edit = psys->edit;
 | |
|   i = pa - psys->particles;
 | |
| 
 | |
|   /* find mirrored particle if needed */
 | |
|   if (!mpa) {
 | |
|     if (!edit->mirror_cache) {
 | |
|       PE_update_mirror_cache(ob, psys);
 | |
|     }
 | |
| 
 | |
|     if (!edit->mirror_cache) {
 | |
|       return; /* something went wrong! */
 | |
|     }
 | |
| 
 | |
|     mi = edit->mirror_cache[i];
 | |
|     if (mi == -1) {
 | |
|       return;
 | |
|     }
 | |
|     mpa = psys->particles + mi;
 | |
|   }
 | |
|   else {
 | |
|     mi = mpa - psys->particles;
 | |
|   }
 | |
| 
 | |
|   point = edit->points + i;
 | |
|   mpoint = edit->points + mi;
 | |
| 
 | |
|   /* make sure they have the same amount of keys */
 | |
|   if (pa->totkey != mpa->totkey) {
 | |
|     if (mpa->hair) {
 | |
|       MEM_freeN(mpa->hair);
 | |
|     }
 | |
|     if (mpoint->keys) {
 | |
|       MEM_freeN(mpoint->keys);
 | |
|     }
 | |
| 
 | |
|     mpa->hair = MEM_dupallocN(pa->hair);
 | |
|     mpa->totkey = pa->totkey;
 | |
|     mpoint->keys = MEM_dupallocN(point->keys);
 | |
|     mpoint->totkey = point->totkey;
 | |
| 
 | |
|     mhkey = mpa->hair;
 | |
|     mkey = mpoint->keys;
 | |
|     for (k = 0; k < mpa->totkey; k++, mkey++, mhkey++) {
 | |
|       mkey->co = mhkey->co;
 | |
|       mkey->time = &mhkey->time;
 | |
|       mkey->flag &= ~PEK_SELECT;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* mirror positions and tags */
 | |
|   psys_mat_hair_to_orco(ob, mesh, psys->part->from, pa, mat);
 | |
|   psys_mat_hair_to_orco(ob, mesh, psys->part->from, mpa, mmat);
 | |
|   invert_m4_m4(immat, mmat);
 | |
| 
 | |
|   hkey = pa->hair;
 | |
|   mhkey = mpa->hair;
 | |
|   key = point->keys;
 | |
|   mkey = mpoint->keys;
 | |
|   for (k = 0; k < pa->totkey; k++, hkey++, mhkey++, key++, mkey++) {
 | |
|     copy_v3_v3(mhkey->co, hkey->co);
 | |
|     mul_m4_v3(mat, mhkey->co);
 | |
|     mhkey->co[0] = -mhkey->co[0];
 | |
|     mul_m4_v3(immat, mhkey->co);
 | |
| 
 | |
|     if (key->flag & PEK_TAG) {
 | |
|       mkey->flag |= PEK_TAG;
 | |
|     }
 | |
| 
 | |
|     mkey->length = key->length;
 | |
|   }
 | |
| 
 | |
|   if (point->flag & PEP_TAG) {
 | |
|     mpoint->flag |= PEP_TAG;
 | |
|   }
 | |
|   if (point->flag & PEP_EDIT_RECALC) {
 | |
|     mpoint->flag |= PEP_EDIT_RECALC;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void PE_apply_mirror(Object *ob, ParticleSystem *psys)
 | |
| {
 | |
|   PTCacheEdit *edit;
 | |
|   ParticleSystemModifierData *psmd_eval;
 | |
|   POINT_P;
 | |
| 
 | |
|   if (!psys) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   edit = psys->edit;
 | |
|   psmd_eval = edit->psmd_eval;
 | |
| 
 | |
|   if (psmd_eval == NULL || psmd_eval->mesh_final == NULL) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!edit->mirror_cache) {
 | |
|     PE_update_mirror_cache(ob, psys);
 | |
|   }
 | |
| 
 | |
|   if (!edit->mirror_cache) {
 | |
|     return; /* something went wrong */
 | |
|   }
 | |
| 
 | |
|   /* we delay settings the PARS_EDIT_RECALC for mirrored particles
 | |
|    * to avoid doing mirror twice */
 | |
|   LOOP_POINTS {
 | |
|     if (point->flag & PEP_EDIT_RECALC) {
 | |
|       PE_mirror_particle(ob, psmd_eval->mesh_final, psys, psys->particles + p, NULL);
 | |
| 
 | |
|       if (edit->mirror_cache[p] != -1) {
 | |
|         edit->points[edit->mirror_cache[p]].flag &= ~PEP_EDIT_RECALC;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   LOOP_POINTS {
 | |
|     if (point->flag & PEP_EDIT_RECALC) {
 | |
|       if (edit->mirror_cache[p] != -1) {
 | |
|         edit->points[edit->mirror_cache[p]].flag |= PEP_EDIT_RECALC;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /************************************************/
 | |
| /*          Edit Calculation                    */
 | |
| /************************************************/
 | |
| 
 | |
| typedef struct DeflectEmitterIter {
 | |
|   Object *object;
 | |
|   ParticleSystem *psys;
 | |
|   PTCacheEdit *edit;
 | |
|   float dist;
 | |
|   float emitterdist;
 | |
| } DeflectEmitterIter;
 | |
| 
 | |
| static void deflect_emitter_iter(void *__restrict iter_data_v,
 | |
|                                  const int iter,
 | |
|                                  const TaskParallelTLS *__restrict UNUSED(tls))
 | |
| {
 | |
|   DeflectEmitterIter *iter_data = (DeflectEmitterIter *)iter_data_v;
 | |
|   PTCacheEdit *edit = iter_data->edit;
 | |
|   PTCacheEditPoint *point = &edit->points[iter];
 | |
|   if ((point->flag & PEP_EDIT_RECALC) == 0) {
 | |
|     return;
 | |
|   }
 | |
|   Object *object = iter_data->object;
 | |
|   ParticleSystem *psys = iter_data->psys;
 | |
|   ParticleSystemModifierData *psmd_eval = iter_data->edit->psmd_eval;
 | |
|   PTCacheEditKey *key;
 | |
|   int k;
 | |
|   float hairimat[4][4], hairmat[4][4];
 | |
|   int index;
 | |
|   float *vec, *nor, dvec[3], dot, dist_1st = 0.0f;
 | |
|   const float dist = iter_data->dist;
 | |
|   const float emitterdist = iter_data->emitterdist;
 | |
|   psys_mat_hair_to_object(
 | |
|       object, psmd_eval->mesh_final, psys->part->from, psys->particles + iter, hairmat);
 | |
| 
 | |
|   LOOP_KEYS {
 | |
|     mul_m4_v3(hairmat, key->co);
 | |
|   }
 | |
| 
 | |
|   LOOP_KEYS {
 | |
|     if (k == 0) {
 | |
|       dist_1st = len_v3v3((key + 1)->co, key->co);
 | |
|       dist_1st *= dist * emitterdist;
 | |
|     }
 | |
|     else {
 | |
|       index = BLI_kdtree_3d_find_nearest(edit->emitter_field, key->co, NULL);
 | |
| 
 | |
|       vec = edit->emitter_cosnos + index * 6;
 | |
|       nor = vec + 3;
 | |
| 
 | |
|       sub_v3_v3v3(dvec, key->co, vec);
 | |
| 
 | |
|       dot = dot_v3v3(dvec, nor);
 | |
|       copy_v3_v3(dvec, nor);
 | |
| 
 | |
|       if (dot > 0.0f) {
 | |
|         if (dot < dist_1st) {
 | |
|           normalize_v3(dvec);
 | |
|           mul_v3_fl(dvec, dist_1st - dot);
 | |
|           add_v3_v3(key->co, dvec);
 | |
|         }
 | |
|       }
 | |
|       else {
 | |
|         normalize_v3(dvec);
 | |
|         mul_v3_fl(dvec, dist_1st - dot);
 | |
|         add_v3_v3(key->co, dvec);
 | |
|       }
 | |
|       if (k == 1) {
 | |
|         dist_1st *= 1.3333f;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   invert_m4_m4(hairimat, hairmat);
 | |
| 
 | |
|   LOOP_KEYS {
 | |
|     mul_m4_v3(hairimat, key->co);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* tries to stop edited particles from going through the emitter's surface */
 | |
| static void pe_deflect_emitter(Scene *scene, Object *ob, PTCacheEdit *edit)
 | |
| {
 | |
|   ParticleEditSettings *pset = PE_settings(scene);
 | |
|   ParticleSystem *psys;
 | |
|   const float dist = ED_view3d_select_dist_px() * 0.01f;
 | |
| 
 | |
|   if (edit == NULL || edit->psys == NULL || (pset->flag & PE_DEFLECT_EMITTER) == 0 ||
 | |
|       (edit->psys->flag & PSYS_GLOBAL_HAIR)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   psys = edit->psys;
 | |
| 
 | |
|   if (edit->psmd_eval == NULL || edit->psmd_eval->mesh_final == NULL) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   DeflectEmitterIter iter_data;
 | |
|   iter_data.object = ob;
 | |
|   iter_data.psys = psys;
 | |
|   iter_data.edit = edit;
 | |
|   iter_data.dist = dist;
 | |
|   iter_data.emitterdist = pset->emitterdist;
 | |
| 
 | |
|   TaskParallelSettings settings;
 | |
|   BLI_parallel_range_settings_defaults(&settings);
 | |
|   BLI_task_parallel_range(0, edit->totpoint, &iter_data, deflect_emitter_iter, &settings);
 | |
| }
 | |
| 
 | |
| typedef struct ApplyLengthsIterData {
 | |
|   PTCacheEdit *edit;
 | |
| } ApplyLengthsIterData;
 | |
| 
 | |
| static void apply_lengths_iter(void *__restrict iter_data_v,
 | |
|                                const int iter,
 | |
|                                const TaskParallelTLS *__restrict UNUSED(tls))
 | |
| {
 | |
|   ApplyLengthsIterData *iter_data = (ApplyLengthsIterData *)iter_data_v;
 | |
|   PTCacheEdit *edit = iter_data->edit;
 | |
|   PTCacheEditPoint *point = &edit->points[iter];
 | |
|   if ((point->flag & PEP_EDIT_RECALC) == 0) {
 | |
|     return;
 | |
|   }
 | |
|   PTCacheEditKey *key;
 | |
|   int k;
 | |
|   LOOP_KEYS {
 | |
|     if (k) {
 | |
|       float dv1[3];
 | |
|       sub_v3_v3v3(dv1, key->co, (key - 1)->co);
 | |
|       normalize_v3(dv1);
 | |
|       mul_v3_fl(dv1, (key - 1)->length);
 | |
|       add_v3_v3v3(key->co, (key - 1)->co, dv1);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* force set distances between neighboring keys */
 | |
| static void PE_apply_lengths(Scene *scene, PTCacheEdit *edit)
 | |
| {
 | |
|   ParticleEditSettings *pset = PE_settings(scene);
 | |
| 
 | |
|   if (edit == 0 || (pset->flag & PE_KEEP_LENGTHS) == 0) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (edit->psys && edit->psys->flag & PSYS_GLOBAL_HAIR) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   ApplyLengthsIterData iter_data;
 | |
|   iter_data.edit = edit;
 | |
| 
 | |
|   TaskParallelSettings settings;
 | |
|   BLI_parallel_range_settings_defaults(&settings);
 | |
|   BLI_task_parallel_range(0, edit->totpoint, &iter_data, apply_lengths_iter, &settings);
 | |
| }
 | |
| 
 | |
| typedef struct IterateLengthsIterData {
 | |
|   PTCacheEdit *edit;
 | |
|   ParticleEditSettings *pset;
 | |
| } IterateLengthsIterData;
 | |
| 
 | |
| static void iterate_lengths_iter(void *__restrict iter_data_v,
 | |
|                                  const int iter,
 | |
|                                  const TaskParallelTLS *__restrict UNUSED(tls))
 | |
| {
 | |
|   IterateLengthsIterData *iter_data = (IterateLengthsIterData *)iter_data_v;
 | |
|   PTCacheEdit *edit = iter_data->edit;
 | |
|   PTCacheEditPoint *point = &edit->points[iter];
 | |
|   if ((point->flag & PEP_EDIT_RECALC) == 0) {
 | |
|     return;
 | |
|   }
 | |
|   ParticleEditSettings *pset = iter_data->pset;
 | |
|   float tlen;
 | |
|   float dv0[3] = {0.0f, 0.0f, 0.0f};
 | |
|   float dv1[3] = {0.0f, 0.0f, 0.0f};
 | |
|   float dv2[3] = {0.0f, 0.0f, 0.0f};
 | |
|   for (int j = 1; j < point->totkey; j++) {
 | |
|     PTCacheEditKey *key;
 | |
|     int k;
 | |
|     float mul = 1.0f / (float)point->totkey;
 | |
|     if (pset->flag & PE_LOCK_FIRST) {
 | |
|       key = point->keys + 1;
 | |
|       k = 1;
 | |
|       dv1[0] = dv1[1] = dv1[2] = 0.0;
 | |
|     }
 | |
|     else {
 | |
|       key = point->keys;
 | |
|       k = 0;
 | |
|       dv0[0] = dv0[1] = dv0[2] = 0.0;
 | |
|     }
 | |
| 
 | |
|     for (; k < point->totkey; k++, key++) {
 | |
|       if (k) {
 | |
|         sub_v3_v3v3(dv0, (key - 1)->co, key->co);
 | |
|         tlen = normalize_v3(dv0);
 | |
|         mul_v3_fl(dv0, (mul * (tlen - (key - 1)->length)));
 | |
|       }
 | |
|       if (k < point->totkey - 1) {
 | |
|         sub_v3_v3v3(dv2, (key + 1)->co, key->co);
 | |
|         tlen = normalize_v3(dv2);
 | |
|         mul_v3_fl(dv2, mul * (tlen - key->length));
 | |
|       }
 | |
|       if (k) {
 | |
|         add_v3_v3((key - 1)->co, dv1);
 | |
|       }
 | |
|       add_v3_v3v3(dv1, dv0, dv2);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* try to find a nice solution to keep distances between neighboring keys */
 | |
| static void pe_iterate_lengths(Scene *scene, PTCacheEdit *edit)
 | |
| {
 | |
|   ParticleEditSettings *pset = PE_settings(scene);
 | |
|   if (edit == 0 || (pset->flag & PE_KEEP_LENGTHS) == 0) {
 | |
|     return;
 | |
|   }
 | |
|   if (edit->psys && edit->psys->flag & PSYS_GLOBAL_HAIR) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   IterateLengthsIterData iter_data;
 | |
|   iter_data.edit = edit;
 | |
|   iter_data.pset = pset;
 | |
| 
 | |
|   TaskParallelSettings settings;
 | |
|   BLI_parallel_range_settings_defaults(&settings);
 | |
|   BLI_task_parallel_range(0, edit->totpoint, &iter_data, iterate_lengths_iter, &settings);
 | |
| }
 | |
| 
 | |
| /* set current distances to be kept between neighboring keys */
 | |
| void recalc_lengths(PTCacheEdit *edit)
 | |
| {
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
| 
 | |
|   if (edit == 0) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   LOOP_EDITED_POINTS {
 | |
|     key = point->keys;
 | |
|     for (k = 0; k < point->totkey - 1; k++, key++) {
 | |
|       key->length = len_v3v3(key->co, (key + 1)->co);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* calculate a tree for finding nearest emitter's vertice */
 | |
| void recalc_emitter_field(Depsgraph *UNUSED(depsgraph), Object *UNUSED(ob), ParticleSystem *psys)
 | |
| {
 | |
|   PTCacheEdit *edit = psys->edit;
 | |
|   Mesh *mesh = edit->psmd_eval->mesh_final;
 | |
|   float *vec, *nor;
 | |
|   int i, totface /*, totvert*/;
 | |
| 
 | |
|   if (!mesh) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (edit->emitter_cosnos) {
 | |
|     MEM_freeN(edit->emitter_cosnos);
 | |
|   }
 | |
| 
 | |
|   BLI_kdtree_3d_free(edit->emitter_field);
 | |
| 
 | |
|   totface = mesh->totface;
 | |
|   /*totvert=dm->getNumVerts(dm);*/ /*UNUSED*/
 | |
| 
 | |
|   edit->emitter_cosnos = MEM_callocN(sizeof(float[6]) * totface, "emitter cosnos");
 | |
| 
 | |
|   edit->emitter_field = BLI_kdtree_3d_new(totface);
 | |
| 
 | |
|   vec = edit->emitter_cosnos;
 | |
|   nor = vec + 3;
 | |
| 
 | |
|   for (i = 0; i < totface; i++, vec += 6, nor += 6) {
 | |
|     MFace *mface = &mesh->mface[i];
 | |
|     MVert *mvert;
 | |
| 
 | |
|     mvert = &mesh->mvert[mface->v1];
 | |
|     copy_v3_v3(vec, mvert->co);
 | |
|     copy_v3fl_v3s(nor, mvert->no);
 | |
| 
 | |
|     mvert = &mesh->mvert[mface->v2];
 | |
|     add_v3_v3v3(vec, vec, mvert->co);
 | |
|     add_v3fl_v3fl_v3s(nor, nor, mvert->no);
 | |
| 
 | |
|     mvert = &mesh->mvert[mface->v3];
 | |
|     add_v3_v3v3(vec, vec, mvert->co);
 | |
|     add_v3fl_v3fl_v3s(nor, nor, mvert->no);
 | |
| 
 | |
|     if (mface->v4) {
 | |
|       mvert = &mesh->mvert[mface->v4];
 | |
|       add_v3_v3v3(vec, vec, mvert->co);
 | |
|       add_v3fl_v3fl_v3s(nor, nor, mvert->no);
 | |
| 
 | |
|       mul_v3_fl(vec, 0.25);
 | |
|     }
 | |
|     else {
 | |
|       mul_v3_fl(vec, 1.0f / 3.0f);
 | |
|     }
 | |
| 
 | |
|     normalize_v3(nor);
 | |
| 
 | |
|     BLI_kdtree_3d_insert(edit->emitter_field, i, vec);
 | |
|   }
 | |
| 
 | |
|   BLI_kdtree_3d_balance(edit->emitter_field);
 | |
| }
 | |
| 
 | |
| static void PE_update_selection(Depsgraph *depsgraph, Scene *scene, Object *ob, int useflag)
 | |
| {
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
|   HairKey *hkey;
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
| 
 | |
|   /* flag all particles to be updated if not using flag */
 | |
|   if (!useflag) {
 | |
|     LOOP_POINTS {
 | |
|       point->flag |= PEP_EDIT_RECALC;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* flush edit key flag to hair key flag to preserve selection
 | |
|    * on save */
 | |
|   if (edit->psys) {
 | |
|     LOOP_POINTS {
 | |
|       hkey = edit->psys->particles[p].hair;
 | |
|       LOOP_KEYS {
 | |
|         hkey->editflag = key->flag;
 | |
|         hkey++;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   psys_cache_edit_paths(depsgraph, scene, ob, edit, CFRA, G.is_rendering);
 | |
| 
 | |
|   /* disable update flag */
 | |
|   LOOP_POINTS {
 | |
|     point->flag &= ~PEP_EDIT_RECALC;
 | |
|   }
 | |
| 
 | |
|   DEG_id_tag_update(&ob->id, ID_RECALC_SELECT);
 | |
| }
 | |
| 
 | |
| void update_world_cos(Object *ob, PTCacheEdit *edit)
 | |
| {
 | |
|   ParticleSystem *psys = edit->psys;
 | |
|   ParticleSystemModifierData *psmd_eval = edit->psmd_eval;
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
|   float hairmat[4][4];
 | |
| 
 | |
|   if (psys == 0 || psys->edit == 0 || psmd_eval == NULL || psmd_eval->mesh_final == NULL) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   LOOP_POINTS {
 | |
|     if (!(psys->flag & PSYS_GLOBAL_HAIR)) {
 | |
|       psys_mat_hair_to_global(
 | |
|           ob, psmd_eval->mesh_final, psys->part->from, psys->particles + p, hairmat);
 | |
|     }
 | |
| 
 | |
|     LOOP_KEYS {
 | |
|       copy_v3_v3(key->world_co, key->co);
 | |
|       if (!(psys->flag & PSYS_GLOBAL_HAIR)) {
 | |
|         mul_m4_v3(hairmat, key->world_co);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| static void update_velocities(PTCacheEdit *edit)
 | |
| {
 | |
|   /*TODO: get frs_sec properly */
 | |
|   float vec1[3], vec2[3], frs_sec, dfra;
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
| 
 | |
|   /* hair doesn't use velocities */
 | |
|   if (edit->psys || !edit->points || !edit->points->keys->vel) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   frs_sec = edit->pid.flag & PTCACHE_VEL_PER_SEC ? 25.0f : 1.0f;
 | |
| 
 | |
|   LOOP_EDITED_POINTS {
 | |
|     LOOP_KEYS {
 | |
|       if (k == 0) {
 | |
|         dfra = *(key + 1)->time - *key->time;
 | |
| 
 | |
|         if (dfra <= 0.0f) {
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         sub_v3_v3v3(key->vel, (key + 1)->co, key->co);
 | |
| 
 | |
|         if (point->totkey > 2) {
 | |
|           sub_v3_v3v3(vec1, (key + 1)->co, (key + 2)->co);
 | |
|           project_v3_v3v3(vec2, vec1, key->vel);
 | |
|           sub_v3_v3v3(vec2, vec1, vec2);
 | |
|           madd_v3_v3fl(key->vel, vec2, 0.5f);
 | |
|         }
 | |
|       }
 | |
|       else if (k == point->totkey - 1) {
 | |
|         dfra = *key->time - *(key - 1)->time;
 | |
| 
 | |
|         if (dfra <= 0.0f) {
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         sub_v3_v3v3(key->vel, key->co, (key - 1)->co);
 | |
| 
 | |
|         if (point->totkey > 2) {
 | |
|           sub_v3_v3v3(vec1, (key - 2)->co, (key - 1)->co);
 | |
|           project_v3_v3v3(vec2, vec1, key->vel);
 | |
|           sub_v3_v3v3(vec2, vec1, vec2);
 | |
|           madd_v3_v3fl(key->vel, vec2, 0.5f);
 | |
|         }
 | |
|       }
 | |
|       else {
 | |
|         dfra = *(key + 1)->time - *(key - 1)->time;
 | |
| 
 | |
|         if (dfra <= 0.0f) {
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         sub_v3_v3v3(key->vel, (key + 1)->co, (key - 1)->co);
 | |
|       }
 | |
|       mul_v3_fl(key->vel, frs_sec / dfra);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void PE_update_object(Depsgraph *depsgraph, Scene *scene, Object *ob, int useflag)
 | |
| {
 | |
|   /* use this to do partial particle updates, not usable when adding or
 | |
|    * removing, then a full redo is necessary and calling this may crash */
 | |
|   ParticleEditSettings *pset = PE_settings(scene);
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
|   POINT_P;
 | |
| 
 | |
|   if (!edit) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   /* flag all particles to be updated if not using flag */
 | |
|   if (!useflag) {
 | |
|     LOOP_POINTS {
 | |
|       point->flag |= PEP_EDIT_RECALC;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* do post process on particle edit keys */
 | |
|   pe_iterate_lengths(scene, edit);
 | |
|   pe_deflect_emitter(scene, ob, edit);
 | |
|   PE_apply_lengths(scene, edit);
 | |
|   if (pe_x_mirror(ob)) {
 | |
|     PE_apply_mirror(ob, edit->psys);
 | |
|   }
 | |
|   if (edit->psys) {
 | |
|     update_world_cos(ob, edit);
 | |
|   }
 | |
|   if (pset->flag & PE_AUTO_VELOCITY) {
 | |
|     update_velocities(edit);
 | |
|   }
 | |
| 
 | |
|   /* Only do this for emitter particles because drawing PE_FADE_TIME is not respected in 2.8 yet
 | |
|    * and flagging with PEK_HIDE will prevent selection. This might get restored once this is
 | |
|    * supported in drawing (but doesn't make much sense for hair anyways). */
 | |
|   if (edit->psys && edit->psys->part->type == PART_EMITTER) {
 | |
|     PE_hide_keys_time(scene, edit, CFRA);
 | |
|   }
 | |
| 
 | |
|   /* regenerate path caches */
 | |
|   psys_cache_edit_paths(depsgraph, scene, ob, edit, CFRA, G.is_rendering);
 | |
| 
 | |
|   /* disable update flag */
 | |
|   LOOP_POINTS {
 | |
|     point->flag &= ~PEP_EDIT_RECALC;
 | |
|   }
 | |
| 
 | |
|   if (edit->psys) {
 | |
|     edit->psys->flag &= ~PSYS_HAIR_UPDATED;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /************************************************/
 | |
| /*          Edit Selections                     */
 | |
| /************************************************/
 | |
| 
 | |
| /*-----selection callbacks-----*/
 | |
| 
 | |
| static void select_key(PEData *data, int point_index, int key_index, bool UNUSED(is_inside))
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   PTCacheEditPoint *point = edit->points + point_index;
 | |
|   PTCacheEditKey *key = point->keys + key_index;
 | |
| 
 | |
|   if (data->select) {
 | |
|     key->flag |= PEK_SELECT;
 | |
|   }
 | |
|   else {
 | |
|     key->flag &= ~PEK_SELECT;
 | |
|   }
 | |
| 
 | |
|   point->flag |= PEP_EDIT_RECALC;
 | |
|   data->is_changed = true;
 | |
| }
 | |
| 
 | |
| static void select_key_op(PEData *data, int point_index, int key_index, bool is_inside)
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   PTCacheEditPoint *point = edit->points + point_index;
 | |
|   PTCacheEditKey *key = point->keys + key_index;
 | |
|   const bool is_select = key->flag & PEK_SELECT;
 | |
|   const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside);
 | |
|   if (sel_op_result != -1) {
 | |
|     SET_FLAG_FROM_TEST(key->flag, sel_op_result, PEK_SELECT);
 | |
|     point->flag |= PEP_EDIT_RECALC;
 | |
|     data->is_changed = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void select_keys(PEData *data,
 | |
|                         int point_index,
 | |
|                         int UNUSED(key_index),
 | |
|                         bool UNUSED(is_inside))
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   PTCacheEditPoint *point = edit->points + point_index;
 | |
|   KEY_K;
 | |
| 
 | |
|   LOOP_KEYS {
 | |
|     if (data->select) {
 | |
|       key->flag |= PEK_SELECT;
 | |
|     }
 | |
|     else {
 | |
|       key->flag &= ~PEK_SELECT;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   point->flag |= PEP_EDIT_RECALC;
 | |
| }
 | |
| 
 | |
| static void extend_key_select(PEData *data, int point_index, int key_index, bool UNUSED(is_inside))
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   PTCacheEditPoint *point = edit->points + point_index;
 | |
|   PTCacheEditKey *key = point->keys + key_index;
 | |
| 
 | |
|   if ((key->flag & PEK_SELECT) == 0) {
 | |
|     key->flag |= PEK_SELECT;
 | |
|     point->flag |= PEP_EDIT_RECALC;
 | |
|     data->is_changed = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void deselect_key_select(PEData *data,
 | |
|                                 int point_index,
 | |
|                                 int key_index,
 | |
|                                 bool UNUSED(is_inside))
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   PTCacheEditPoint *point = edit->points + point_index;
 | |
|   PTCacheEditKey *key = point->keys + key_index;
 | |
| 
 | |
|   if ((key->flag & PEK_SELECT) != 0) {
 | |
|     key->flag &= ~PEK_SELECT;
 | |
|     point->flag |= PEP_EDIT_RECALC;
 | |
|     data->is_changed = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void toggle_key_select(PEData *data, int point_index, int key_index, bool UNUSED(is_inside))
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   PTCacheEditPoint *point = edit->points + point_index;
 | |
|   PTCacheEditKey *key = point->keys + key_index;
 | |
| 
 | |
|   key->flag ^= PEK_SELECT;
 | |
|   point->flag |= PEP_EDIT_RECALC;
 | |
|   data->is_changed = true;
 | |
| }
 | |
| 
 | |
| /************************ de select all operator ************************/
 | |
| 
 | |
| static bool select_action_apply(PTCacheEditPoint *point, PTCacheEditKey *key, int action)
 | |
| {
 | |
|   bool changed = false;
 | |
|   switch (action) {
 | |
|     case SEL_SELECT:
 | |
|       if ((key->flag & PEK_SELECT) == 0) {
 | |
|         key->flag |= PEK_SELECT;
 | |
|         point->flag |= PEP_EDIT_RECALC;
 | |
|         changed = true;
 | |
|       }
 | |
|       break;
 | |
|     case SEL_DESELECT:
 | |
|       if (key->flag & PEK_SELECT) {
 | |
|         key->flag &= ~PEK_SELECT;
 | |
|         point->flag |= PEP_EDIT_RECALC;
 | |
|         changed = true;
 | |
|       }
 | |
|       break;
 | |
|     case SEL_INVERT:
 | |
|       if ((key->flag & PEK_SELECT) == 0) {
 | |
|         key->flag |= PEK_SELECT;
 | |
|         point->flag |= PEP_EDIT_RECALC;
 | |
|         changed = true;
 | |
|       }
 | |
|       else {
 | |
|         key->flag &= ~PEK_SELECT;
 | |
|         point->flag |= PEP_EDIT_RECALC;
 | |
|         changed = true;
 | |
|       }
 | |
|       break;
 | |
|   }
 | |
|   return changed;
 | |
| }
 | |
| 
 | |
| static int pe_select_all_exec(bContext *C, wmOperator *op)
 | |
| {
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
|   int action = RNA_enum_get(op->ptr, "action");
 | |
| 
 | |
|   if (action == SEL_TOGGLE) {
 | |
|     action = SEL_SELECT;
 | |
|     LOOP_VISIBLE_POINTS {
 | |
|       LOOP_SELECTED_KEYS {
 | |
|         action = SEL_DESELECT;
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       if (action == SEL_DESELECT) {
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   bool changed = false;
 | |
|   LOOP_VISIBLE_POINTS {
 | |
|     LOOP_VISIBLE_KEYS {
 | |
|       changed |= select_action_apply(point, key, action);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (changed) {
 | |
|     PE_update_selection(depsgraph, scene, ob, 1);
 | |
|     WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, ob);
 | |
|   }
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_select_all(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "(De)select All";
 | |
|   ot->idname = "PARTICLE_OT_select_all";
 | |
|   ot->description = "(De)select all particles' keys";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = pe_select_all_exec;
 | |
|   ot->poll = PE_poll;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| 
 | |
|   WM_operator_properties_select_all(ot);
 | |
| }
 | |
| 
 | |
| /************************ pick select operator ************************/
 | |
| 
 | |
| bool PE_mouse_particles(bContext *C, const int mval[2], bool extend, bool deselect, bool toggle)
 | |
| {
 | |
|   PEData data;
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
| 
 | |
|   PE_set_view3d_data(C, &data);
 | |
| 
 | |
|   PTCacheEdit *edit = PE_get_current(data.depsgraph, scene, ob);
 | |
| 
 | |
|   if (!PE_start_edit(edit)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (!extend && !deselect && !toggle) {
 | |
|     LOOP_VISIBLE_POINTS {
 | |
|       LOOP_SELECTED_KEYS {
 | |
|         key->flag &= ~PEK_SELECT;
 | |
|         point->flag |= PEP_EDIT_RECALC;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   data.mval = mval;
 | |
|   data.rad = ED_view3d_select_dist_px();
 | |
| 
 | |
|   /* 1 = nearest only */
 | |
|   if (extend) {
 | |
|     for_mouse_hit_keys(&data, extend_key_select, PSEL_NEAREST);
 | |
|   }
 | |
|   else if (deselect) {
 | |
|     for_mouse_hit_keys(&data, deselect_key_select, PSEL_NEAREST);
 | |
|   }
 | |
|   else {
 | |
|     for_mouse_hit_keys(&data, toggle_key_select, PSEL_NEAREST);
 | |
|   }
 | |
| 
 | |
|   if (data.is_changed) {
 | |
|     PE_update_selection(data.depsgraph, scene, ob, 1);
 | |
|     WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, data.ob);
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /************************ select root operator ************************/
 | |
| 
 | |
| static void select_root(PEData *data, int point_index)
 | |
| {
 | |
|   PTCacheEditPoint *point = data->edit->points + point_index;
 | |
|   PTCacheEditKey *key = point->keys;
 | |
| 
 | |
|   if (point->flag & PEP_HIDE) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (data->select_action != SEL_TOGGLE) {
 | |
|     data->is_changed = select_action_apply(point, key, data->select_action);
 | |
|   }
 | |
|   else if (key->flag & PEK_SELECT) {
 | |
|     data->select_toggle_action = SEL_DESELECT;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static int select_roots_exec(bContext *C, wmOperator *op)
 | |
| {
 | |
|   PEData data;
 | |
|   int action = RNA_enum_get(op->ptr, "action");
 | |
| 
 | |
|   PE_set_data(C, &data);
 | |
| 
 | |
|   if (action == SEL_TOGGLE) {
 | |
|     data.select_action = SEL_TOGGLE;
 | |
|     data.select_toggle_action = SEL_SELECT;
 | |
| 
 | |
|     foreach_point(&data, select_root);
 | |
| 
 | |
|     action = data.select_toggle_action;
 | |
|   }
 | |
| 
 | |
|   data.select_action = action;
 | |
|   foreach_point(&data, select_root);
 | |
| 
 | |
|   if (data.is_changed) {
 | |
|     PE_update_selection(data.depsgraph, data.scene, data.ob, 1);
 | |
|     WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, data.ob);
 | |
|   }
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_select_roots(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Select Roots";
 | |
|   ot->idname = "PARTICLE_OT_select_roots";
 | |
|   ot->description = "Select roots of all visible particles";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = select_roots_exec;
 | |
|   ot->poll = PE_poll;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| 
 | |
|   /* properties */
 | |
|   WM_operator_properties_select_action(ot, SEL_SELECT, false);
 | |
| }
 | |
| 
 | |
| /************************ select tip operator ************************/
 | |
| 
 | |
| static void select_tip(PEData *data, int point_index)
 | |
| {
 | |
|   PTCacheEditPoint *point = data->edit->points + point_index;
 | |
|   PTCacheEditKey *key;
 | |
| 
 | |
|   if (point->totkey == 0) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   key = &point->keys[point->totkey - 1];
 | |
| 
 | |
|   if (point->flag & PEP_HIDE) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (data->select_action != SEL_TOGGLE) {
 | |
|     data->is_changed = select_action_apply(point, key, data->select_action);
 | |
|   }
 | |
|   else if (key->flag & PEK_SELECT) {
 | |
|     data->select_toggle_action = SEL_DESELECT;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static int select_tips_exec(bContext *C, wmOperator *op)
 | |
| {
 | |
|   PEData data;
 | |
|   int action = RNA_enum_get(op->ptr, "action");
 | |
| 
 | |
|   PE_set_data(C, &data);
 | |
| 
 | |
|   if (action == SEL_TOGGLE) {
 | |
|     data.select_action = SEL_TOGGLE;
 | |
|     data.select_toggle_action = SEL_SELECT;
 | |
| 
 | |
|     foreach_point(&data, select_tip);
 | |
| 
 | |
|     action = data.select_toggle_action;
 | |
|   }
 | |
| 
 | |
|   data.select_action = action;
 | |
|   foreach_point(&data, select_tip);
 | |
| 
 | |
|   if (data.is_changed) {
 | |
|     PE_update_selection(data.depsgraph, data.scene, data.ob, 1);
 | |
|     WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, data.ob);
 | |
| 
 | |
|     return OPERATOR_FINISHED;
 | |
|   }
 | |
|   return OPERATOR_CANCELLED;
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_select_tips(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Select Tips";
 | |
|   ot->idname = "PARTICLE_OT_select_tips";
 | |
|   ot->description = "Select tips of all visible particles";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = select_tips_exec;
 | |
|   ot->poll = PE_poll;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| 
 | |
|   /* properties */
 | |
|   WM_operator_properties_select_action(ot, SEL_SELECT, false);
 | |
| }
 | |
| 
 | |
| /*********************** select random operator ************************/
 | |
| 
 | |
| enum { RAN_HAIR, RAN_POINTS };
 | |
| 
 | |
| static const EnumPropertyItem select_random_type_items[] = {
 | |
|     {RAN_HAIR, "HAIR", 0, "Hair", ""},
 | |
|     {RAN_POINTS, "POINTS", 0, "Points", ""},
 | |
|     {0, NULL, 0, NULL, NULL},
 | |
| };
 | |
| 
 | |
| static int select_random_exec(bContext *C, wmOperator *op)
 | |
| {
 | |
|   PEData data;
 | |
|   int type;
 | |
| 
 | |
|   /* used by LOOP_VISIBLE_POINTS, LOOP_VISIBLE_KEYS and LOOP_KEYS */
 | |
|   PTCacheEdit *edit;
 | |
|   PTCacheEditPoint *point;
 | |
|   PTCacheEditKey *key;
 | |
|   int p;
 | |
|   int k;
 | |
| 
 | |
|   const float randfac = RNA_float_get(op->ptr, "percent") / 100.0f;
 | |
|   const int seed = WM_operator_properties_select_random_seed_increment_get(op);
 | |
|   const bool select = (RNA_enum_get(op->ptr, "action") == SEL_SELECT);
 | |
|   RNG *rng;
 | |
| 
 | |
|   type = RNA_enum_get(op->ptr, "type");
 | |
| 
 | |
|   PE_set_data(C, &data);
 | |
|   data.select_action = SEL_SELECT;
 | |
|   edit = PE_get_current(data.depsgraph, data.scene, data.ob);
 | |
| 
 | |
|   rng = BLI_rng_new_srandom(seed);
 | |
| 
 | |
|   switch (type) {
 | |
|     case RAN_HAIR:
 | |
|       LOOP_VISIBLE_POINTS {
 | |
|         int flag = ((BLI_rng_get_float(rng) < randfac) == select) ? SEL_SELECT : SEL_DESELECT;
 | |
|         LOOP_KEYS {
 | |
|           data.is_changed |= select_action_apply(point, key, flag);
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
|     case RAN_POINTS:
 | |
|       LOOP_VISIBLE_POINTS {
 | |
|         LOOP_VISIBLE_KEYS {
 | |
|           int flag = ((BLI_rng_get_float(rng) < randfac) == select) ? SEL_SELECT : SEL_DESELECT;
 | |
|           data.is_changed |= select_action_apply(point, key, flag);
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   BLI_rng_free(rng);
 | |
| 
 | |
|   if (data.is_changed) {
 | |
|     PE_update_selection(data.depsgraph, data.scene, data.ob, 1);
 | |
|     WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, data.ob);
 | |
|   }
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_select_random(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Select Random";
 | |
|   ot->idname = "PARTICLE_OT_select_random";
 | |
|   ot->description = "Select a randomly distributed set of hair or points";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = select_random_exec;
 | |
|   ot->poll = PE_poll;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| 
 | |
|   /* properties */
 | |
|   WM_operator_properties_select_random(ot);
 | |
|   ot->prop = RNA_def_enum(ot->srna,
 | |
|                           "type",
 | |
|                           select_random_type_items,
 | |
|                           RAN_HAIR,
 | |
|                           "Type",
 | |
|                           "Select either hair or points");
 | |
| }
 | |
| 
 | |
| /************************ select linked operator ************************/
 | |
| 
 | |
| static int select_linked_exec(bContext *C, wmOperator *UNUSED(op))
 | |
| {
 | |
|   PEData data;
 | |
|   PE_set_data(C, &data);
 | |
|   data.select = true;
 | |
| 
 | |
|   foreach_selected_key(&data, select_keys);
 | |
| 
 | |
|   PE_update_selection(data.depsgraph, data.scene, data.ob, 1);
 | |
|   WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, data.ob);
 | |
| 
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_select_linked(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Select Linked All";
 | |
|   ot->idname = "PARTICLE_OT_select_linked";
 | |
|   ot->description = "Select all keys linked to already selected ones";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = select_linked_exec;
 | |
|   ot->poll = PE_poll;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| 
 | |
|   /* properties */
 | |
| }
 | |
| 
 | |
| static int select_linked_pick_exec(bContext *C, wmOperator *op)
 | |
| {
 | |
|   PEData data;
 | |
|   int mval[2];
 | |
|   int location[2];
 | |
| 
 | |
|   RNA_int_get_array(op->ptr, "location", location);
 | |
|   mval[0] = location[0];
 | |
|   mval[1] = location[1];
 | |
| 
 | |
|   PE_set_view3d_data(C, &data);
 | |
|   data.mval = mval;
 | |
|   data.rad = 75.0f;
 | |
|   data.select = !RNA_boolean_get(op->ptr, "deselect");
 | |
| 
 | |
|   for_mouse_hit_keys(&data, select_keys, PSEL_NEAREST);
 | |
|   PE_update_selection(data.depsgraph, data.scene, data.ob, 1);
 | |
|   WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, data.ob);
 | |
| 
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| static int select_linked_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
 | |
| {
 | |
|   RNA_int_set_array(op->ptr, "location", event->mval);
 | |
|   return select_linked_pick_exec(C, op);
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_select_linked_pick(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Select Linked";
 | |
|   ot->idname = "PARTICLE_OT_select_linked_pick";
 | |
|   ot->description = "Select nearest particle from mouse pointer";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = select_linked_pick_exec;
 | |
|   ot->invoke = select_linked_pick_invoke;
 | |
|   ot->poll = PE_poll_view3d;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| 
 | |
|   /* properties */
 | |
|   RNA_def_boolean(
 | |
|       ot->srna, "deselect", 0, "Deselect", "Deselect linked keys rather than selecting them");
 | |
|   RNA_def_int_vector(ot->srna, "location", 2, NULL, 0, INT_MAX, "Location", "", 0, 16384);
 | |
| }
 | |
| 
 | |
| /************************ box select operator ************************/
 | |
| bool PE_deselect_all_visible_ex(PTCacheEdit *edit)
 | |
| {
 | |
|   bool changed = false;
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
| 
 | |
|   LOOP_VISIBLE_POINTS {
 | |
|     LOOP_SELECTED_KEYS {
 | |
|       if ((key->flag & PEK_SELECT) != 0) {
 | |
|         key->flag &= ~PEK_SELECT;
 | |
|         point->flag |= PEP_EDIT_RECALC;
 | |
|         changed = true;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return changed;
 | |
| }
 | |
| 
 | |
| bool PE_deselect_all_visible(bContext *C)
 | |
| {
 | |
|   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
|   if (!PE_start_edit(edit)) {
 | |
|     return false;
 | |
|   }
 | |
|   return PE_deselect_all_visible_ex(edit);
 | |
| }
 | |
| 
 | |
| bool PE_box_select(bContext *C, const rcti *rect, const int sel_op)
 | |
| {
 | |
|   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
|   PEData data;
 | |
| 
 | |
|   if (!PE_start_edit(edit)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   PE_set_view3d_data(C, &data);
 | |
|   data.rect = rect;
 | |
|   data.sel_op = sel_op;
 | |
| 
 | |
|   if (SEL_OP_USE_PRE_DESELECT(sel_op)) {
 | |
|     data.is_changed = PE_deselect_all_visible_ex(edit);
 | |
|   }
 | |
| 
 | |
|   if (BLI_rcti_is_empty(rect)) {
 | |
|     /* pass */
 | |
|   }
 | |
|   else {
 | |
|     for_mouse_hit_keys(&data, select_key_op, PSEL_ALL_KEYS);
 | |
|   }
 | |
| 
 | |
|   if (data.is_changed) {
 | |
|     PE_update_selection(data.depsgraph, scene, ob, 1);
 | |
|     WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, ob);
 | |
|   }
 | |
|   return data.is_changed;
 | |
| }
 | |
| 
 | |
| /************************ circle select operator ************************/
 | |
| 
 | |
| bool PE_circle_select(bContext *C, const int sel_op, const int mval[2], float rad)
 | |
| {
 | |
|   BLI_assert(ELEM(sel_op, SEL_OP_SET, SEL_OP_ADD, SEL_OP_SUB));
 | |
|   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
|   PEData data;
 | |
| 
 | |
|   if (!PE_start_edit(edit)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   const bool select = (sel_op != SEL_OP_SUB);
 | |
| 
 | |
|   PE_set_view3d_data(C, &data);
 | |
|   data.mval = mval;
 | |
|   data.rad = rad;
 | |
|   data.select = select;
 | |
| 
 | |
|   if (SEL_OP_USE_PRE_DESELECT(sel_op)) {
 | |
|     data.is_changed = PE_deselect_all_visible_ex(edit);
 | |
|   }
 | |
|   for_mouse_hit_keys(&data, select_key, 0);
 | |
|   if (data.is_changed) {
 | |
|     PE_update_selection(data.depsgraph, scene, ob, 1);
 | |
|     WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, ob);
 | |
|   }
 | |
|   return data.is_changed;
 | |
| }
 | |
| 
 | |
| /************************ lasso select operator ************************/
 | |
| 
 | |
| int PE_lasso_select(bContext *C, const int mcoords[][2], const int mcoords_len, const int sel_op)
 | |
| {
 | |
|   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
|   ARegion *region = CTX_wm_region(C);
 | |
|   ParticleEditSettings *pset = PE_settings(scene);
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
|   float co[3], mat[4][4];
 | |
|   int screen_co[2];
 | |
| 
 | |
|   PEData data;
 | |
| 
 | |
|   unit_m4(mat);
 | |
| 
 | |
|   if (!PE_start_edit(edit)) {
 | |
|     return OPERATOR_CANCELLED;
 | |
|   }
 | |
| 
 | |
|   /* only for depths */
 | |
|   PE_set_view3d_data(C, &data);
 | |
| 
 | |
|   if (SEL_OP_USE_PRE_DESELECT(sel_op)) {
 | |
|     data.is_changed |= PE_deselect_all_visible_ex(edit);
 | |
|   }
 | |
| 
 | |
|   ParticleSystem *psys = edit->psys;
 | |
|   ParticleSystemModifierData *psmd_eval = edit->psmd_eval;
 | |
|   LOOP_VISIBLE_POINTS {
 | |
|     if (edit->psys && !(psys->flag & PSYS_GLOBAL_HAIR)) {
 | |
|       psys_mat_hair_to_global(
 | |
|           ob, psmd_eval->mesh_final, psys->part->from, psys->particles + p, mat);
 | |
|     }
 | |
| 
 | |
|     if (pset->selectmode == SCE_SELECT_POINT) {
 | |
|       LOOP_VISIBLE_KEYS {
 | |
|         copy_v3_v3(co, key->co);
 | |
|         mul_m4_v3(mat, co);
 | |
|         const bool is_select = key->flag & PEK_SELECT;
 | |
|         const bool is_inside =
 | |
|             ((ED_view3d_project_int_global(region, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) ==
 | |
|               V3D_PROJ_RET_OK) &&
 | |
|              BLI_lasso_is_point_inside(
 | |
|                  mcoords, mcoords_len, screen_co[0], screen_co[1], IS_CLIPPED) &&
 | |
|              key_test_depth(&data, co, screen_co));
 | |
|         const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside);
 | |
|         if (sel_op_result != -1) {
 | |
|           SET_FLAG_FROM_TEST(key->flag, sel_op_result, PEK_SELECT);
 | |
|           point->flag |= PEP_EDIT_RECALC;
 | |
|           data.is_changed = true;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     else if (pset->selectmode == SCE_SELECT_END) {
 | |
|       if (point->totkey) {
 | |
|         key = point->keys + point->totkey - 1;
 | |
|         copy_v3_v3(co, key->co);
 | |
|         mul_m4_v3(mat, co);
 | |
|         const bool is_select = key->flag & PEK_SELECT;
 | |
|         const bool is_inside =
 | |
|             ((ED_view3d_project_int_global(region, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) ==
 | |
|               V3D_PROJ_RET_OK) &&
 | |
|              BLI_lasso_is_point_inside(
 | |
|                  mcoords, mcoords_len, screen_co[0], screen_co[1], IS_CLIPPED) &&
 | |
|              key_test_depth(&data, co, screen_co));
 | |
|         const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside);
 | |
|         if (sel_op_result != -1) {
 | |
|           SET_FLAG_FROM_TEST(key->flag, sel_op_result, PEK_SELECT);
 | |
|           point->flag |= PEP_EDIT_RECALC;
 | |
|           data.is_changed = true;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (data.is_changed) {
 | |
|     PE_update_selection(data.depsgraph, scene, ob, 1);
 | |
|     WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, ob);
 | |
|     return OPERATOR_FINISHED;
 | |
|   }
 | |
|   return OPERATOR_CANCELLED;
 | |
| }
 | |
| 
 | |
| /*************************** hide operator **************************/
 | |
| 
 | |
| static int hide_exec(bContext *C, wmOperator *op)
 | |
| {
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
 | |
| 
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
| 
 | |
|   if (RNA_boolean_get(op->ptr, "unselected")) {
 | |
|     LOOP_UNSELECTED_POINTS {
 | |
|       point->flag |= PEP_HIDE;
 | |
|       point->flag |= PEP_EDIT_RECALC;
 | |
| 
 | |
|       LOOP_KEYS {
 | |
|         key->flag &= ~PEK_SELECT;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   else {
 | |
|     LOOP_SELECTED_POINTS {
 | |
|       point->flag |= PEP_HIDE;
 | |
|       point->flag |= PEP_EDIT_RECALC;
 | |
| 
 | |
|       LOOP_KEYS {
 | |
|         key->flag &= ~PEK_SELECT;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   PE_update_selection(depsgraph, scene, ob, 1);
 | |
|   WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, ob);
 | |
| 
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_hide(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Hide Selected";
 | |
|   ot->idname = "PARTICLE_OT_hide";
 | |
|   ot->description = "Hide selected particles";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = hide_exec;
 | |
|   ot->poll = PE_poll;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| 
 | |
|   /* props */
 | |
|   RNA_def_boolean(ot->srna, "unselected", 0, "Unselected", "Hide unselected rather than selected");
 | |
| }
 | |
| 
 | |
| /*************************** reveal operator **************************/
 | |
| 
 | |
| static int reveal_exec(bContext *C, wmOperator *op)
 | |
| {
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
|   const bool select = RNA_boolean_get(op->ptr, "select");
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
| 
 | |
|   LOOP_POINTS {
 | |
|     if (point->flag & PEP_HIDE) {
 | |
|       point->flag &= ~PEP_HIDE;
 | |
|       point->flag |= PEP_EDIT_RECALC;
 | |
| 
 | |
|       LOOP_KEYS {
 | |
|         SET_FLAG_FROM_TEST(key->flag, select, PEK_SELECT);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   PE_update_selection(depsgraph, scene, ob, 1);
 | |
|   WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, ob);
 | |
| 
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_reveal(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Reveal";
 | |
|   ot->idname = "PARTICLE_OT_reveal";
 | |
|   ot->description = "Show hidden particles";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = reveal_exec;
 | |
|   ot->poll = PE_poll;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| 
 | |
|   /* props */
 | |
|   RNA_def_boolean(ot->srna, "select", true, "Select", "");
 | |
| }
 | |
| 
 | |
| /************************ select less operator ************************/
 | |
| 
 | |
| static void select_less_keys(PEData *data, int point_index)
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   PTCacheEditPoint *point = edit->points + point_index;
 | |
|   KEY_K;
 | |
| 
 | |
|   LOOP_SELECTED_KEYS {
 | |
|     if (k == 0) {
 | |
|       if (((key + 1)->flag & PEK_SELECT) == 0) {
 | |
|         key->flag |= PEK_TAG;
 | |
|       }
 | |
|     }
 | |
|     else if (k == point->totkey - 1) {
 | |
|       if (((key - 1)->flag & PEK_SELECT) == 0) {
 | |
|         key->flag |= PEK_TAG;
 | |
|       }
 | |
|     }
 | |
|     else {
 | |
|       if ((((key - 1)->flag & (key + 1)->flag) & PEK_SELECT) == 0) {
 | |
|         key->flag |= PEK_TAG;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   LOOP_KEYS {
 | |
|     if ((key->flag & PEK_TAG) && (key->flag & PEK_SELECT)) {
 | |
|       key->flag &= ~(PEK_TAG | PEK_SELECT);
 | |
|       point->flag |= PEP_EDIT_RECALC; /* redraw selection only */
 | |
|       data->is_changed = true;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static int select_less_exec(bContext *C, wmOperator *UNUSED(op))
 | |
| {
 | |
|   PEData data;
 | |
| 
 | |
|   PE_set_data(C, &data);
 | |
|   foreach_point(&data, select_less_keys);
 | |
| 
 | |
|   PE_update_selection(data.depsgraph, data.scene, data.ob, 1);
 | |
|   WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, data.ob);
 | |
| 
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_select_less(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Select Less";
 | |
|   ot->idname = "PARTICLE_OT_select_less";
 | |
|   ot->description = "Deselect boundary selected keys of each particle";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = select_less_exec;
 | |
|   ot->poll = PE_poll;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| }
 | |
| 
 | |
| /************************ select more operator ************************/
 | |
| 
 | |
| static void select_more_keys(PEData *data, int point_index)
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   PTCacheEditPoint *point = edit->points + point_index;
 | |
|   KEY_K;
 | |
| 
 | |
|   LOOP_KEYS {
 | |
|     if (key->flag & PEK_SELECT) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (k == 0) {
 | |
|       if ((key + 1)->flag & PEK_SELECT) {
 | |
|         key->flag |= PEK_TAG;
 | |
|       }
 | |
|     }
 | |
|     else if (k == point->totkey - 1) {
 | |
|       if ((key - 1)->flag & PEK_SELECT) {
 | |
|         key->flag |= PEK_TAG;
 | |
|       }
 | |
|     }
 | |
|     else {
 | |
|       if (((key - 1)->flag | (key + 1)->flag) & PEK_SELECT) {
 | |
|         key->flag |= PEK_TAG;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   LOOP_KEYS {
 | |
|     if ((key->flag & PEK_TAG) && (key->flag & PEK_SELECT) == 0) {
 | |
|       key->flag &= ~PEK_TAG;
 | |
|       key->flag |= PEK_SELECT;
 | |
|       point->flag |= PEP_EDIT_RECALC; /* redraw selection only */
 | |
|       data->is_changed = true;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static int select_more_exec(bContext *C, wmOperator *UNUSED(op))
 | |
| {
 | |
|   PEData data;
 | |
| 
 | |
|   PE_set_data(C, &data);
 | |
|   foreach_point(&data, select_more_keys);
 | |
| 
 | |
|   PE_update_selection(data.depsgraph, data.scene, data.ob, 1);
 | |
|   WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, data.ob);
 | |
| 
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_select_more(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Select More";
 | |
|   ot->idname = "PARTICLE_OT_select_more";
 | |
|   ot->description = "Select keys linked to boundary selected keys of each particle";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = select_more_exec;
 | |
|   ot->poll = PE_poll;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| }
 | |
| 
 | |
| /************************ rekey operator ************************/
 | |
| 
 | |
| static void rekey_particle(PEData *data, int pa_index)
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   ParticleSystem *psys = edit->psys;
 | |
|   ParticleSimulationData sim = {0};
 | |
|   ParticleData *pa = psys->particles + pa_index;
 | |
|   PTCacheEditPoint *point = edit->points + pa_index;
 | |
|   ParticleKey state;
 | |
|   HairKey *key, *new_keys, *okey;
 | |
|   PTCacheEditKey *ekey;
 | |
|   float dval, sta, end;
 | |
|   int k;
 | |
| 
 | |
|   sim.depsgraph = data->depsgraph;
 | |
|   sim.scene = data->scene;
 | |
|   sim.ob = data->ob;
 | |
|   sim.psys = edit->psys;
 | |
| 
 | |
|   pa->flag |= PARS_REKEY;
 | |
| 
 | |
|   key = new_keys = MEM_callocN(data->totrekey * sizeof(HairKey), "Hair re-key keys");
 | |
| 
 | |
|   okey = pa->hair;
 | |
|   /* root and tip stay the same */
 | |
|   copy_v3_v3(key->co, okey->co);
 | |
|   copy_v3_v3((key + data->totrekey - 1)->co, (okey + pa->totkey - 1)->co);
 | |
| 
 | |
|   sta = key->time = okey->time;
 | |
|   end = (key + data->totrekey - 1)->time = (okey + pa->totkey - 1)->time;
 | |
|   dval = (end - sta) / (float)(data->totrekey - 1);
 | |
| 
 | |
|   /* interpolate new keys from old ones */
 | |
|   for (k = 1, key++; k < data->totrekey - 1; k++, key++) {
 | |
|     state.time = (float)k / (float)(data->totrekey - 1);
 | |
|     psys_get_particle_on_path(&sim, pa_index, &state, 0);
 | |
|     copy_v3_v3(key->co, state.co);
 | |
|     key->time = sta + k * dval;
 | |
|   }
 | |
| 
 | |
|   /* replace keys */
 | |
|   if (pa->hair) {
 | |
|     MEM_freeN(pa->hair);
 | |
|   }
 | |
|   pa->hair = new_keys;
 | |
| 
 | |
|   point->totkey = pa->totkey = data->totrekey;
 | |
| 
 | |
|   if (point->keys) {
 | |
|     MEM_freeN(point->keys);
 | |
|   }
 | |
|   ekey = point->keys = MEM_callocN(pa->totkey * sizeof(PTCacheEditKey), "Hair re-key edit keys");
 | |
| 
 | |
|   for (k = 0, key = pa->hair; k < pa->totkey; k++, key++, ekey++) {
 | |
|     ekey->co = key->co;
 | |
|     ekey->time = &key->time;
 | |
|     ekey->flag |= PEK_SELECT;
 | |
|     if (!(psys->flag & PSYS_GLOBAL_HAIR)) {
 | |
|       ekey->flag |= PEK_USE_WCO;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   pa->flag &= ~PARS_REKEY;
 | |
|   point->flag |= PEP_EDIT_RECALC;
 | |
| }
 | |
| 
 | |
| static int rekey_exec(bContext *C, wmOperator *op)
 | |
| {
 | |
|   PEData data;
 | |
| 
 | |
|   PE_set_data(C, &data);
 | |
| 
 | |
|   data.dval = 1.0f / (float)(data.totrekey - 1);
 | |
|   data.totrekey = RNA_int_get(op->ptr, "keys_number");
 | |
| 
 | |
|   foreach_selected_point(&data, rekey_particle);
 | |
| 
 | |
|   recalc_lengths(data.edit);
 | |
|   PE_update_object(data.depsgraph, data.scene, data.ob, 1);
 | |
|   WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_EDITED, data.ob);
 | |
| 
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_rekey(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Rekey";
 | |
|   ot->idname = "PARTICLE_OT_rekey";
 | |
|   ot->description = "Change the number of keys of selected particles (root and tip keys included)";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = rekey_exec;
 | |
|   ot->invoke = WM_operator_props_popup;
 | |
|   ot->poll = PE_hair_poll;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| 
 | |
|   /* properties */
 | |
|   RNA_def_int(ot->srna, "keys_number", 2, 2, INT_MAX, "Number of Keys", "", 2, 100);
 | |
| }
 | |
| 
 | |
| static void rekey_particle_to_time(
 | |
|     const bContext *C, Scene *scene, Object *ob, int pa_index, float path_time)
 | |
| {
 | |
|   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
|   ParticleSystem *psys;
 | |
|   ParticleSimulationData sim = {0};
 | |
|   ParticleData *pa;
 | |
|   ParticleKey state;
 | |
|   HairKey *new_keys, *key;
 | |
|   PTCacheEditKey *ekey;
 | |
|   int k;
 | |
| 
 | |
|   if (!edit || !edit->psys) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   psys = edit->psys;
 | |
| 
 | |
|   sim.depsgraph = depsgraph;
 | |
|   sim.scene = scene;
 | |
|   sim.ob = ob;
 | |
|   sim.psys = psys;
 | |
| 
 | |
|   pa = psys->particles + pa_index;
 | |
| 
 | |
|   pa->flag |= PARS_REKEY;
 | |
| 
 | |
|   key = new_keys = MEM_dupallocN(pa->hair);
 | |
| 
 | |
|   /* interpolate new keys from old ones (roots stay the same) */
 | |
|   for (k = 1, key++; k < pa->totkey; k++, key++) {
 | |
|     state.time = path_time * (float)k / (float)(pa->totkey - 1);
 | |
|     psys_get_particle_on_path(&sim, pa_index, &state, 0);
 | |
|     copy_v3_v3(key->co, state.co);
 | |
|   }
 | |
| 
 | |
|   /* replace hair keys */
 | |
|   if (pa->hair) {
 | |
|     MEM_freeN(pa->hair);
 | |
|   }
 | |
|   pa->hair = new_keys;
 | |
| 
 | |
|   /* update edit pointers */
 | |
|   for (k = 0, key = pa->hair, ekey = edit->points[pa_index].keys; k < pa->totkey;
 | |
|        k++, key++, ekey++) {
 | |
|     ekey->co = key->co;
 | |
|     ekey->time = &key->time;
 | |
|   }
 | |
| 
 | |
|   pa->flag &= ~PARS_REKEY;
 | |
| }
 | |
| 
 | |
| /************************* utilities **************************/
 | |
| 
 | |
| static int remove_tagged_particles(Object *ob, ParticleSystem *psys, int mirror)
 | |
| {
 | |
|   PTCacheEdit *edit = psys->edit;
 | |
|   ParticleData *pa, *npa = 0, *new_pars = 0;
 | |
|   POINT_P;
 | |
|   PTCacheEditPoint *npoint = 0, *new_points = 0;
 | |
|   ParticleSystemModifierData *psmd_eval;
 | |
|   int i, new_totpart = psys->totpart, removed = 0;
 | |
| 
 | |
|   if (mirror) {
 | |
|     /* mirror tags */
 | |
|     psmd_eval = edit->psmd_eval;
 | |
| 
 | |
|     LOOP_TAGGED_POINTS {
 | |
|       PE_mirror_particle(ob, psmd_eval->mesh_final, psys, psys->particles + p, NULL);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   LOOP_TAGGED_POINTS {
 | |
|     new_totpart--;
 | |
|     removed++;
 | |
|   }
 | |
| 
 | |
|   if (new_totpart != psys->totpart) {
 | |
|     if (new_totpart) {
 | |
|       npa = new_pars = MEM_callocN(new_totpart * sizeof(ParticleData), "ParticleData array");
 | |
|       npoint = new_points = MEM_callocN(new_totpart * sizeof(PTCacheEditPoint),
 | |
|                                         "PTCacheEditKey array");
 | |
| 
 | |
|       if (ELEM(NULL, new_pars, new_points)) {
 | |
|         /* allocation error! */
 | |
|         if (new_pars) {
 | |
|           MEM_freeN(new_pars);
 | |
|         }
 | |
|         if (new_points) {
 | |
|           MEM_freeN(new_points);
 | |
|         }
 | |
|         return 0;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     pa = psys->particles;
 | |
|     point = edit->points;
 | |
|     for (i = 0; i < psys->totpart; i++, pa++, point++) {
 | |
|       if (point->flag & PEP_TAG) {
 | |
|         if (point->keys) {
 | |
|           MEM_freeN(point->keys);
 | |
|         }
 | |
|         if (pa->hair) {
 | |
|           MEM_freeN(pa->hair);
 | |
|         }
 | |
|       }
 | |
|       else {
 | |
|         memcpy(npa, pa, sizeof(ParticleData));
 | |
|         memcpy(npoint, point, sizeof(PTCacheEditPoint));
 | |
|         npa++;
 | |
|         npoint++;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (psys->particles) {
 | |
|       MEM_freeN(psys->particles);
 | |
|     }
 | |
|     psys->particles = new_pars;
 | |
| 
 | |
|     if (edit->points) {
 | |
|       MEM_freeN(edit->points);
 | |
|     }
 | |
|     edit->points = new_points;
 | |
| 
 | |
|     if (edit->mirror_cache) {
 | |
|       MEM_freeN(edit->mirror_cache);
 | |
|       edit->mirror_cache = NULL;
 | |
|     }
 | |
| 
 | |
|     if (psys->child) {
 | |
|       MEM_freeN(psys->child);
 | |
|       psys->child = NULL;
 | |
|       psys->totchild = 0;
 | |
|     }
 | |
| 
 | |
|     edit->totpoint = psys->totpart = new_totpart;
 | |
|   }
 | |
| 
 | |
|   return removed;
 | |
| }
 | |
| 
 | |
| static void remove_tagged_keys(Depsgraph *depsgraph, Object *ob, ParticleSystem *psys)
 | |
| {
 | |
|   PTCacheEdit *edit = psys->edit;
 | |
|   ParticleData *pa;
 | |
|   HairKey *hkey, *nhkey, *new_hkeys = 0;
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
|   PTCacheEditKey *nkey, *new_keys;
 | |
|   short new_totkey;
 | |
| 
 | |
|   if (pe_x_mirror(ob)) {
 | |
|     /* mirror key tags */
 | |
|     ParticleSystemModifierData *psmd = psys_get_modifier(ob, psys);
 | |
|     ParticleSystemModifierData *psmd_eval = (ParticleSystemModifierData *)
 | |
|         BKE_modifier_get_evaluated(depsgraph, ob, &psmd->modifier);
 | |
| 
 | |
|     LOOP_POINTS {
 | |
|       LOOP_TAGGED_KEYS {
 | |
|         PE_mirror_particle(ob, psmd_eval->mesh_final, psys, psys->particles + p, NULL);
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   LOOP_POINTS {
 | |
|     new_totkey = point->totkey;
 | |
|     LOOP_TAGGED_KEYS {
 | |
|       new_totkey--;
 | |
|     }
 | |
|     /* we can't have elements with less than two keys*/
 | |
|     if (new_totkey < 2) {
 | |
|       point->flag |= PEP_TAG;
 | |
|     }
 | |
|   }
 | |
|   remove_tagged_particles(ob, psys, pe_x_mirror(ob));
 | |
| 
 | |
|   LOOP_POINTS {
 | |
|     pa = psys->particles + p;
 | |
|     new_totkey = pa->totkey;
 | |
| 
 | |
|     LOOP_TAGGED_KEYS {
 | |
|       new_totkey--;
 | |
|     }
 | |
| 
 | |
|     if (new_totkey != pa->totkey) {
 | |
|       nhkey = new_hkeys = MEM_callocN(new_totkey * sizeof(HairKey), "HairKeys");
 | |
|       nkey = new_keys = MEM_callocN(new_totkey * sizeof(PTCacheEditKey), "particle edit keys");
 | |
| 
 | |
|       hkey = pa->hair;
 | |
|       LOOP_KEYS {
 | |
|         while (key->flag & PEK_TAG && hkey < pa->hair + pa->totkey) {
 | |
|           key++;
 | |
|           hkey++;
 | |
|         }
 | |
| 
 | |
|         if (hkey < pa->hair + pa->totkey) {
 | |
|           copy_v3_v3(nhkey->co, hkey->co);
 | |
|           nhkey->editflag = hkey->editflag;
 | |
|           nhkey->time = hkey->time;
 | |
|           nhkey->weight = hkey->weight;
 | |
| 
 | |
|           nkey->co = nhkey->co;
 | |
|           nkey->time = &nhkey->time;
 | |
|           /* these can be copied from old edit keys */
 | |
|           nkey->flag = key->flag;
 | |
|           nkey->ftime = key->ftime;
 | |
|           nkey->length = key->length;
 | |
|           copy_v3_v3(nkey->world_co, key->world_co);
 | |
|         }
 | |
|         nkey++;
 | |
|         nhkey++;
 | |
|         hkey++;
 | |
|       }
 | |
| 
 | |
|       if (pa->hair) {
 | |
|         MEM_freeN(pa->hair);
 | |
|       }
 | |
| 
 | |
|       if (point->keys) {
 | |
|         MEM_freeN(point->keys);
 | |
|       }
 | |
| 
 | |
|       pa->hair = new_hkeys;
 | |
|       point->keys = new_keys;
 | |
| 
 | |
|       point->totkey = pa->totkey = new_totkey;
 | |
| 
 | |
|       /* flag for recalculating length */
 | |
|       point->flag |= PEP_EDIT_RECALC;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /************************ subdivide opertor *********************/
 | |
| 
 | |
| /* works like normal edit mode subdivide, inserts keys between neighboring selected keys */
 | |
| static void subdivide_particle(PEData *data, int pa_index)
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   ParticleSystem *psys = edit->psys;
 | |
|   ParticleSimulationData sim = {0};
 | |
|   ParticleData *pa = psys->particles + pa_index;
 | |
|   PTCacheEditPoint *point = edit->points + pa_index;
 | |
|   ParticleKey state;
 | |
|   HairKey *key, *nkey, *new_keys;
 | |
|   PTCacheEditKey *ekey, *nekey, *new_ekeys;
 | |
| 
 | |
|   int k;
 | |
|   short totnewkey = 0;
 | |
|   float endtime;
 | |
| 
 | |
|   sim.depsgraph = data->depsgraph;
 | |
|   sim.scene = data->scene;
 | |
|   sim.ob = data->ob;
 | |
|   sim.psys = edit->psys;
 | |
| 
 | |
|   for (k = 0, ekey = point->keys; k < pa->totkey - 1; k++, ekey++) {
 | |
|     if (ekey->flag & PEK_SELECT && (ekey + 1)->flag & PEK_SELECT) {
 | |
|       totnewkey++;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (totnewkey == 0) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   pa->flag |= PARS_REKEY;
 | |
| 
 | |
|   nkey = new_keys = MEM_callocN((pa->totkey + totnewkey) * (sizeof(HairKey)),
 | |
|                                 "Hair subdivide keys");
 | |
|   nekey = new_ekeys = MEM_callocN((pa->totkey + totnewkey) * (sizeof(PTCacheEditKey)),
 | |
|                                   "Hair subdivide edit keys");
 | |
| 
 | |
|   key = pa->hair;
 | |
|   endtime = key[pa->totkey - 1].time;
 | |
| 
 | |
|   for (k = 0, ekey = point->keys; k < pa->totkey - 1; k++, key++, ekey++) {
 | |
| 
 | |
|     memcpy(nkey, key, sizeof(HairKey));
 | |
|     memcpy(nekey, ekey, sizeof(PTCacheEditKey));
 | |
| 
 | |
|     nekey->co = nkey->co;
 | |
|     nekey->time = &nkey->time;
 | |
| 
 | |
|     nkey++;
 | |
|     nekey++;
 | |
| 
 | |
|     if (ekey->flag & PEK_SELECT && (ekey + 1)->flag & PEK_SELECT) {
 | |
|       nkey->time = (key->time + (key + 1)->time) * 0.5f;
 | |
|       state.time = (endtime != 0.0f) ? nkey->time / endtime : 0.0f;
 | |
|       psys_get_particle_on_path(&sim, pa_index, &state, 0);
 | |
|       copy_v3_v3(nkey->co, state.co);
 | |
| 
 | |
|       nekey->co = nkey->co;
 | |
|       nekey->time = &nkey->time;
 | |
|       nekey->flag |= PEK_SELECT;
 | |
|       if (!(psys->flag & PSYS_GLOBAL_HAIR)) {
 | |
|         nekey->flag |= PEK_USE_WCO;
 | |
|       }
 | |
| 
 | |
|       nekey++;
 | |
|       nkey++;
 | |
|     }
 | |
|   }
 | |
|   /*tip still not copied*/
 | |
|   memcpy(nkey, key, sizeof(HairKey));
 | |
|   memcpy(nekey, ekey, sizeof(PTCacheEditKey));
 | |
| 
 | |
|   nekey->co = nkey->co;
 | |
|   nekey->time = &nkey->time;
 | |
| 
 | |
|   if (pa->hair) {
 | |
|     MEM_freeN(pa->hair);
 | |
|   }
 | |
|   pa->hair = new_keys;
 | |
| 
 | |
|   if (point->keys) {
 | |
|     MEM_freeN(point->keys);
 | |
|   }
 | |
|   point->keys = new_ekeys;
 | |
| 
 | |
|   point->totkey = pa->totkey = pa->totkey + totnewkey;
 | |
|   point->flag |= PEP_EDIT_RECALC;
 | |
|   pa->flag &= ~PARS_REKEY;
 | |
| }
 | |
| 
 | |
| static int subdivide_exec(bContext *C, wmOperator *UNUSED(op))
 | |
| {
 | |
|   PEData data;
 | |
| 
 | |
|   PE_set_data(C, &data);
 | |
|   foreach_point(&data, subdivide_particle);
 | |
| 
 | |
|   recalc_lengths(data.edit);
 | |
|   PE_update_selection(data.depsgraph, data.scene, data.ob, 1);
 | |
|   PE_update_object(data.depsgraph, data.scene, data.ob, 1);
 | |
|   DEG_id_tag_update(&data.ob->id, ID_RECALC_SELECT);
 | |
|   WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_EDITED, data.ob);
 | |
| 
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_subdivide(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Subdivide";
 | |
|   ot->idname = "PARTICLE_OT_subdivide";
 | |
|   ot->description = "Subdivide selected particles segments (adds keys)";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = subdivide_exec;
 | |
|   ot->poll = PE_hair_poll;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| }
 | |
| 
 | |
| /************************ remove doubles opertor *********************/
 | |
| 
 | |
| static int remove_doubles_exec(bContext *C, wmOperator *op)
 | |
| {
 | |
|   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
|   ParticleSystem *psys = edit->psys;
 | |
|   ParticleSystemModifierData *psmd_eval;
 | |
|   KDTree_3d *tree;
 | |
|   KDTreeNearest_3d nearest[10];
 | |
|   POINT_P;
 | |
|   float mat[4][4], co[3], threshold = RNA_float_get(op->ptr, "threshold");
 | |
|   int n, totn, removed, totremoved;
 | |
| 
 | |
|   if (psys->flag & PSYS_GLOBAL_HAIR) {
 | |
|     return OPERATOR_CANCELLED;
 | |
|   }
 | |
| 
 | |
|   edit = psys->edit;
 | |
|   psmd_eval = edit->psmd_eval;
 | |
|   totremoved = 0;
 | |
| 
 | |
|   do {
 | |
|     removed = 0;
 | |
| 
 | |
|     tree = BLI_kdtree_3d_new(psys->totpart);
 | |
| 
 | |
|     /* insert particles into kd tree */
 | |
|     LOOP_SELECTED_POINTS {
 | |
|       psys_mat_hair_to_object(
 | |
|           ob, psmd_eval->mesh_final, psys->part->from, psys->particles + p, mat);
 | |
|       copy_v3_v3(co, point->keys->co);
 | |
|       mul_m4_v3(mat, co);
 | |
|       BLI_kdtree_3d_insert(tree, p, co);
 | |
|     }
 | |
| 
 | |
|     BLI_kdtree_3d_balance(tree);
 | |
| 
 | |
|     /* tag particles to be removed */
 | |
|     LOOP_SELECTED_POINTS {
 | |
|       psys_mat_hair_to_object(
 | |
|           ob, psmd_eval->mesh_final, psys->part->from, psys->particles + p, mat);
 | |
|       copy_v3_v3(co, point->keys->co);
 | |
|       mul_m4_v3(mat, co);
 | |
| 
 | |
|       totn = BLI_kdtree_3d_find_nearest_n(tree, co, nearest, 10);
 | |
| 
 | |
|       for (n = 0; n < totn; n++) {
 | |
|         /* this needs a custom threshold still */
 | |
|         if (nearest[n].index > p && nearest[n].dist < threshold) {
 | |
|           if (!(point->flag & PEP_TAG)) {
 | |
|             point->flag |= PEP_TAG;
 | |
|             removed++;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     BLI_kdtree_3d_free(tree);
 | |
| 
 | |
|     /* remove tagged particles - don't do mirror here! */
 | |
|     remove_tagged_particles(ob, psys, 0);
 | |
|     totremoved += removed;
 | |
|   } while (removed);
 | |
| 
 | |
|   if (totremoved == 0) {
 | |
|     return OPERATOR_CANCELLED;
 | |
|   }
 | |
| 
 | |
|   BKE_reportf(op->reports, RPT_INFO, "Removed %d double particle(s)", totremoved);
 | |
| 
 | |
|   DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
 | |
|   WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_EDITED, ob);
 | |
| 
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_remove_doubles(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Remove Doubles";
 | |
|   ot->idname = "PARTICLE_OT_remove_doubles";
 | |
|   ot->description = "Remove selected particles close enough of others";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = remove_doubles_exec;
 | |
|   ot->poll = PE_hair_poll;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| 
 | |
|   /* properties */
 | |
|   RNA_def_float(ot->srna,
 | |
|                 "threshold",
 | |
|                 0.0002f,
 | |
|                 0.0f,
 | |
|                 FLT_MAX,
 | |
|                 "Merge Distance",
 | |
|                 "Threshold distance within which particles are removed",
 | |
|                 0.00001f,
 | |
|                 0.1f);
 | |
| }
 | |
| 
 | |
| static int weight_set_exec(bContext *C, wmOperator *op)
 | |
| {
 | |
|   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   ParticleEditSettings *pset = PE_settings(scene);
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
|   ParticleSystem *psys = edit->psys;
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
|   HairKey *hkey;
 | |
|   float weight;
 | |
|   ParticleBrushData *brush = &pset->brush[pset->brushtype];
 | |
|   float factor = RNA_float_get(op->ptr, "factor");
 | |
| 
 | |
|   weight = brush->strength;
 | |
|   edit = psys->edit;
 | |
| 
 | |
|   LOOP_SELECTED_POINTS {
 | |
|     ParticleData *pa = psys->particles + p;
 | |
| 
 | |
|     LOOP_SELECTED_KEYS {
 | |
|       hkey = pa->hair + k;
 | |
|       hkey->weight = interpf(weight, hkey->weight, factor);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
 | |
|   WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_EDITED, ob);
 | |
| 
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_weight_set(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Weight Set";
 | |
|   ot->idname = "PARTICLE_OT_weight_set";
 | |
|   ot->description = "Set the weight of selected keys";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = weight_set_exec;
 | |
|   ot->poll = PE_hair_poll;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| 
 | |
|   RNA_def_float(ot->srna,
 | |
|                 "factor",
 | |
|                 1,
 | |
|                 0,
 | |
|                 1,
 | |
|                 "Factor",
 | |
|                 "Interpolation factor between current brush weight, and keys' weights",
 | |
|                 0,
 | |
|                 1);
 | |
| }
 | |
| 
 | |
| /************************ cursor drawing *******************************/
 | |
| 
 | |
| static void brush_drawcursor(bContext *C, int x, int y, void *UNUSED(customdata))
 | |
| {
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   ParticleEditSettings *pset = PE_settings(scene);
 | |
|   ParticleBrushData *brush;
 | |
| 
 | |
|   if (!WM_toolsystem_active_tool_is_brush(C)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   brush = &pset->brush[pset->brushtype];
 | |
| 
 | |
|   if (brush) {
 | |
|     uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
 | |
|     immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
 | |
| 
 | |
|     immUniformColor4ub(255, 255, 255, 128);
 | |
| 
 | |
|     GPU_line_smooth(true);
 | |
|     GPU_blend(GPU_BLEND_ALPHA);
 | |
| 
 | |
|     imm_draw_circle_wire_2d(pos, (float)x, (float)y, pe_brush_size_get(scene, brush), 40);
 | |
| 
 | |
|     GPU_blend(GPU_BLEND_NONE);
 | |
|     GPU_line_smooth(false);
 | |
| 
 | |
|     immUnbindProgram();
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void toggle_particle_cursor(Scene *scene, bool enable)
 | |
| {
 | |
|   ParticleEditSettings *pset = PE_settings(scene);
 | |
| 
 | |
|   if (pset->paintcursor && !enable) {
 | |
|     WM_paint_cursor_end(pset->paintcursor);
 | |
|     pset->paintcursor = NULL;
 | |
|   }
 | |
|   else if (enable) {
 | |
|     pset->paintcursor = WM_paint_cursor_activate(
 | |
|         SPACE_VIEW3D, RGN_TYPE_WINDOW, PE_poll_view3d, brush_drawcursor, NULL);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*************************** delete operator **************************/
 | |
| 
 | |
| enum { DEL_PARTICLE, DEL_KEY };
 | |
| 
 | |
| static const EnumPropertyItem delete_type_items[] = {
 | |
|     {DEL_PARTICLE, "PARTICLE", 0, "Particle", ""},
 | |
|     {DEL_KEY, "KEY", 0, "Key", ""},
 | |
|     {0, NULL, 0, NULL, NULL},
 | |
| };
 | |
| 
 | |
| static void set_delete_particle(PEData *data, int pa_index)
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
| 
 | |
|   edit->points[pa_index].flag |= PEP_TAG;
 | |
| }
 | |
| 
 | |
| static void set_delete_particle_key(PEData *data,
 | |
|                                     int pa_index,
 | |
|                                     int key_index,
 | |
|                                     bool UNUSED(is_inside))
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
| 
 | |
|   edit->points[pa_index].keys[key_index].flag |= PEK_TAG;
 | |
| }
 | |
| 
 | |
| static int delete_exec(bContext *C, wmOperator *op)
 | |
| {
 | |
|   PEData data;
 | |
|   int type = RNA_enum_get(op->ptr, "type");
 | |
| 
 | |
|   PE_set_data(C, &data);
 | |
| 
 | |
|   if (type == DEL_KEY) {
 | |
|     foreach_selected_key(&data, set_delete_particle_key);
 | |
|     remove_tagged_keys(data.depsgraph, data.ob, data.edit->psys);
 | |
|     recalc_lengths(data.edit);
 | |
|   }
 | |
|   else if (type == DEL_PARTICLE) {
 | |
|     foreach_selected_point(&data, set_delete_particle);
 | |
|     remove_tagged_particles(data.ob, data.edit->psys, pe_x_mirror(data.ob));
 | |
|     recalc_lengths(data.edit);
 | |
|   }
 | |
| 
 | |
|   DEG_id_tag_update(&data.ob->id, ID_RECALC_GEOMETRY);
 | |
|   BKE_particle_batch_cache_dirty_tag(data.edit->psys, BKE_PARTICLE_BATCH_DIRTY_ALL);
 | |
|   WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_EDITED, data.ob);
 | |
| 
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_delete(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Delete";
 | |
|   ot->idname = "PARTICLE_OT_delete";
 | |
|   ot->description = "Delete selected particles or keys";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = delete_exec;
 | |
|   ot->invoke = WM_menu_invoke;
 | |
|   ot->poll = PE_hair_poll;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| 
 | |
|   /* properties */
 | |
|   ot->prop = RNA_def_enum(ot->srna,
 | |
|                           "type",
 | |
|                           delete_type_items,
 | |
|                           DEL_PARTICLE,
 | |
|                           "Type",
 | |
|                           "Delete a full particle or only keys");
 | |
| }
 | |
| 
 | |
| /*************************** mirror operator **************************/
 | |
| 
 | |
| static void PE_mirror_x(Depsgraph *depsgraph, Scene *scene, Object *ob, int tagged)
 | |
| {
 | |
|   Mesh *me = (Mesh *)(ob->data);
 | |
|   ParticleSystemModifierData *psmd_eval;
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
|   ParticleSystem *psys = edit->psys;
 | |
|   ParticleData *pa, *newpa, *new_pars;
 | |
|   PTCacheEditPoint *newpoint, *new_points;
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
|   HairKey *hkey;
 | |
|   int *mirrorfaces = NULL;
 | |
|   int rotation, totpart, newtotpart;
 | |
| 
 | |
|   if (psys->flag & PSYS_GLOBAL_HAIR) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   psmd_eval = edit->psmd_eval;
 | |
|   if (!psmd_eval->mesh_final) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const bool use_dm_final_indices = (psys->part->use_modifier_stack &&
 | |
|                                      !psmd_eval->mesh_final->runtime.deformed_only);
 | |
| 
 | |
|   /* NOTE: this is not nice to use tessfaces but hard to avoid since pa->num uses tessfaces */
 | |
|   BKE_mesh_tessface_ensure(me);
 | |
| 
 | |
|   /* NOTE: In case psys uses Mesh tessface indices, we mirror final Mesh itself, not orig mesh.
 | |
|    * Avoids an (impossible) mesh -> orig -> mesh tessface indices conversion. */
 | |
|   mirrorfaces = mesh_get_x_mirror_faces(
 | |
|       ob, NULL, use_dm_final_indices ? psmd_eval->mesh_final : NULL);
 | |
| 
 | |
|   if (!edit->mirror_cache) {
 | |
|     PE_update_mirror_cache(ob, psys);
 | |
|   }
 | |
| 
 | |
|   totpart = psys->totpart;
 | |
|   newtotpart = psys->totpart;
 | |
|   LOOP_VISIBLE_POINTS {
 | |
|     pa = psys->particles + p;
 | |
| 
 | |
|     if (!tagged) {
 | |
|       if (point_is_selected(point)) {
 | |
|         if (edit->mirror_cache[p] != -1) {
 | |
|           /* already has a mirror, don't need to duplicate */
 | |
|           PE_mirror_particle(ob, psmd_eval->mesh_final, psys, pa, NULL);
 | |
|           continue;
 | |
|         }
 | |
|         point->flag |= PEP_TAG;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if ((point->flag & PEP_TAG) && mirrorfaces[pa->num * 2] != -1) {
 | |
|       newtotpart++;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (newtotpart != psys->totpart) {
 | |
|     MFace *mtessface = use_dm_final_indices ? psmd_eval->mesh_final->mface : me->mface;
 | |
| 
 | |
|     /* allocate new arrays and copy existing */
 | |
|     new_pars = MEM_callocN(newtotpart * sizeof(ParticleData), "ParticleData new");
 | |
|     new_points = MEM_callocN(newtotpart * sizeof(PTCacheEditPoint), "PTCacheEditPoint new");
 | |
| 
 | |
|     if (psys->particles) {
 | |
|       memcpy(new_pars, psys->particles, totpart * sizeof(ParticleData));
 | |
|       MEM_freeN(psys->particles);
 | |
|     }
 | |
|     psys->particles = new_pars;
 | |
| 
 | |
|     if (edit->points) {
 | |
|       memcpy(new_points, edit->points, totpart * sizeof(PTCacheEditPoint));
 | |
|       MEM_freeN(edit->points);
 | |
|     }
 | |
|     edit->points = new_points;
 | |
| 
 | |
|     if (edit->mirror_cache) {
 | |
|       MEM_freeN(edit->mirror_cache);
 | |
|       edit->mirror_cache = NULL;
 | |
|     }
 | |
| 
 | |
|     edit->totpoint = psys->totpart = newtotpart;
 | |
| 
 | |
|     /* create new elements */
 | |
|     newpa = psys->particles + totpart;
 | |
|     newpoint = edit->points + totpart;
 | |
| 
 | |
|     for (p = 0, point = edit->points; p < totpart; p++, point++) {
 | |
|       pa = psys->particles + p;
 | |
|       const int pa_num = pa->num;
 | |
| 
 | |
|       if (point->flag & PEP_HIDE) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       if (!(point->flag & PEP_TAG) || mirrorfaces[pa_num * 2] == -1) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       /* duplicate */
 | |
|       *newpa = *pa;
 | |
|       *newpoint = *point;
 | |
|       if (pa->hair) {
 | |
|         newpa->hair = MEM_dupallocN(pa->hair);
 | |
|       }
 | |
|       if (point->keys) {
 | |
|         newpoint->keys = MEM_dupallocN(point->keys);
 | |
|       }
 | |
| 
 | |
|       /* rotate weights according to vertex index rotation */
 | |
|       rotation = mirrorfaces[pa_num * 2 + 1];
 | |
|       newpa->fuv[0] = pa->fuv[2];
 | |
|       newpa->fuv[1] = pa->fuv[1];
 | |
|       newpa->fuv[2] = pa->fuv[0];
 | |
|       newpa->fuv[3] = pa->fuv[3];
 | |
|       while (rotation--) {
 | |
|         if (mtessface[pa_num].v4) {
 | |
|           SHIFT4(float, newpa->fuv[0], newpa->fuv[1], newpa->fuv[2], newpa->fuv[3]);
 | |
|         }
 | |
|         else {
 | |
|           SHIFT3(float, newpa->fuv[0], newpa->fuv[1], newpa->fuv[2]);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       /* assign face index */
 | |
|       /* NOTE: mesh_get_x_mirror_faces generates -1 for non-found mirror,
 | |
|        * same as DMCACHE_NOTFOUND. */
 | |
|       newpa->num = mirrorfaces[pa_num * 2];
 | |
| 
 | |
|       if (use_dm_final_indices) {
 | |
|         newpa->num_dmcache = DMCACHE_ISCHILD;
 | |
|       }
 | |
|       else {
 | |
|         newpa->num_dmcache = psys_particle_dm_face_lookup(
 | |
|             psmd_eval->mesh_final, psmd_eval->mesh_original, newpa->num, newpa->fuv, NULL);
 | |
|       }
 | |
| 
 | |
|       /* update edit key pointers */
 | |
|       key = newpoint->keys;
 | |
|       for (k = 0, hkey = newpa->hair; k < newpa->totkey; k++, hkey++, key++) {
 | |
|         key->co = hkey->co;
 | |
|         key->time = &hkey->time;
 | |
|       }
 | |
| 
 | |
|       /* map key positions as mirror over x axis */
 | |
|       PE_mirror_particle(ob, psmd_eval->mesh_final, psys, pa, newpa);
 | |
| 
 | |
|       newpa++;
 | |
|       newpoint++;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   LOOP_POINTS {
 | |
|     point->flag &= ~PEP_TAG;
 | |
|   }
 | |
| 
 | |
|   MEM_freeN(mirrorfaces);
 | |
| }
 | |
| 
 | |
| static int mirror_exec(bContext *C, wmOperator *UNUSED(op))
 | |
| {
 | |
|   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
| 
 | |
|   PE_mirror_x(depsgraph, scene, ob, 0);
 | |
| 
 | |
|   update_world_cos(ob, edit);
 | |
|   psys_free_path_cache(NULL, edit);
 | |
| 
 | |
|   WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_EDITED, ob);
 | |
|   BKE_particle_batch_cache_dirty_tag(edit->psys, BKE_PARTICLE_BATCH_DIRTY_ALL);
 | |
|   DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
 | |
| 
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| static bool mirror_poll(bContext *C)
 | |
| {
 | |
|   if (!PE_hair_poll(C)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
| 
 | |
|   /* The operator only works for hairs emitted from faces. */
 | |
|   return edit->psys->part->from == PART_FROM_FACE;
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_mirror(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Mirror";
 | |
|   ot->idname = "PARTICLE_OT_mirror";
 | |
|   ot->description = "Duplicate and mirror the selected particles along the local X axis";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = mirror_exec;
 | |
|   ot->poll = mirror_poll;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| }
 | |
| 
 | |
| /************************* brush edit callbacks ********************/
 | |
| 
 | |
| static void brush_comb(PEData *data,
 | |
|                        float UNUSED(mat[4][4]),
 | |
|                        float imat[4][4],
 | |
|                        int point_index,
 | |
|                        int key_index,
 | |
|                        PTCacheEditKey *key,
 | |
|                        float mouse_distance)
 | |
| {
 | |
|   ParticleEditSettings *pset = PE_settings(data->scene);
 | |
|   float cvec[3], fac;
 | |
| 
 | |
|   if (pset->flag & PE_LOCK_FIRST && key_index == 0) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   fac = (float)pow((double)(1.0f - mouse_distance / data->rad), (double)data->combfac);
 | |
| 
 | |
|   copy_v3_v3(cvec, data->dvec);
 | |
|   mul_mat3_m4_v3(imat, cvec);
 | |
|   mul_v3_fl(cvec, fac);
 | |
|   add_v3_v3(key->co, cvec);
 | |
| 
 | |
|   (data->edit->points + point_index)->flag |= PEP_EDIT_RECALC;
 | |
| }
 | |
| 
 | |
| static void brush_cut(PEData *data, int pa_index)
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   ARegion *region = data->vc.region;
 | |
|   Object *ob = data->ob;
 | |
|   ParticleEditSettings *pset = PE_settings(data->scene);
 | |
|   ParticleCacheKey *key = edit->pathcache[pa_index];
 | |
|   float rad2, cut_time = 1.0;
 | |
|   float x0, x1, v0, v1, o0, o1, xo0, xo1, d, dv;
 | |
|   int k, cut, keys = (int)pow(2.0, (double)pset->draw_step);
 | |
|   int screen_co[2];
 | |
| 
 | |
|   BLI_assert(data->rng != NULL);
 | |
|   /* blunt scissors */
 | |
|   if (BLI_rng_get_float(data->rng) > data->cutfac) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   /* don't cut hidden */
 | |
|   if (edit->points[pa_index].flag & PEP_HIDE) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (ED_view3d_project_int_global(region, key->co, screen_co, V3D_PROJ_TEST_CLIP_NEAR) !=
 | |
|       V3D_PROJ_RET_OK) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   rad2 = data->rad * data->rad;
 | |
| 
 | |
|   cut = 0;
 | |
| 
 | |
|   x0 = (float)screen_co[0];
 | |
|   x1 = (float)screen_co[1];
 | |
| 
 | |
|   o0 = (float)data->mval[0];
 | |
|   o1 = (float)data->mval[1];
 | |
| 
 | |
|   xo0 = x0 - o0;
 | |
|   xo1 = x1 - o1;
 | |
| 
 | |
|   /* check if root is inside circle */
 | |
|   if (xo0 * xo0 + xo1 * xo1 < rad2 && key_test_depth(data, key->co, screen_co)) {
 | |
|     cut_time = -1.0f;
 | |
|     cut = 1;
 | |
|   }
 | |
|   else {
 | |
|     /* calculate path time closest to root that was inside the circle */
 | |
|     for (k = 1, key++; k <= keys; k++, key++) {
 | |
| 
 | |
|       if ((ED_view3d_project_int_global(region, key->co, screen_co, V3D_PROJ_TEST_CLIP_NEAR) !=
 | |
|            V3D_PROJ_RET_OK) ||
 | |
|           key_test_depth(data, key->co, screen_co) == 0) {
 | |
|         x0 = (float)screen_co[0];
 | |
|         x1 = (float)screen_co[1];
 | |
| 
 | |
|         xo0 = x0 - o0;
 | |
|         xo1 = x1 - o1;
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       v0 = (float)screen_co[0] - x0;
 | |
|       v1 = (float)screen_co[1] - x1;
 | |
| 
 | |
|       dv = v0 * v0 + v1 * v1;
 | |
| 
 | |
|       d = (v0 * xo1 - v1 * xo0);
 | |
| 
 | |
|       d = dv * rad2 - d * d;
 | |
| 
 | |
|       if (d > 0.0f) {
 | |
|         d = sqrtf(d);
 | |
| 
 | |
|         cut_time = -(v0 * xo0 + v1 * xo1 + d);
 | |
| 
 | |
|         if (cut_time > 0.0f) {
 | |
|           cut_time /= dv;
 | |
| 
 | |
|           if (cut_time < 1.0f) {
 | |
|             cut_time += (float)(k - 1);
 | |
|             cut_time /= (float)keys;
 | |
|             cut = 1;
 | |
|             break;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       x0 = (float)screen_co[0];
 | |
|       x1 = (float)screen_co[1];
 | |
| 
 | |
|       xo0 = x0 - o0;
 | |
|       xo1 = x1 - o1;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (cut) {
 | |
|     if (cut_time < 0.0f) {
 | |
|       edit->points[pa_index].flag |= PEP_TAG;
 | |
|     }
 | |
|     else {
 | |
|       rekey_particle_to_time(data->context, data->scene, ob, pa_index, cut_time);
 | |
|       edit->points[pa_index].flag |= PEP_EDIT_RECALC;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void brush_length(PEData *data, int point_index, float UNUSED(mouse_distance))
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   PTCacheEditPoint *point = edit->points + point_index;
 | |
|   KEY_K;
 | |
|   float dvec[3], pvec[3] = {0.0f, 0.0f, 0.0f};
 | |
| 
 | |
|   LOOP_KEYS {
 | |
|     if (k == 0) {
 | |
|       copy_v3_v3(pvec, key->co);
 | |
|     }
 | |
|     else {
 | |
|       sub_v3_v3v3(dvec, key->co, pvec);
 | |
|       copy_v3_v3(pvec, key->co);
 | |
|       mul_v3_fl(dvec, data->growfac);
 | |
|       add_v3_v3v3(key->co, (key - 1)->co, dvec);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   point->flag |= PEP_EDIT_RECALC;
 | |
| }
 | |
| 
 | |
| static void brush_puff(PEData *data, int point_index, float mouse_distance)
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   ParticleSystem *psys = edit->psys;
 | |
|   PTCacheEditPoint *point = edit->points + point_index;
 | |
|   KEY_K;
 | |
|   float mat[4][4], imat[4][4];
 | |
| 
 | |
|   float onor_prev[3];           /* previous normal (particle-space) */
 | |
|   float ofs_prev[3];            /* accumulate offset for puff_volume (particle-space) */
 | |
|   float co_root[3], no_root[3]; /* root location and normal (global-space) */
 | |
|   float co_prev[3], co[3];      /* track key coords as we loop (global-space) */
 | |
|   float fac = 0.0f, length_accum = 0.0f;
 | |
|   bool puff_volume = false;
 | |
|   bool changed = false;
 | |
| 
 | |
|   zero_v3(ofs_prev);
 | |
| 
 | |
|   {
 | |
|     ParticleEditSettings *pset = PE_settings(data->scene);
 | |
|     ParticleBrushData *brush = &pset->brush[pset->brushtype];
 | |
|     puff_volume = (brush->flag & PE_BRUSH_DATA_PUFF_VOLUME) != 0;
 | |
|   }
 | |
| 
 | |
|   if (psys && !(psys->flag & PSYS_GLOBAL_HAIR)) {
 | |
|     psys_mat_hair_to_global(
 | |
|         data->ob, data->mesh, psys->part->from, psys->particles + point_index, mat);
 | |
|     invert_m4_m4(imat, mat);
 | |
|   }
 | |
|   else {
 | |
|     unit_m4(mat);
 | |
|     unit_m4(imat);
 | |
|   }
 | |
| 
 | |
|   LOOP_KEYS {
 | |
|     float kco[3];
 | |
| 
 | |
|     if (k == 0) {
 | |
|       /* find root coordinate and normal on emitter */
 | |
|       copy_v3_v3(co, key->co);
 | |
|       mul_m4_v3(mat, co);
 | |
| 
 | |
|       /* use 'kco' as the object space version of worldspace 'co',
 | |
|        * ob->imat is set before calling */
 | |
|       mul_v3_m4v3(kco, data->ob->imat, co);
 | |
| 
 | |
|       point_index = BLI_kdtree_3d_find_nearest(edit->emitter_field, kco, NULL);
 | |
|       if (point_index == -1) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       copy_v3_v3(co_root, co);
 | |
|       copy_v3_v3(no_root, &edit->emitter_cosnos[point_index * 6 + 3]);
 | |
|       mul_mat3_m4_v3(data->ob->obmat, no_root); /* normal into global-space */
 | |
|       normalize_v3(no_root);
 | |
| 
 | |
|       if (puff_volume) {
 | |
|         copy_v3_v3(onor_prev, no_root);
 | |
|         mul_mat3_m4_v3(imat, onor_prev); /* global-space into particle space */
 | |
|         normalize_v3(onor_prev);
 | |
|       }
 | |
| 
 | |
|       fac = (float)pow((double)(1.0f - mouse_distance / data->rad), (double)data->pufffac);
 | |
|       fac *= 0.025f;
 | |
|       if (data->invert) {
 | |
|         fac = -fac;
 | |
|       }
 | |
|     }
 | |
|     else {
 | |
|       /* compute position as if hair was standing up straight.
 | |
|        * */
 | |
|       float length;
 | |
|       copy_v3_v3(co_prev, co);
 | |
|       copy_v3_v3(co, key->co);
 | |
|       mul_m4_v3(mat, co);
 | |
|       length = len_v3v3(co_prev, co);
 | |
|       length_accum += length;
 | |
| 
 | |
|       if ((data->select == 0 || (key->flag & PEK_SELECT)) && !(key->flag & PEK_HIDE)) {
 | |
|         float dco[3]; /* delta temp var */
 | |
| 
 | |
|         madd_v3_v3v3fl(kco, co_root, no_root, length_accum);
 | |
| 
 | |
|         /* blend between the current and straight position */
 | |
|         sub_v3_v3v3(dco, kco, co);
 | |
|         madd_v3_v3fl(co, dco, fac);
 | |
|         /* keep the same distance from the root or we get glitches T35406. */
 | |
|         dist_ensure_v3_v3fl(co, co_root, length_accum);
 | |
| 
 | |
|         /* re-use dco to compare before and after translation and add to the offset  */
 | |
|         copy_v3_v3(dco, key->co);
 | |
| 
 | |
|         mul_v3_m4v3(key->co, imat, co);
 | |
| 
 | |
|         if (puff_volume) {
 | |
|           /* accumulate the total distance moved to apply to unselected
 | |
|            * keys that come after */
 | |
|           sub_v3_v3v3(ofs_prev, key->co, dco);
 | |
|         }
 | |
|         changed = true;
 | |
|       }
 | |
|       else {
 | |
| 
 | |
|         if (puff_volume) {
 | |
| #if 0
 | |
|           /* this is simple but looks bad, adds annoying kinks */
 | |
|           add_v3_v3(key->co, ofs);
 | |
| #else
 | |
|           /* translate (not rotate) the rest of the hair if its not selected  */
 | |
|           {
 | |
| /* NOLINTNEXTLINE: readability-redundant-preprocessor */
 | |
| #  if 0 /* kindof works but looks worse than what's below */
 | |
| 
 | |
|             /* Move the unselected point on a vector based on the
 | |
|              * hair direction and the offset */
 | |
|             float c1[3], c2[3];
 | |
|             sub_v3_v3v3(dco, lastco, co);
 | |
|             mul_mat3_m4_v3(imat, dco); /* into particle space */
 | |
| 
 | |
|             /* move the point along a vector perpendicular to the
 | |
|              * hairs direction, reduces odd kinks, */
 | |
|             cross_v3_v3v3(c1, ofs, dco);
 | |
|             cross_v3_v3v3(c2, c1, dco);
 | |
|             normalize_v3(c2);
 | |
|             mul_v3_fl(c2, len_v3(ofs));
 | |
|             add_v3_v3(key->co, c2);
 | |
| #  else
 | |
|             /* Move the unselected point on a vector based on the
 | |
|              * the normal of the closest geometry */
 | |
|             float oco[3], onor[3];
 | |
|             copy_v3_v3(oco, key->co);
 | |
|             mul_m4_v3(mat, oco);
 | |
| 
 | |
|             /* use 'kco' as the object space version of worldspace 'co',
 | |
|              * ob->imat is set before calling */
 | |
|             mul_v3_m4v3(kco, data->ob->imat, oco);
 | |
| 
 | |
|             point_index = BLI_kdtree_3d_find_nearest(edit->emitter_field, kco, NULL);
 | |
|             if (point_index != -1) {
 | |
|               copy_v3_v3(onor, &edit->emitter_cosnos[point_index * 6 + 3]);
 | |
|               mul_mat3_m4_v3(data->ob->obmat, onor); /* normal into worldspace */
 | |
|               mul_mat3_m4_v3(imat, onor);            /* worldspace into particle space */
 | |
|               normalize_v3(onor);
 | |
|             }
 | |
|             else {
 | |
|               copy_v3_v3(onor, onor_prev);
 | |
|             }
 | |
| 
 | |
|             if (!is_zero_v3(ofs_prev)) {
 | |
|               mul_v3_fl(onor, len_v3(ofs_prev));
 | |
| 
 | |
|               add_v3_v3(key->co, onor);
 | |
|             }
 | |
| 
 | |
|             copy_v3_v3(onor_prev, onor);
 | |
| #  endif
 | |
|           }
 | |
| #endif
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (changed) {
 | |
|     point->flag |= PEP_EDIT_RECALC;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void BKE_brush_weight_get(PEData *data,
 | |
|                                  float UNUSED(mat[4][4]),
 | |
|                                  float UNUSED(imat[4][4]),
 | |
|                                  int point_index,
 | |
|                                  int key_index,
 | |
|                                  PTCacheEditKey *UNUSED(key),
 | |
|                                  float UNUSED(mouse_distance))
 | |
| {
 | |
|   /* roots have full weight always */
 | |
|   if (key_index) {
 | |
|     PTCacheEdit *edit = data->edit;
 | |
|     ParticleSystem *psys = edit->psys;
 | |
| 
 | |
|     ParticleData *pa = psys->particles + point_index;
 | |
|     pa->hair[key_index].weight = data->weightfac;
 | |
| 
 | |
|     (data->edit->points + point_index)->flag |= PEP_EDIT_RECALC;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void brush_smooth_get(PEData *data,
 | |
|                              float mat[4][4],
 | |
|                              float UNUSED(imat[4][4]),
 | |
|                              int UNUSED(point_index),
 | |
|                              int key_index,
 | |
|                              PTCacheEditKey *key,
 | |
|                              float UNUSED(mouse_distance))
 | |
| {
 | |
|   if (key_index) {
 | |
|     float dvec[3];
 | |
| 
 | |
|     sub_v3_v3v3(dvec, key->co, (key - 1)->co);
 | |
|     mul_mat3_m4_v3(mat, dvec);
 | |
|     add_v3_v3(data->vec, dvec);
 | |
|     data->tot++;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void brush_smooth_do(PEData *data,
 | |
|                             float UNUSED(mat[4][4]),
 | |
|                             float imat[4][4],
 | |
|                             int point_index,
 | |
|                             int key_index,
 | |
|                             PTCacheEditKey *key,
 | |
|                             float UNUSED(mouse_distance))
 | |
| {
 | |
|   float vec[3], dvec[3];
 | |
| 
 | |
|   if (key_index) {
 | |
|     copy_v3_v3(vec, data->vec);
 | |
|     mul_mat3_m4_v3(imat, vec);
 | |
| 
 | |
|     sub_v3_v3v3(dvec, key->co, (key - 1)->co);
 | |
| 
 | |
|     sub_v3_v3v3(dvec, vec, dvec);
 | |
|     mul_v3_fl(dvec, data->smoothfac);
 | |
| 
 | |
|     add_v3_v3(key->co, dvec);
 | |
|   }
 | |
| 
 | |
|   (data->edit->points + point_index)->flag |= PEP_EDIT_RECALC;
 | |
| }
 | |
| 
 | |
| /* convert from triangle barycentric weights to quad mean value weights */
 | |
| 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])
 | |
| {
 | |
|   float co[3], vert[4][3];
 | |
| 
 | |
|   copy_v3_v3(vert[0], v1);
 | |
|   copy_v3_v3(vert[1], v2);
 | |
|   copy_v3_v3(vert[2], v3);
 | |
|   copy_v3_v3(vert[3], v4);
 | |
| 
 | |
|   co[0] = v1[0] * w[0] + v2[0] * w[1] + v3[0] * w[2] + v4[0] * w[3];
 | |
|   co[1] = v1[1] * w[0] + v2[1] * w[1] + v3[1] * w[2] + v4[1] * w[3];
 | |
|   co[2] = v1[2] * w[0] + v2[2] * w[1] + v3[2] * w[2] + v4[2] * w[3];
 | |
| 
 | |
|   interp_weights_poly_v3(w, vert, 4, co);
 | |
| }
 | |
| 
 | |
| /** Check intersection with an evaluated mesh. */
 | |
| static int particle_intersect_mesh(Depsgraph *depsgraph,
 | |
|                                    Scene *UNUSED(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)
 | |
| {
 | |
|   MFace *mface = NULL;
 | |
|   MVert *mvert = NULL;
 | |
|   int i, totface, intersect = 0;
 | |
|   float cur_d, cur_uv[2], v1[3], v2[3], v3[3], v4[3], min[3], max[3], p_min[3], p_max[3];
 | |
|   float cur_ipoint[3];
 | |
| 
 | |
|   if (mesh == NULL) {
 | |
|     psys_disable_all(ob);
 | |
| 
 | |
|     Scene *scene_eval = DEG_get_evaluated_scene(depsgraph);
 | |
|     Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob);
 | |
| 
 | |
|     mesh = mesh_get_eval_final(depsgraph, scene_eval, ob_eval, &CD_MASK_BAREMESH);
 | |
|     if (mesh == NULL) {
 | |
|       mesh = mesh_get_eval_deform(depsgraph, scene_eval, ob_eval, &CD_MASK_BAREMESH);
 | |
|     }
 | |
| 
 | |
|     psys_enable_all(ob);
 | |
| 
 | |
|     if (mesh == NULL) {
 | |
|       return 0;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* BMESH_ONLY, deform dm may not have tessface */
 | |
|   BKE_mesh_tessface_ensure(mesh);
 | |
| 
 | |
|   if (pa_minmax == 0) {
 | |
|     INIT_MINMAX(p_min, p_max);
 | |
|     minmax_v3v3_v3(p_min, p_max, co1);
 | |
|     minmax_v3v3_v3(p_min, p_max, co2);
 | |
|   }
 | |
|   else {
 | |
|     copy_v3_v3(p_min, pa_minmax);
 | |
|     copy_v3_v3(p_max, pa_minmax + 3);
 | |
|   }
 | |
| 
 | |
|   totface = mesh->totface;
 | |
|   mface = mesh->mface;
 | |
|   mvert = mesh->mvert;
 | |
| 
 | |
|   /* lets intersect the faces */
 | |
|   for (i = 0; i < totface; i++, mface++) {
 | |
|     if (vert_cos) {
 | |
|       copy_v3_v3(v1, vert_cos + 3 * mface->v1);
 | |
|       copy_v3_v3(v2, vert_cos + 3 * mface->v2);
 | |
|       copy_v3_v3(v3, vert_cos + 3 * mface->v3);
 | |
|       if (mface->v4) {
 | |
|         copy_v3_v3(v4, vert_cos + 3 * mface->v4);
 | |
|       }
 | |
|     }
 | |
|     else {
 | |
|       copy_v3_v3(v1, mvert[mface->v1].co);
 | |
|       copy_v3_v3(v2, mvert[mface->v2].co);
 | |
|       copy_v3_v3(v3, mvert[mface->v3].co);
 | |
|       if (mface->v4) {
 | |
|         copy_v3_v3(v4, mvert[mface->v4].co);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (face_minmax == 0) {
 | |
|       INIT_MINMAX(min, max);
 | |
|       DO_MINMAX(v1, min, max);
 | |
|       DO_MINMAX(v2, min, max);
 | |
|       DO_MINMAX(v3, min, max);
 | |
|       if (mface->v4) {
 | |
|         DO_MINMAX(v4, min, max);
 | |
|       }
 | |
|       if (isect_aabb_aabb_v3(min, max, p_min, p_max) == 0) {
 | |
|         continue;
 | |
|       }
 | |
|     }
 | |
|     else {
 | |
|       copy_v3_v3(min, face_minmax + 6 * i);
 | |
|       copy_v3_v3(max, face_minmax + 6 * i + 3);
 | |
|       if (isect_aabb_aabb_v3(min, max, p_min, p_max) == 0) {
 | |
|         continue;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (radius > 0.0f) {
 | |
|       if (isect_sweeping_sphere_tri_v3(co1, co2, radius, v2, v3, v1, &cur_d, cur_ipoint)) {
 | |
|         if (cur_d < *min_d) {
 | |
|           *min_d = cur_d;
 | |
|           copy_v3_v3(ipoint, cur_ipoint);
 | |
|           *min_face = i;
 | |
|           intersect = 1;
 | |
|         }
 | |
|       }
 | |
|       if (mface->v4) {
 | |
|         if (isect_sweeping_sphere_tri_v3(co1, co2, radius, v4, v1, v3, &cur_d, cur_ipoint)) {
 | |
|           if (cur_d < *min_d) {
 | |
|             *min_d = cur_d;
 | |
|             copy_v3_v3(ipoint, cur_ipoint);
 | |
|             *min_face = i;
 | |
|             intersect = 1;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     else {
 | |
|       if (isect_line_segment_tri_v3(co1, co2, v1, v2, v3, &cur_d, cur_uv)) {
 | |
|         if (cur_d < *min_d) {
 | |
|           *min_d = cur_d;
 | |
|           min_w[0] = 1.0f - cur_uv[0] - cur_uv[1];
 | |
|           min_w[1] = cur_uv[0];
 | |
|           min_w[2] = cur_uv[1];
 | |
|           min_w[3] = 0.0f;
 | |
|           if (mface->v4) {
 | |
|             intersect_dm_quad_weights(v1, v2, v3, v4, min_w);
 | |
|           }
 | |
|           *min_face = i;
 | |
|           intersect = 1;
 | |
|         }
 | |
|       }
 | |
|       if (mface->v4) {
 | |
|         if (isect_line_segment_tri_v3(co1, co2, v1, v3, v4, &cur_d, cur_uv)) {
 | |
|           if (cur_d < *min_d) {
 | |
|             *min_d = cur_d;
 | |
|             min_w[0] = 1.0f - cur_uv[0] - cur_uv[1];
 | |
|             min_w[1] = 0.0f;
 | |
|             min_w[2] = cur_uv[0];
 | |
|             min_w[3] = cur_uv[1];
 | |
|             intersect_dm_quad_weights(v1, v2, v3, v4, min_w);
 | |
|             *min_face = i;
 | |
|             intersect = 1;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return intersect;
 | |
| }
 | |
| 
 | |
| typedef struct BrushAddCountIterData {
 | |
|   Depsgraph *depsgraph;
 | |
|   Scene *scene;
 | |
|   Object *object;
 | |
|   Mesh *mesh;
 | |
|   PEData *data;
 | |
|   int number;
 | |
|   short size;
 | |
|   float imat[4][4];
 | |
|   ParticleData *add_pars;
 | |
| } BrushAddCountIterData;
 | |
| 
 | |
| typedef struct BrushAddCountIterTLSData {
 | |
|   RNG *rng;
 | |
|   int num_added;
 | |
| } BrushAddCountIterTLSData;
 | |
| 
 | |
| static void brush_add_count_iter(void *__restrict iter_data_v,
 | |
|                                  const int iter,
 | |
|                                  const TaskParallelTLS *__restrict tls_v)
 | |
| {
 | |
|   BrushAddCountIterData *iter_data = (BrushAddCountIterData *)iter_data_v;
 | |
|   Depsgraph *depsgraph = iter_data->depsgraph;
 | |
|   PEData *data = iter_data->data;
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   ParticleSystem *psys = edit->psys;
 | |
|   ParticleSystemModifierData *psmd_eval = edit->psmd_eval;
 | |
|   ParticleData *add_pars = iter_data->add_pars;
 | |
|   BrushAddCountIterTLSData *tls = tls_v->userdata_chunk;
 | |
|   const int number = iter_data->number;
 | |
|   const short size = iter_data->size;
 | |
|   const int size2 = size * size;
 | |
|   float dmx, dmy;
 | |
|   if (number > 1) {
 | |
|     dmx = size;
 | |
|     dmy = size;
 | |
|     if (tls->rng == NULL) {
 | |
|       tls->rng = BLI_rng_new_srandom(psys->seed + data->mval[0] + data->mval[1] +
 | |
|                                      BLI_task_parallel_thread_id(tls_v));
 | |
|     }
 | |
|     /* rejection sampling to get points in circle */
 | |
|     while (dmx * dmx + dmy * dmy > size2) {
 | |
|       dmx = (2.0f * BLI_rng_get_float(tls->rng) - 1.0f) * size;
 | |
|       dmy = (2.0f * BLI_rng_get_float(tls->rng) - 1.0f) * size;
 | |
|     }
 | |
|   }
 | |
|   else {
 | |
|     dmx = 0.0f;
 | |
|     dmy = 0.0f;
 | |
|   }
 | |
| 
 | |
|   float mco[2];
 | |
|   mco[0] = data->mval[0] + dmx;
 | |
|   mco[1] = data->mval[1] + dmy;
 | |
| 
 | |
|   float co1[3], co2[3];
 | |
|   ED_view3d_win_to_segment_clipped(depsgraph, data->vc.region, data->vc.v3d, mco, co1, co2, true);
 | |
| 
 | |
|   mul_m4_v3(iter_data->imat, co1);
 | |
|   mul_m4_v3(iter_data->imat, co2);
 | |
|   float min_d = 2.0;
 | |
| 
 | |
|   /* warning, returns the derived mesh face */
 | |
|   BLI_assert(iter_data->mesh != NULL);
 | |
|   if (particle_intersect_mesh(depsgraph,
 | |
|                               iter_data->scene,
 | |
|                               iter_data->object,
 | |
|                               iter_data->mesh,
 | |
|                               0,
 | |
|                               co1,
 | |
|                               co2,
 | |
|                               &min_d,
 | |
|                               &add_pars[iter].num_dmcache,
 | |
|                               add_pars[iter].fuv,
 | |
|                               0,
 | |
|                               0,
 | |
|                               0,
 | |
|                               0)) {
 | |
|     if (psys->part->use_modifier_stack && !psmd_eval->mesh_final->runtime.deformed_only) {
 | |
|       add_pars[iter].num = add_pars[iter].num_dmcache;
 | |
|       add_pars[iter].num_dmcache = DMCACHE_ISCHILD;
 | |
|     }
 | |
|     else if (iter_data->mesh == psmd_eval->mesh_original) {
 | |
|       /* Final DM is not same topology as orig mesh,
 | |
|        * we have to map num_dmcache to real final dm. */
 | |
|       add_pars[iter].num = add_pars[iter].num_dmcache;
 | |
|       add_pars[iter].num_dmcache = psys_particle_dm_face_lookup(psmd_eval->mesh_final,
 | |
|                                                                 psmd_eval->mesh_original,
 | |
|                                                                 add_pars[iter].num,
 | |
|                                                                 add_pars[iter].fuv,
 | |
|                                                                 NULL);
 | |
|     }
 | |
|     else {
 | |
|       add_pars[iter].num = add_pars[iter].num_dmcache;
 | |
|     }
 | |
|     if (add_pars[iter].num != DMCACHE_NOTFOUND) {
 | |
|       tls->num_added++;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void brush_add_count_iter_reduce(const void *__restrict UNUSED(userdata),
 | |
|                                         void *__restrict join_v,
 | |
|                                         void *__restrict chunk_v)
 | |
| {
 | |
|   BrushAddCountIterTLSData *join = (BrushAddCountIterTLSData *)join_v;
 | |
|   BrushAddCountIterTLSData *tls = (BrushAddCountIterTLSData *)chunk_v;
 | |
|   join->num_added += tls->num_added;
 | |
| }
 | |
| 
 | |
| static void brush_add_count_iter_free(const void *__restrict UNUSED(userdata_v),
 | |
|                                       void *__restrict chunk_v)
 | |
| {
 | |
|   BrushAddCountIterTLSData *tls = (BrushAddCountIterTLSData *)chunk_v;
 | |
|   if (tls->rng != NULL) {
 | |
|     BLI_rng_free(tls->rng);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static int brush_add(const bContext *C, PEData *data, short number)
 | |
| {
 | |
|   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
 | |
|   Scene *scene = data->scene;
 | |
|   Object *ob = data->ob;
 | |
|   Mesh *mesh;
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   ParticleSystem *psys = edit->psys;
 | |
|   ParticleData *add_pars;
 | |
|   ParticleSystemModifierData *psmd_eval = edit->psmd_eval;
 | |
|   ParticleSimulationData sim = {0};
 | |
|   ParticleEditSettings *pset = PE_settings(scene);
 | |
|   int i, k, n = 0, totpart = psys->totpart;
 | |
|   float co1[3], imat[4][4];
 | |
|   float framestep, timestep;
 | |
|   short size = pset->brush[PE_BRUSH_ADD].size;
 | |
|   RNG *rng;
 | |
| 
 | |
|   invert_m4_m4(imat, ob->obmat);
 | |
| 
 | |
|   if (psys->flag & PSYS_GLOBAL_HAIR) {
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   add_pars = MEM_callocN(number * sizeof(ParticleData), "ParticleData add");
 | |
| 
 | |
|   rng = BLI_rng_new_srandom(psys->seed + data->mval[0] + data->mval[1]);
 | |
| 
 | |
|   sim.depsgraph = depsgraph;
 | |
|   sim.scene = scene;
 | |
|   sim.ob = ob;
 | |
|   sim.psys = psys;
 | |
|   sim.psmd = psmd_eval;
 | |
| 
 | |
|   timestep = psys_get_timestep(&sim);
 | |
| 
 | |
|   if (psys->part->use_modifier_stack || psmd_eval->mesh_final->runtime.deformed_only) {
 | |
|     mesh = psmd_eval->mesh_final;
 | |
|   }
 | |
|   else {
 | |
|     mesh = psmd_eval->mesh_original;
 | |
|   }
 | |
|   BLI_assert(mesh);
 | |
| 
 | |
|   /* Calculate positions of new particles to add, based on brush intersection
 | |
|    * with object. New particle data is assigned to a corresponding to check
 | |
|    * index element of add_pars array. This means, that add_pars is a sparse
 | |
|    * array.
 | |
|    */
 | |
|   BrushAddCountIterData iter_data;
 | |
|   iter_data.depsgraph = depsgraph;
 | |
|   iter_data.scene = scene;
 | |
|   iter_data.object = ob;
 | |
|   iter_data.mesh = mesh;
 | |
|   iter_data.data = data;
 | |
|   iter_data.number = number;
 | |
|   iter_data.size = size;
 | |
|   iter_data.add_pars = add_pars;
 | |
|   copy_m4_m4(iter_data.imat, imat);
 | |
| 
 | |
|   BrushAddCountIterTLSData tls = {NULL};
 | |
| 
 | |
|   TaskParallelSettings settings;
 | |
|   BLI_parallel_range_settings_defaults(&settings);
 | |
|   settings.userdata_chunk = &tls;
 | |
|   settings.userdata_chunk_size = sizeof(BrushAddCountIterTLSData);
 | |
|   settings.func_reduce = brush_add_count_iter_reduce;
 | |
|   settings.func_free = brush_add_count_iter_free;
 | |
|   BLI_task_parallel_range(0, number, &iter_data, brush_add_count_iter, &settings);
 | |
| 
 | |
|   /* Convert add_parse to a dense array, where all new particles are in the
 | |
|    * beginning of the array.
 | |
|    */
 | |
|   n = tls.num_added;
 | |
|   for (int current_iter = 0, new_index = 0; current_iter < number; current_iter++) {
 | |
|     if (add_pars[current_iter].num == DMCACHE_NOTFOUND) {
 | |
|       continue;
 | |
|     }
 | |
|     if (new_index != current_iter) {
 | |
|       new_index++;
 | |
|       continue;
 | |
|     }
 | |
|     memcpy(add_pars + new_index, add_pars + current_iter, sizeof(ParticleData));
 | |
|     new_index++;
 | |
|   }
 | |
| 
 | |
|   /* TODO(sergey): Consider multi-threading this part as well. */
 | |
|   if (n) {
 | |
|     int newtotpart = totpart + n;
 | |
|     float hairmat[4][4], cur_co[3];
 | |
|     KDTree_3d *tree = 0;
 | |
|     ParticleData *pa,
 | |
|         *new_pars = MEM_callocN(newtotpart * sizeof(ParticleData), "ParticleData new");
 | |
|     PTCacheEditPoint *point, *new_points = MEM_callocN(newtotpart * sizeof(PTCacheEditPoint),
 | |
|                                                        "PTCacheEditPoint array new");
 | |
|     PTCacheEditKey *key;
 | |
|     HairKey *hkey;
 | |
| 
 | |
|     /* save existing elements */
 | |
|     memcpy(new_pars, psys->particles, totpart * sizeof(ParticleData));
 | |
|     memcpy(new_points, edit->points, totpart * sizeof(PTCacheEditPoint));
 | |
| 
 | |
|     /* change old arrays to new ones */
 | |
|     if (psys->particles) {
 | |
|       MEM_freeN(psys->particles);
 | |
|     }
 | |
|     psys->particles = new_pars;
 | |
| 
 | |
|     if (edit->points) {
 | |
|       MEM_freeN(edit->points);
 | |
|     }
 | |
|     edit->points = new_points;
 | |
| 
 | |
|     if (edit->mirror_cache) {
 | |
|       MEM_freeN(edit->mirror_cache);
 | |
|       edit->mirror_cache = NULL;
 | |
|     }
 | |
| 
 | |
|     /* create tree for interpolation */
 | |
|     if (pset->flag & PE_INTERPOLATE_ADDED && psys->totpart) {
 | |
|       tree = BLI_kdtree_3d_new(psys->totpart);
 | |
| 
 | |
|       for (i = 0, pa = psys->particles; i < totpart; i++, pa++) {
 | |
|         psys_particle_on_dm(psmd_eval->mesh_final,
 | |
|                             psys->part->from,
 | |
|                             pa->num,
 | |
|                             pa->num_dmcache,
 | |
|                             pa->fuv,
 | |
|                             pa->foffset,
 | |
|                             cur_co,
 | |
|                             0,
 | |
|                             0,
 | |
|                             0,
 | |
|                             0);
 | |
|         BLI_kdtree_3d_insert(tree, i, cur_co);
 | |
|       }
 | |
| 
 | |
|       BLI_kdtree_3d_balance(tree);
 | |
|     }
 | |
| 
 | |
|     edit->totpoint = psys->totpart = newtotpart;
 | |
| 
 | |
|     /* create new elements */
 | |
|     pa = psys->particles + totpart;
 | |
|     point = edit->points + totpart;
 | |
| 
 | |
|     for (i = totpart; i < newtotpart; i++, pa++, point++) {
 | |
|       memcpy(pa, add_pars + i - totpart, sizeof(ParticleData));
 | |
|       pa->hair = MEM_callocN(pset->totaddkey * sizeof(HairKey), "BakeKey key add");
 | |
|       key = point->keys = MEM_callocN(pset->totaddkey * sizeof(PTCacheEditKey),
 | |
|                                       "PTCacheEditKey add");
 | |
|       point->totkey = pa->totkey = pset->totaddkey;
 | |
| 
 | |
|       for (k = 0, hkey = pa->hair; k < pa->totkey; k++, hkey++, key++) {
 | |
|         key->co = hkey->co;
 | |
|         key->time = &hkey->time;
 | |
| 
 | |
|         if (!(psys->flag & PSYS_GLOBAL_HAIR)) {
 | |
|           key->flag |= PEK_USE_WCO;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       pa->size = 1.0f;
 | |
|       init_particle(&sim, pa);
 | |
|       reset_particle(&sim, pa, 0.0, 1.0);
 | |
|       point->flag |= PEP_EDIT_RECALC;
 | |
|       if (pe_x_mirror(ob)) {
 | |
|         point->flag |= PEP_TAG; /* signal for duplicate */
 | |
|       }
 | |
| 
 | |
|       framestep = pa->lifetime / (float)(pset->totaddkey - 1);
 | |
| 
 | |
|       if (tree) {
 | |
|         ParticleData *ppa;
 | |
|         HairKey *thkey;
 | |
|         ParticleKey key3[3];
 | |
|         KDTreeNearest_3d ptn[3];
 | |
|         int w, maxw;
 | |
|         float maxd, totw = 0.0, weight[3];
 | |
| 
 | |
|         psys_particle_on_dm(psmd_eval->mesh_final,
 | |
|                             psys->part->from,
 | |
|                             pa->num,
 | |
|                             pa->num_dmcache,
 | |
|                             pa->fuv,
 | |
|                             pa->foffset,
 | |
|                             co1,
 | |
|                             0,
 | |
|                             0,
 | |
|                             0,
 | |
|                             0);
 | |
|         maxw = BLI_kdtree_3d_find_nearest_n(tree, co1, ptn, 3);
 | |
| 
 | |
|         maxd = ptn[maxw - 1].dist;
 | |
| 
 | |
|         for (w = 0; w < maxw; w++) {
 | |
|           weight[w] = (float)pow(2.0, (double)(-6.0f * ptn[w].dist / maxd));
 | |
|           totw += weight[w];
 | |
|         }
 | |
|         for (; w < 3; w++) {
 | |
|           weight[w] = 0.0f;
 | |
|         }
 | |
| 
 | |
|         if (totw > 0.0f) {
 | |
|           for (w = 0; w < maxw; w++) {
 | |
|             weight[w] /= totw;
 | |
|           }
 | |
|         }
 | |
|         else {
 | |
|           for (w = 0; w < maxw; w++) {
 | |
|             weight[w] = 1.0f / maxw;
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         ppa = psys->particles + ptn[0].index;
 | |
| 
 | |
|         for (k = 0; k < pset->totaddkey; k++) {
 | |
|           thkey = (HairKey *)pa->hair + k;
 | |
|           thkey->time = pa->time + k * framestep;
 | |
| 
 | |
|           key3[0].time = thkey->time / 100.0f;
 | |
|           psys_get_particle_on_path(&sim, ptn[0].index, key3, 0);
 | |
|           mul_v3_fl(key3[0].co, weight[0]);
 | |
| 
 | |
|           /* TODO: interpolating the weight would be nicer */
 | |
|           thkey->weight = (ppa->hair + MIN2(k, ppa->totkey - 1))->weight;
 | |
| 
 | |
|           if (maxw > 1) {
 | |
|             key3[1].time = key3[0].time;
 | |
|             psys_get_particle_on_path(&sim, ptn[1].index, &key3[1], 0);
 | |
|             mul_v3_fl(key3[1].co, weight[1]);
 | |
|             add_v3_v3(key3[0].co, key3[1].co);
 | |
| 
 | |
|             if (maxw > 2) {
 | |
|               key3[2].time = key3[0].time;
 | |
|               psys_get_particle_on_path(&sim, ptn[2].index, &key3[2], 0);
 | |
|               mul_v3_fl(key3[2].co, weight[2]);
 | |
|               add_v3_v3(key3[0].co, key3[2].co);
 | |
|             }
 | |
|           }
 | |
| 
 | |
|           if (k == 0) {
 | |
|             sub_v3_v3v3(co1, pa->state.co, key3[0].co);
 | |
|           }
 | |
| 
 | |
|           add_v3_v3v3(thkey->co, key3[0].co, co1);
 | |
| 
 | |
|           thkey->time = key3[0].time;
 | |
|         }
 | |
|       }
 | |
|       else {
 | |
|         for (k = 0, hkey = pa->hair; k < pset->totaddkey; k++, hkey++) {
 | |
|           madd_v3_v3v3fl(hkey->co, pa->state.co, pa->state.vel, k * framestep * timestep);
 | |
|           hkey->time += k * framestep;
 | |
|           hkey->weight = 1.0f - (float)k / (float)(pset->totaddkey - 1);
 | |
|         }
 | |
|       }
 | |
|       for (k = 0, hkey = pa->hair; k < pset->totaddkey; k++, hkey++) {
 | |
|         psys_mat_hair_to_global(ob, psmd_eval->mesh_final, psys->part->from, pa, hairmat);
 | |
|         invert_m4_m4(imat, hairmat);
 | |
|         mul_m4_v3(imat, hkey->co);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (tree) {
 | |
|       BLI_kdtree_3d_free(tree);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   MEM_freeN(add_pars);
 | |
| 
 | |
|   BLI_rng_free(rng);
 | |
| 
 | |
|   return n;
 | |
| }
 | |
| 
 | |
| /************************* brush edit operator ********************/
 | |
| 
 | |
| typedef struct BrushEdit {
 | |
|   Scene *scene;
 | |
|   ViewLayer *view_layer;
 | |
|   Object *ob;
 | |
|   PTCacheEdit *edit;
 | |
| 
 | |
|   int first;
 | |
|   int lastmouse[2];
 | |
|   float zfac;
 | |
| 
 | |
|   /* optional cached view settings to avoid setting on every mousemove */
 | |
|   PEData data;
 | |
| } BrushEdit;
 | |
| 
 | |
| static int brush_edit_init(bContext *C, wmOperator *op)
 | |
| {
 | |
|   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   ViewLayer *view_layer = CTX_data_view_layer(C);
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
|   ARegion *region = CTX_wm_region(C);
 | |
|   BrushEdit *bedit;
 | |
|   float min[3], max[3];
 | |
| 
 | |
|   if (!WM_toolsystem_active_tool_is_brush(C)) {
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   /* set the 'distance factor' for grabbing (used in comb etc) */
 | |
|   INIT_MINMAX(min, max);
 | |
|   PE_minmax(depsgraph, scene, view_layer, min, max);
 | |
|   mid_v3_v3v3(min, min, max);
 | |
| 
 | |
|   bedit = MEM_callocN(sizeof(BrushEdit), "BrushEdit");
 | |
|   bedit->first = 1;
 | |
|   op->customdata = bedit;
 | |
| 
 | |
|   bedit->scene = scene;
 | |
|   bedit->view_layer = view_layer;
 | |
|   bedit->ob = ob;
 | |
|   bedit->edit = edit;
 | |
| 
 | |
|   bedit->zfac = ED_view3d_calc_zfac(region->regiondata, min, NULL);
 | |
| 
 | |
|   /* cache view depths and settings for re-use */
 | |
|   PE_set_view3d_data(C, &bedit->data);
 | |
|   PE_create_random_generator(&bedit->data);
 | |
| 
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| static void brush_edit_apply(bContext *C, wmOperator *op, PointerRNA *itemptr)
 | |
| {
 | |
|   BrushEdit *bedit = op->customdata;
 | |
|   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
 | |
|   Scene *scene = bedit->scene;
 | |
|   Object *ob = bedit->ob;
 | |
|   PTCacheEdit *edit = bedit->edit;
 | |
|   ParticleEditSettings *pset = PE_settings(scene);
 | |
|   ParticleSystemModifierData *psmd_eval = edit->psmd_eval;
 | |
|   ParticleBrushData *brush = &pset->brush[pset->brushtype];
 | |
|   ARegion *region = CTX_wm_region(C);
 | |
|   float vec[3], mousef[2];
 | |
|   int mval[2];
 | |
|   int flip, mouse[2], removed = 0, added = 0, selected = 0, tot_steps = 1, step = 1;
 | |
|   float dx, dy, dmax;
 | |
|   int lock_root = pset->flag & PE_LOCK_FIRST;
 | |
| 
 | |
|   if (!PE_start_edit(edit)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   RNA_float_get_array(itemptr, "mouse", mousef);
 | |
|   mouse[0] = mousef[0];
 | |
|   mouse[1] = mousef[1];
 | |
|   flip = RNA_boolean_get(itemptr, "pen_flip");
 | |
| 
 | |
|   if (bedit->first) {
 | |
|     bedit->lastmouse[0] = mouse[0];
 | |
|     bedit->lastmouse[1] = mouse[1];
 | |
|   }
 | |
| 
 | |
|   dx = mouse[0] - bedit->lastmouse[0];
 | |
|   dy = mouse[1] - bedit->lastmouse[1];
 | |
| 
 | |
|   mval[0] = mouse[0];
 | |
|   mval[1] = mouse[1];
 | |
| 
 | |
|   /* disable locking temporatily for disconnected hair */
 | |
|   if (edit->psys && edit->psys->flag & PSYS_GLOBAL_HAIR) {
 | |
|     pset->flag &= ~PE_LOCK_FIRST;
 | |
|   }
 | |
| 
 | |
|   if (((pset->brushtype == PE_BRUSH_ADD) ?
 | |
|            (sqrtf(dx * dx + dy * dy) > pset->brush[PE_BRUSH_ADD].step) :
 | |
|            (dx != 0 || dy != 0)) ||
 | |
|       bedit->first) {
 | |
|     PEData data = bedit->data;
 | |
|     data.context = C; /* TODO(mai): why isnt this set in bedit->data? */
 | |
| 
 | |
|     view3d_operator_needs_opengl(C);
 | |
|     selected = (short)count_selected_keys(scene, edit);
 | |
| 
 | |
|     dmax = max_ff(fabsf(dx), fabsf(dy));
 | |
|     tot_steps = dmax / (0.2f * pe_brush_size_get(scene, brush)) + 1;
 | |
| 
 | |
|     dx /= (float)tot_steps;
 | |
|     dy /= (float)tot_steps;
 | |
| 
 | |
|     for (step = 1; step <= tot_steps; step++) {
 | |
|       mval[0] = bedit->lastmouse[0] + step * dx;
 | |
|       mval[1] = bedit->lastmouse[1] + step * dy;
 | |
| 
 | |
|       switch (pset->brushtype) {
 | |
|         case PE_BRUSH_COMB: {
 | |
|           const float mval_f[2] = {dx, dy};
 | |
|           data.mval = mval;
 | |
|           data.rad = pe_brush_size_get(scene, brush);
 | |
| 
 | |
|           data.combfac = (brush->strength - 0.5f) * 2.0f;
 | |
|           if (data.combfac < 0.0f) {
 | |
|             data.combfac = 1.0f - 9.0f * data.combfac;
 | |
|           }
 | |
|           else {
 | |
|             data.combfac = 1.0f - data.combfac;
 | |
|           }
 | |
| 
 | |
|           invert_m4_m4(ob->imat, ob->obmat);
 | |
| 
 | |
|           ED_view3d_win_to_delta(region, mval_f, vec, bedit->zfac);
 | |
|           data.dvec = vec;
 | |
| 
 | |
|           foreach_mouse_hit_key(&data, brush_comb, selected);
 | |
|           break;
 | |
|         }
 | |
|         case PE_BRUSH_CUT: {
 | |
|           if (edit->psys && edit->pathcache) {
 | |
|             data.mval = mval;
 | |
|             data.rad = pe_brush_size_get(scene, brush);
 | |
|             data.cutfac = brush->strength;
 | |
| 
 | |
|             if (selected) {
 | |
|               foreach_selected_point(&data, brush_cut);
 | |
|             }
 | |
|             else {
 | |
|               foreach_point(&data, brush_cut);
 | |
|             }
 | |
| 
 | |
|             removed = remove_tagged_particles(ob, edit->psys, pe_x_mirror(ob));
 | |
|             if (pset->flag & PE_KEEP_LENGTHS) {
 | |
|               recalc_lengths(edit);
 | |
|             }
 | |
|           }
 | |
|           else {
 | |
|             removed = 0;
 | |
|           }
 | |
| 
 | |
|           break;
 | |
|         }
 | |
|         case PE_BRUSH_LENGTH: {
 | |
|           data.mval = mval;
 | |
| 
 | |
|           data.rad = pe_brush_size_get(scene, brush);
 | |
|           data.growfac = brush->strength / 50.0f;
 | |
| 
 | |
|           if (brush->invert ^ flip) {
 | |
|             data.growfac = 1.0f - data.growfac;
 | |
|           }
 | |
|           else {
 | |
|             data.growfac = 1.0f + data.growfac;
 | |
|           }
 | |
| 
 | |
|           foreach_mouse_hit_point(&data, brush_length, selected);
 | |
| 
 | |
|           if (pset->flag & PE_KEEP_LENGTHS) {
 | |
|             recalc_lengths(edit);
 | |
|           }
 | |
|           break;
 | |
|         }
 | |
|         case PE_BRUSH_PUFF: {
 | |
|           if (edit->psys) {
 | |
|             data.mesh = psmd_eval->mesh_final;
 | |
|             data.mval = mval;
 | |
|             data.rad = pe_brush_size_get(scene, brush);
 | |
|             data.select = selected;
 | |
| 
 | |
|             data.pufffac = (brush->strength - 0.5f) * 2.0f;
 | |
|             if (data.pufffac < 0.0f) {
 | |
|               data.pufffac = 1.0f - 9.0f * data.pufffac;
 | |
|             }
 | |
|             else {
 | |
|               data.pufffac = 1.0f - data.pufffac;
 | |
|             }
 | |
| 
 | |
|             data.invert = (brush->invert ^ flip);
 | |
|             invert_m4_m4(ob->imat, ob->obmat);
 | |
| 
 | |
|             foreach_mouse_hit_point(&data, brush_puff, selected);
 | |
|           }
 | |
|           break;
 | |
|         }
 | |
|         case PE_BRUSH_ADD: {
 | |
|           if (edit->psys && edit->psys->part->from == PART_FROM_FACE) {
 | |
|             data.mval = mval;
 | |
| 
 | |
|             added = brush_add(C, &data, brush->count);
 | |
| 
 | |
|             if (pset->flag & PE_KEEP_LENGTHS) {
 | |
|               recalc_lengths(edit);
 | |
|             }
 | |
|           }
 | |
|           else {
 | |
|             added = 0;
 | |
|           }
 | |
|           break;
 | |
|         }
 | |
|         case PE_BRUSH_SMOOTH: {
 | |
|           data.mval = mval;
 | |
|           data.rad = pe_brush_size_get(scene, brush);
 | |
| 
 | |
|           data.vec[0] = data.vec[1] = data.vec[2] = 0.0f;
 | |
|           data.tot = 0;
 | |
| 
 | |
|           data.smoothfac = brush->strength;
 | |
| 
 | |
|           invert_m4_m4(ob->imat, ob->obmat);
 | |
| 
 | |
|           foreach_mouse_hit_key(&data, brush_smooth_get, selected);
 | |
| 
 | |
|           if (data.tot) {
 | |
|             mul_v3_fl(data.vec, 1.0f / (float)data.tot);
 | |
|             foreach_mouse_hit_key(&data, brush_smooth_do, selected);
 | |
|           }
 | |
| 
 | |
|           break;
 | |
|         }
 | |
|         case PE_BRUSH_WEIGHT: {
 | |
|           if (edit->psys) {
 | |
|             data.mesh = psmd_eval->mesh_final;
 | |
|             data.mval = mval;
 | |
|             data.rad = pe_brush_size_get(scene, brush);
 | |
| 
 | |
|             data.weightfac = brush->strength; /* note that this will never be zero */
 | |
| 
 | |
|             foreach_mouse_hit_key(&data, BKE_brush_weight_get, selected);
 | |
|           }
 | |
| 
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       if ((pset->flag & PE_KEEP_LENGTHS) == 0) {
 | |
|         recalc_lengths(edit);
 | |
|       }
 | |
| 
 | |
|       if (ELEM(pset->brushtype, PE_BRUSH_ADD, PE_BRUSH_CUT) && (added || removed)) {
 | |
|         if (pset->brushtype == PE_BRUSH_ADD && pe_x_mirror(ob)) {
 | |
|           PE_mirror_x(depsgraph, scene, ob, 1);
 | |
|         }
 | |
| 
 | |
|         update_world_cos(ob, edit);
 | |
|         psys_free_path_cache(NULL, edit);
 | |
|         DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
 | |
|       }
 | |
|       else {
 | |
|         PE_update_object(depsgraph, scene, ob, 1);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (edit->psys) {
 | |
|       WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_EDITED, ob);
 | |
|       BKE_particle_batch_cache_dirty_tag(edit->psys, BKE_PARTICLE_BATCH_DIRTY_ALL);
 | |
|       DEG_id_tag_update(&ob->id, ID_RECALC_PSYS_REDO);
 | |
|     }
 | |
|     else {
 | |
|       DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
 | |
|       WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
 | |
|     }
 | |
| 
 | |
|     bedit->lastmouse[0] = mouse[0];
 | |
|     bedit->lastmouse[1] = mouse[1];
 | |
|     bedit->first = 0;
 | |
|   }
 | |
| 
 | |
|   pset->flag |= lock_root;
 | |
| }
 | |
| 
 | |
| static void brush_edit_exit(wmOperator *op)
 | |
| {
 | |
|   BrushEdit *bedit = op->customdata;
 | |
| 
 | |
|   PE_free_random_generator(&bedit->data);
 | |
|   MEM_freeN(bedit);
 | |
| }
 | |
| 
 | |
| static int brush_edit_exec(bContext *C, wmOperator *op)
 | |
| {
 | |
|   if (!brush_edit_init(C, op)) {
 | |
|     return OPERATOR_CANCELLED;
 | |
|   }
 | |
| 
 | |
|   RNA_BEGIN (op->ptr, itemptr, "stroke") {
 | |
|     brush_edit_apply(C, op, &itemptr);
 | |
|   }
 | |
|   RNA_END;
 | |
| 
 | |
|   brush_edit_exit(op);
 | |
| 
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| static void brush_edit_apply_event(bContext *C, wmOperator *op, const wmEvent *event)
 | |
| {
 | |
|   PointerRNA itemptr;
 | |
|   float mouse[2];
 | |
| 
 | |
|   copy_v2fl_v2i(mouse, event->mval);
 | |
| 
 | |
|   /* fill in stroke */
 | |
|   RNA_collection_add(op->ptr, "stroke", &itemptr);
 | |
| 
 | |
|   RNA_float_set_array(&itemptr, "mouse", mouse);
 | |
|   RNA_boolean_set(&itemptr, "pen_flip", event->shift != false); /* XXX hardcoded */
 | |
| 
 | |
|   /* apply */
 | |
|   brush_edit_apply(C, op, &itemptr);
 | |
| }
 | |
| 
 | |
| static int brush_edit_invoke(bContext *C, wmOperator *op, const wmEvent *event)
 | |
| {
 | |
|   if (!brush_edit_init(C, op)) {
 | |
|     return OPERATOR_CANCELLED;
 | |
|   }
 | |
| 
 | |
|   brush_edit_apply_event(C, op, event);
 | |
| 
 | |
|   WM_event_add_modal_handler(C, op);
 | |
| 
 | |
|   return OPERATOR_RUNNING_MODAL;
 | |
| }
 | |
| 
 | |
| static int brush_edit_modal(bContext *C, wmOperator *op, const wmEvent *event)
 | |
| {
 | |
|   switch (event->type) {
 | |
|     case LEFTMOUSE:
 | |
|     case MIDDLEMOUSE:
 | |
|     case RIGHTMOUSE: /* XXX hardcoded */
 | |
|       if (event->val == KM_RELEASE) {
 | |
|         brush_edit_exit(op);
 | |
|         return OPERATOR_FINISHED;
 | |
|       }
 | |
|       break;
 | |
|     case MOUSEMOVE:
 | |
|       brush_edit_apply_event(C, op, event);
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   return OPERATOR_RUNNING_MODAL;
 | |
| }
 | |
| 
 | |
| static void brush_edit_cancel(bContext *UNUSED(C), wmOperator *op)
 | |
| {
 | |
|   brush_edit_exit(op);
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_brush_edit(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Brush Edit";
 | |
|   ot->idname = "PARTICLE_OT_brush_edit";
 | |
|   ot->description = "Apply a stroke of brush to the particles";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = brush_edit_exec;
 | |
|   ot->invoke = brush_edit_invoke;
 | |
|   ot->modal = brush_edit_modal;
 | |
|   ot->cancel = brush_edit_cancel;
 | |
|   ot->poll = PE_poll_view3d;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
 | |
| 
 | |
|   /* properties */
 | |
|   PropertyRNA *prop;
 | |
|   prop = RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
 | |
|   RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
 | |
| }
 | |
| 
 | |
| /*********************** cut shape ***************************/
 | |
| 
 | |
| static bool shape_cut_poll(bContext *C)
 | |
| {
 | |
|   if (PE_hair_poll(C)) {
 | |
|     Scene *scene = CTX_data_scene(C);
 | |
|     ParticleEditSettings *pset = PE_settings(scene);
 | |
| 
 | |
|     if (pset->shape_object && (pset->shape_object->type == OB_MESH)) {
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| typedef struct PointInsideBVH {
 | |
|   BVHTreeFromMesh bvhdata;
 | |
|   int num_hits;
 | |
| } PointInsideBVH;
 | |
| 
 | |
| static void point_inside_bvh_cb(void *userdata,
 | |
|                                 int index,
 | |
|                                 const BVHTreeRay *ray,
 | |
|                                 BVHTreeRayHit *hit)
 | |
| {
 | |
|   PointInsideBVH *data = userdata;
 | |
| 
 | |
|   data->bvhdata.raycast_callback(&data->bvhdata, index, ray, hit);
 | |
| 
 | |
|   if (hit->index != -1) {
 | |
|     ++data->num_hits;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* true if the point is inside the shape mesh */
 | |
| static bool shape_cut_test_point(PEData *data, ParticleEditSettings *pset, ParticleCacheKey *key)
 | |
| {
 | |
|   BVHTreeFromMesh *shape_bvh = &data->shape_bvh;
 | |
|   const float dir[3] = {1.0f, 0.0f, 0.0f};
 | |
|   PointInsideBVH userdata;
 | |
| 
 | |
|   userdata.bvhdata = data->shape_bvh;
 | |
|   userdata.num_hits = 0;
 | |
| 
 | |
|   float co_shape[3];
 | |
|   mul_v3_m4v3(co_shape, pset->shape_object->imat, key->co);
 | |
| 
 | |
|   BLI_bvhtree_ray_cast_all(
 | |
|       shape_bvh->tree, co_shape, dir, 0.0f, BVH_RAYCAST_DIST_MAX, point_inside_bvh_cb, &userdata);
 | |
| 
 | |
|   /* for any point inside a watertight mesh the number of hits is uneven */
 | |
|   return (userdata.num_hits % 2) == 1;
 | |
| }
 | |
| 
 | |
| static void shape_cut(PEData *data, int pa_index)
 | |
| {
 | |
|   PTCacheEdit *edit = data->edit;
 | |
|   Object *ob = data->ob;
 | |
|   ParticleEditSettings *pset = PE_settings(data->scene);
 | |
|   ParticleCacheKey *key;
 | |
| 
 | |
|   bool cut;
 | |
|   float cut_time = 1.0;
 | |
|   int k, totkeys = 1 << pset->draw_step;
 | |
| 
 | |
|   /* don't cut hidden */
 | |
|   if (edit->points[pa_index].flag & PEP_HIDE) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   cut = false;
 | |
| 
 | |
|   /* check if root is inside the cut shape */
 | |
|   key = edit->pathcache[pa_index];
 | |
|   if (!shape_cut_test_point(data, pset, key)) {
 | |
|     cut_time = -1.0f;
 | |
|     cut = true;
 | |
|   }
 | |
|   else {
 | |
|     for (k = 0; k < totkeys; k++, key++) {
 | |
|       BVHTreeRayHit hit;
 | |
| 
 | |
|       float co_curr_shape[3], co_next_shape[3];
 | |
|       float dir_shape[3];
 | |
|       float len_shape;
 | |
| 
 | |
|       mul_v3_m4v3(co_curr_shape, pset->shape_object->imat, key->co);
 | |
|       mul_v3_m4v3(co_next_shape, pset->shape_object->imat, (key + 1)->co);
 | |
| 
 | |
|       sub_v3_v3v3(dir_shape, co_next_shape, co_curr_shape);
 | |
|       len_shape = normalize_v3(dir_shape);
 | |
| 
 | |
|       memset(&hit, 0, sizeof(hit));
 | |
|       hit.index = -1;
 | |
|       hit.dist = len_shape;
 | |
|       BLI_bvhtree_ray_cast(data->shape_bvh.tree,
 | |
|                            co_curr_shape,
 | |
|                            dir_shape,
 | |
|                            0.0f,
 | |
|                            &hit,
 | |
|                            data->shape_bvh.raycast_callback,
 | |
|                            &data->shape_bvh);
 | |
|       if (hit.index >= 0) {
 | |
|         if (hit.dist < len_shape) {
 | |
|           cut_time = ((hit.dist / len_shape) + (float)k) / (float)totkeys;
 | |
|           cut = true;
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (cut) {
 | |
|     if (cut_time < 0.0f) {
 | |
|       edit->points[pa_index].flag |= PEP_TAG;
 | |
|     }
 | |
|     else {
 | |
|       rekey_particle_to_time(data->context, data->scene, ob, pa_index, cut_time);
 | |
|       edit->points[pa_index].flag |= PEP_EDIT_RECALC;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static int shape_cut_exec(bContext *C, wmOperator *UNUSED(op))
 | |
| {
 | |
|   Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
|   ParticleEditSettings *pset = PE_settings(scene);
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
|   Object *shapeob = pset->shape_object;
 | |
|   int selected = count_selected_keys(scene, edit);
 | |
|   int lock_root = pset->flag & PE_LOCK_FIRST;
 | |
| 
 | |
|   if (!PE_start_edit(edit)) {
 | |
|     return OPERATOR_CANCELLED;
 | |
|   }
 | |
| 
 | |
|   /* disable locking temporatily for disconnected hair */
 | |
|   if (edit->psys && edit->psys->flag & PSYS_GLOBAL_HAIR) {
 | |
|     pset->flag &= ~PE_LOCK_FIRST;
 | |
|   }
 | |
| 
 | |
|   if (edit->psys && edit->pathcache) {
 | |
|     PEData data;
 | |
|     int removed;
 | |
| 
 | |
|     PE_set_data(C, &data);
 | |
|     if (!PE_create_shape_tree(&data, shapeob)) {
 | |
|       /* shapeob may not have faces... */
 | |
|       return OPERATOR_CANCELLED;
 | |
|     }
 | |
| 
 | |
|     if (selected) {
 | |
|       foreach_selected_point(&data, shape_cut);
 | |
|     }
 | |
|     else {
 | |
|       foreach_point(&data, shape_cut);
 | |
|     }
 | |
| 
 | |
|     removed = remove_tagged_particles(ob, edit->psys, pe_x_mirror(ob));
 | |
|     recalc_lengths(edit);
 | |
| 
 | |
|     if (removed) {
 | |
|       update_world_cos(ob, edit);
 | |
|       psys_free_path_cache(NULL, edit);
 | |
|       DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
 | |
|     }
 | |
|     else {
 | |
|       PE_update_object(data.depsgraph, scene, ob, 1);
 | |
|     }
 | |
| 
 | |
|     if (edit->psys) {
 | |
|       WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_EDITED, ob);
 | |
|       BKE_particle_batch_cache_dirty_tag(edit->psys, BKE_PARTICLE_BATCH_DIRTY_ALL);
 | |
|       DEG_id_tag_update(&ob->id, ID_RECALC_PSYS_REDO);
 | |
|     }
 | |
|     else {
 | |
|       DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
 | |
|       WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
 | |
|     }
 | |
| 
 | |
|     PE_free_shape_tree(&data);
 | |
|   }
 | |
| 
 | |
|   pset->flag |= lock_root;
 | |
| 
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_shape_cut(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Shape Cut";
 | |
|   ot->idname = "PARTICLE_OT_shape_cut";
 | |
|   ot->description = "Cut hair to conform to the set shape object";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = shape_cut_exec;
 | |
|   ot->poll = shape_cut_poll;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| }
 | |
| 
 | |
| /************************ utilities ******************************/
 | |
| 
 | |
| int PE_minmax(
 | |
|     Depsgraph *depsgraph, Scene *scene, ViewLayer *view_layer, float min[3], float max[3])
 | |
| {
 | |
|   Object *ob = OBACT(view_layer);
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
|   ParticleSystem *psys;
 | |
|   ParticleSystemModifierData *psmd_eval = NULL;
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
|   float co[3], mat[4][4];
 | |
|   int ok = 0;
 | |
| 
 | |
|   if (!edit) {
 | |
|     return ok;
 | |
|   }
 | |
| 
 | |
|   if ((psys = edit->psys)) {
 | |
|     psmd_eval = edit->psmd_eval;
 | |
|   }
 | |
|   else {
 | |
|     unit_m4(mat);
 | |
|   }
 | |
| 
 | |
|   LOOP_VISIBLE_POINTS {
 | |
|     if (psys) {
 | |
|       psys_mat_hair_to_global(
 | |
|           ob, psmd_eval->mesh_final, psys->part->from, psys->particles + p, mat);
 | |
|     }
 | |
| 
 | |
|     LOOP_SELECTED_KEYS {
 | |
|       copy_v3_v3(co, key->co);
 | |
|       mul_m4_v3(mat, co);
 | |
|       DO_MINMAX(co, min, max);
 | |
|       ok = 1;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!ok) {
 | |
|     BKE_object_minmax(ob, min, max, true);
 | |
|     ok = 1;
 | |
|   }
 | |
| 
 | |
|   return ok;
 | |
| }
 | |
| 
 | |
| /************************ particle edit toggle operator ************************/
 | |
| 
 | |
| /* initialize needed data for bake edit */
 | |
| void PE_create_particle_edit(
 | |
|     Depsgraph *depsgraph, Scene *scene, Object *ob, PointCache *cache, ParticleSystem *psys)
 | |
| {
 | |
|   Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob);
 | |
|   PTCacheEdit *edit;
 | |
|   ParticleSystemModifierData *psmd = (psys) ? psys_get_modifier(ob, psys) : NULL;
 | |
|   ParticleSystemModifierData *psmd_eval = NULL;
 | |
|   POINT_P;
 | |
|   KEY_K;
 | |
|   ParticleData *pa = NULL;
 | |
|   HairKey *hkey;
 | |
|   int totpoint;
 | |
| 
 | |
|   if (psmd != NULL) {
 | |
|     psmd_eval = (ParticleSystemModifierData *)BKE_modifiers_findby_name(ob_eval,
 | |
|                                                                         psmd->modifier.name);
 | |
|   }
 | |
| 
 | |
|   /* no psmd->dm happens in case particle system modifier is not enabled */
 | |
|   if (!(psys && psmd && psmd_eval->mesh_final) && !cache) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (cache && cache->flag & PTCACHE_DISK_CACHE) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (psys == NULL && (cache && BLI_listbase_is_empty(&cache->mem_cache))) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   edit = (psys) ? psys->edit : cache->edit;
 | |
| 
 | |
|   if (!edit) {
 | |
|     ParticleSystem *psys_eval = NULL;
 | |
|     if (psys) {
 | |
|       psys_eval = psys_eval_get(depsgraph, ob, psys);
 | |
|       psys_copy_particles(psys, psys_eval);
 | |
|     }
 | |
| 
 | |
|     totpoint = psys ? psys->totpart : (int)((PTCacheMem *)cache->mem_cache.first)->totpoint;
 | |
| 
 | |
|     edit = MEM_callocN(sizeof(PTCacheEdit), "PE_create_particle_edit");
 | |
|     edit->points = MEM_callocN(totpoint * sizeof(PTCacheEditPoint), "PTCacheEditPoints");
 | |
|     edit->totpoint = totpoint;
 | |
| 
 | |
|     if (psys && !cache) {
 | |
|       edit->psmd = psmd;
 | |
|       edit->psmd_eval = psmd_eval;
 | |
|       psys->edit = edit;
 | |
|       edit->psys = psys;
 | |
|       edit->psys_eval = psys_eval;
 | |
| 
 | |
|       psys->free_edit = PE_free_ptcache_edit;
 | |
| 
 | |
|       edit->pathcache = NULL;
 | |
|       BLI_listbase_clear(&edit->pathcachebufs);
 | |
| 
 | |
|       pa = psys->particles;
 | |
|       LOOP_POINTS {
 | |
|         point->totkey = pa->totkey;
 | |
|         point->keys = MEM_callocN(point->totkey * sizeof(PTCacheEditKey), "ParticleEditKeys");
 | |
|         point->flag |= PEP_EDIT_RECALC;
 | |
| 
 | |
|         hkey = pa->hair;
 | |
|         LOOP_KEYS {
 | |
|           key->co = hkey->co;
 | |
|           key->time = &hkey->time;
 | |
|           key->flag = hkey->editflag;
 | |
|           if (!(psys->flag & PSYS_GLOBAL_HAIR)) {
 | |
|             key->flag |= PEK_USE_WCO;
 | |
|             hkey->editflag |= PEK_USE_WCO;
 | |
|           }
 | |
| 
 | |
|           hkey++;
 | |
|         }
 | |
|         pa++;
 | |
|       }
 | |
|       update_world_cos(ob, edit);
 | |
|     }
 | |
|     else {
 | |
|       PTCacheMem *pm;
 | |
|       int totframe = 0;
 | |
| 
 | |
|       cache->edit = edit;
 | |
|       cache->free_edit = PE_free_ptcache_edit;
 | |
|       edit->psys = NULL;
 | |
| 
 | |
|       for (pm = cache->mem_cache.first; pm; pm = pm->next) {
 | |
|         totframe++;
 | |
|       }
 | |
| 
 | |
|       for (pm = cache->mem_cache.first; pm; pm = pm->next) {
 | |
|         LOOP_POINTS {
 | |
|           void *cur[BPHYS_TOT_DATA];
 | |
|           if (BKE_ptcache_mem_pointers_seek(p, pm, cur) == 0) {
 | |
|             continue;
 | |
|           }
 | |
| 
 | |
|           if (!point->totkey) {
 | |
|             key = point->keys = MEM_callocN(totframe * sizeof(PTCacheEditKey), "ParticleEditKeys");
 | |
|             point->flag |= PEP_EDIT_RECALC;
 | |
|           }
 | |
|           else {
 | |
|             key = point->keys + point->totkey;
 | |
|           }
 | |
| 
 | |
|           key->co = cur[BPHYS_DATA_LOCATION];
 | |
|           key->vel = cur[BPHYS_DATA_VELOCITY];
 | |
|           key->rot = cur[BPHYS_DATA_ROTATION];
 | |
|           key->ftime = (float)pm->frame;
 | |
|           key->time = &key->ftime;
 | |
|           BKE_ptcache_mem_pointers_incr(cur);
 | |
| 
 | |
|           point->totkey++;
 | |
|         }
 | |
|       }
 | |
|       psys = NULL;
 | |
|     }
 | |
| 
 | |
|     recalc_lengths(edit);
 | |
|     if (psys && !cache) {
 | |
|       recalc_emitter_field(depsgraph, ob, psys);
 | |
|     }
 | |
| 
 | |
|     PE_update_object(depsgraph, scene, ob, 1);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool particle_edit_toggle_poll(bContext *C)
 | |
| {
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
| 
 | |
|   if (ob == NULL || ob->type != OB_MESH) {
 | |
|     return 0;
 | |
|   }
 | |
|   if (!ob->data || ID_IS_LINKED(ob->data)) {
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   return (ob->particlesystem.first || BKE_modifiers_findby_type(ob, eModifierType_Cloth) ||
 | |
|           BKE_modifiers_findby_type(ob, eModifierType_Softbody));
 | |
| }
 | |
| 
 | |
| static void free_all_psys_edit(Object *object)
 | |
| {
 | |
|   for (ParticleSystem *psys = object->particlesystem.first; psys != NULL; psys = psys->next) {
 | |
|     if (psys->edit != NULL) {
 | |
|       BLI_assert(psys->free_edit != NULL);
 | |
|       psys->free_edit(psys->edit);
 | |
|       psys->free_edit = NULL;
 | |
|       psys->edit = NULL;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ED_object_particle_edit_mode_enter_ex(Depsgraph *depsgraph, Scene *scene, Object *ob)
 | |
| {
 | |
|   PTCacheEdit *edit;
 | |
| 
 | |
|   ob->mode |= OB_MODE_PARTICLE_EDIT;
 | |
| 
 | |
|   edit = PE_create_current(depsgraph, scene, ob);
 | |
| 
 | |
|   /* Mesh may have changed since last entering editmode.
 | |
|    * note, this may have run before if the edit data was just created,
 | |
|    * so could avoid this and speed up a little. */
 | |
|   if (edit && edit->psys) {
 | |
|     /* Make sure pointer to the evaluated modifier data is up to date,
 | |
|      * with possible changes applied when object was outside of the
 | |
|      * edit mode. */
 | |
|     Object *object_eval = DEG_get_evaluated_object(depsgraph, ob);
 | |
|     edit->psmd_eval = (ParticleSystemModifierData *)BKE_modifiers_findby_name(
 | |
|         object_eval, edit->psmd->modifier.name);
 | |
|     recalc_emitter_field(depsgraph, ob, edit->psys);
 | |
|   }
 | |
| 
 | |
|   toggle_particle_cursor(scene, true);
 | |
|   DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
 | |
|   WM_main_add_notifier(NC_SCENE | ND_MODE | NS_MODE_PARTICLE, NULL);
 | |
| }
 | |
| 
 | |
| void ED_object_particle_edit_mode_enter(bContext *C)
 | |
| {
 | |
|   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
|   ED_object_particle_edit_mode_enter_ex(depsgraph, scene, ob);
 | |
| }
 | |
| 
 | |
| void ED_object_particle_edit_mode_exit_ex(Scene *scene, Object *ob)
 | |
| {
 | |
|   ob->mode &= ~OB_MODE_PARTICLE_EDIT;
 | |
|   toggle_particle_cursor(scene, false);
 | |
|   free_all_psys_edit(ob);
 | |
| 
 | |
|   DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
 | |
|   WM_main_add_notifier(NC_SCENE | ND_MODE | NS_MODE_OBJECT, NULL);
 | |
| }
 | |
| 
 | |
| void ED_object_particle_edit_mode_exit(bContext *C)
 | |
| {
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
|   ED_object_particle_edit_mode_exit_ex(scene, ob);
 | |
| }
 | |
| 
 | |
| static int particle_edit_toggle_exec(bContext *C, wmOperator *op)
 | |
| {
 | |
|   struct wmMsgBus *mbus = CTX_wm_message_bus(C);
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
|   const int mode_flag = OB_MODE_PARTICLE_EDIT;
 | |
|   const bool is_mode_set = (ob->mode & mode_flag) != 0;
 | |
| 
 | |
|   if (!is_mode_set) {
 | |
|     if (!ED_object_mode_compat_set(C, ob, mode_flag, op->reports)) {
 | |
|       return OPERATOR_CANCELLED;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!is_mode_set) {
 | |
|     Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
 | |
|     ED_object_particle_edit_mode_enter_ex(depsgraph, scene, ob);
 | |
|   }
 | |
|   else {
 | |
|     ED_object_particle_edit_mode_exit_ex(scene, ob);
 | |
|   }
 | |
| 
 | |
|   WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode);
 | |
| 
 | |
|   WM_toolsystem_update_from_context_view3d(C);
 | |
| 
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_particle_edit_toggle(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Particle Edit Toggle";
 | |
|   ot->idname = "PARTICLE_OT_particle_edit_toggle";
 | |
|   ot->description = "Toggle particle edit mode";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = particle_edit_toggle_exec;
 | |
|   ot->poll = particle_edit_toggle_poll;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| }
 | |
| 
 | |
| /************************ set editable operator ************************/
 | |
| 
 | |
| static int clear_edited_exec(bContext *C, wmOperator *UNUSED(op))
 | |
| {
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
|   ParticleSystem *psys = psys_get_current(ob);
 | |
| 
 | |
|   if (psys->edit) {
 | |
|     if (psys->edit->edited || 1) {
 | |
|       PE_free_ptcache_edit(psys->edit);
 | |
| 
 | |
|       psys->edit = NULL;
 | |
|       psys->free_edit = NULL;
 | |
| 
 | |
|       psys->recalc |= ID_RECALC_PSYS_RESET;
 | |
|       psys->flag &= ~PSYS_GLOBAL_HAIR;
 | |
|       psys->flag &= ~PSYS_EDITED;
 | |
| 
 | |
|       psys_reset(psys, PSYS_RESET_DEPSGRAPH);
 | |
|       WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_EDITED, ob);
 | |
|       BKE_particle_batch_cache_dirty_tag(psys, BKE_PARTICLE_BATCH_DIRTY_ALL);
 | |
|       DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
 | |
|     }
 | |
|   }
 | |
|   else { /* some operation might have protected hair from editing so let's clear the flag */
 | |
|     psys->recalc |= ID_RECALC_PSYS_RESET;
 | |
|     psys->flag &= ~PSYS_GLOBAL_HAIR;
 | |
|     psys->flag &= ~PSYS_EDITED;
 | |
|     WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_EDITED, ob);
 | |
|     DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
 | |
|   }
 | |
| 
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_edited_clear(wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Clear Edited";
 | |
|   ot->idname = "PARTICLE_OT_edited_clear";
 | |
|   ot->description = "Undo all edition performed on the particle system";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = clear_edited_exec;
 | |
|   ot->poll = particle_edit_toggle_poll;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| }
 | |
| 
 | |
| /************************ Unify length operator ************************/
 | |
| 
 | |
| static float calculate_point_length(PTCacheEditPoint *point)
 | |
| {
 | |
|   float length = 0.0f;
 | |
|   KEY_K;
 | |
|   LOOP_KEYS {
 | |
|     if (k > 0) {
 | |
|       length += len_v3v3((key - 1)->co, key->co);
 | |
|     }
 | |
|   }
 | |
|   return length;
 | |
| }
 | |
| 
 | |
| static float calculate_average_length(PTCacheEdit *edit)
 | |
| {
 | |
|   int num_selected = 0;
 | |
|   float total_length = 0;
 | |
|   POINT_P;
 | |
|   LOOP_SELECTED_POINTS {
 | |
|     total_length += calculate_point_length(point);
 | |
|     num_selected++;
 | |
|   }
 | |
|   if (num_selected == 0) {
 | |
|     return 0.0f;
 | |
|   }
 | |
|   return total_length / num_selected;
 | |
| }
 | |
| 
 | |
| static void scale_point_factor(PTCacheEditPoint *point, float factor)
 | |
| {
 | |
|   float orig_prev_co[3], prev_co[3];
 | |
|   KEY_K;
 | |
|   LOOP_KEYS {
 | |
|     if (k == 0) {
 | |
|       copy_v3_v3(orig_prev_co, key->co);
 | |
|       copy_v3_v3(prev_co, key->co);
 | |
|     }
 | |
|     else {
 | |
|       float new_co[3];
 | |
|       float delta[3];
 | |
| 
 | |
|       sub_v3_v3v3(delta, key->co, orig_prev_co);
 | |
|       mul_v3_fl(delta, factor);
 | |
|       add_v3_v3v3(new_co, prev_co, delta);
 | |
| 
 | |
|       copy_v3_v3(orig_prev_co, key->co);
 | |
|       copy_v3_v3(key->co, new_co);
 | |
|       copy_v3_v3(prev_co, key->co);
 | |
|     }
 | |
|   }
 | |
|   point->flag |= PEP_EDIT_RECALC;
 | |
| }
 | |
| 
 | |
| static void scale_point_to_length(PTCacheEditPoint *point, float length)
 | |
| {
 | |
|   const float point_length = calculate_point_length(point);
 | |
|   if (point_length != 0.0f) {
 | |
|     const float factor = length / point_length;
 | |
|     scale_point_factor(point, factor);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void scale_points_to_length(PTCacheEdit *edit, float length)
 | |
| {
 | |
|   POINT_P;
 | |
|   LOOP_SELECTED_POINTS {
 | |
|     scale_point_to_length(point, length);
 | |
|   }
 | |
|   recalc_lengths(edit);
 | |
| }
 | |
| 
 | |
| static int unify_length_exec(bContext *C, wmOperator *UNUSED(op))
 | |
| {
 | |
|   Object *ob = CTX_data_active_object(C);
 | |
|   Scene *scene = CTX_data_scene(C);
 | |
|   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
 | |
| 
 | |
|   PTCacheEdit *edit = PE_get_current(depsgraph, scene, ob);
 | |
|   float average_length = calculate_average_length(edit);
 | |
| 
 | |
|   if (average_length == 0.0f) {
 | |
|     return OPERATOR_CANCELLED;
 | |
|   }
 | |
|   scale_points_to_length(edit, average_length);
 | |
| 
 | |
|   PE_update_object(depsgraph, scene, ob, 1);
 | |
|   if (edit->psys) {
 | |
|     WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_EDITED, ob);
 | |
|   }
 | |
|   else {
 | |
|     DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
 | |
|     WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
 | |
|   }
 | |
| 
 | |
|   return OPERATOR_FINISHED;
 | |
| }
 | |
| 
 | |
| void PARTICLE_OT_unify_length(struct wmOperatorType *ot)
 | |
| {
 | |
|   /* identifiers */
 | |
|   ot->name = "Unify Length";
 | |
|   ot->idname = "PARTICLE_OT_unify_length";
 | |
|   ot->description = "Make selected hair the same length";
 | |
| 
 | |
|   /* api callbacks */
 | |
|   ot->exec = unify_length_exec;
 | |
|   ot->poll = PE_poll_view3d;
 | |
| 
 | |
|   /* flags */
 | |
|   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | |
| }
 |