Diff
checker
Testo
Testo
Immagini
Documenti
Excel
Cartelle
Legal
Enterprise
Applicazione per desktop
Prezzi
Accedi
Scarica Diffchecker Desktop
Confronta il testo
Trova la differenza tra due file di testo
Strumenti
Cronologia
Editor live
Comprimi invariate
Senza a capo
Layout
Diviso
Unificato
Livello di dettaglio
Intelligente
Parola
Carattere
Evidenziazione sintassi
Scegli sintassi
Ignora
Trasforma testo
Vai alla prima modifica
Modifica input
Diffchecker Desktop
Il modo più sicuro per usare Diffchecker. Ottieni l'app Diffchecker Desktop: i tuoi diff non lasciano mai il tuo computer!
Ottieni Desktop
Untitled diff
Creato
7 anni fa
Il diff non scade mai
Eliminare
Esporta
Condividere
Spiegare
311 rimozioni
Linee
Totale
Rimosso
Caratteri
Totale
Rimosso
Per continuare a utilizzare questa funzione, aggiorna a
Diff
checker
Pro
Visualizza prezzi
1050 linee
Copia tutti
275 aggiunte
Linee
Totale
Aggiunto
Caratteri
Totale
Aggiunto
Per continuare a utilizzare questa funzione, aggiorna a
Diff
checker
Pro
Visualizza prezzi
1047 linee
Copia tutti
/*
/*
* This program is free software; you can redistribute it and/or
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* of the License, or (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*
* The Original Code is Copyright (C) 2007 by Janne Karhu.
* The Original Code is Copyright (C) 2007 by Janne Karhu.
* All rights reserved.
* All rights reserved.
*/
*/
Copia
Copiato
Copia
Copiato
/** \file
/** \file
blender/editors/physics/particle_edit.c
*
\ingroup edphys
*
\ingroup edphys
*/
*/
#include <stdlib.h>
#include <stdlib.h>
#include <math.h>
#include <math.h>
#include <string.h>
#include <string.h>
#include <assert.h>
#include <assert.h>
#include "MEM_guardedalloc.h"
#include "MEM_guardedalloc.h"
#include "DNA_scene_types.h"
#include "DNA_scene_types.h"
#include "DNA_mesh_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_view3d_types.h"
#include "DNA_view3d_types.h"
#include "DNA_screen_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "DNA_space_types.h"
#include "BLI_math.h"
#include "BLI_math.h"
#include "BLI_lasso_2d.h"
#include "BLI_lasso_2d.h"
#include "BLI_listbase.h"
#include "BLI_listbase.h"
#include "BLI_kdtree.h"
#include "BLI_kdtree.h"
#include "BLI_rand.h"
#include "BLI_rand.h"
Copia
Copiato
Copia
Copiato
#include "BLI_task.h"
#include "BLI_utildefines.h"
#include "BLI_utildefines.h"
Copia
Copiato
Copia
Copiato
#include "BKE_bvhutils.h"
#include "BKE_context.h"
#include "BKE_context.h"
Copia
Copiato
Copia
Copiato
#include "BKE_depsgraph.h"
#include "BKE_DerivedMesh.h"
#include "BKE_global.h"
#include "BKE_global.h"
Copia
Copiato
Copia
Copiato
#include "BKE_object.h"
#include "BKE_main.h"
#include "BKE_main.h"
#include "BKE_mesh.h"
#include "BKE_mesh.h"
Copia
Copiato
Copia
Copiato
#include "BKE_mesh_runtime.h"
#include "BKE_modifier.h"
#include "BKE_modifier.h"
Copia
Copiato
Copia
Copiato
#include "BKE_object.h"
#include "BKE_particle.h"
#include "BKE_particle.h"
Copia
Copiato
Copia
Copiato
#include "BKE_report.h"
#include "BKE_bvhutils.h"
#include "BKE_pointcache.h"
#include "BKE_pointcache.h"
Copia
Copiato
Copia
Copiato
#include "BKE_report.h"
#include "BKE_scene.h"
#include "DEG_depsgraph.h"
Copia
Copiato
Copia
Copiato
#include "BIF_gl.h"
#include "BIF_glutil.h"
#include "ED_object.h"
#include "ED_object.h"
#include "ED_physics.h"
#include "ED_physics.h"
#include "ED_mesh.h"
#include "ED_mesh.h"
#include "ED_particle.h"
#include "ED_particle.h"
Copia
Copiato
Copia
Copiato
#include "ED_screen.h"
#include "ED_select_utils.h"
#include "ED_view3d.h"
#include "ED_view3d.h"
Copia
Copiato
Copia
Copiato
#include "GPU_immediate.h"
#include "GPU_immediate_util.h"
#include "GPU_state.h"
#include "UI_resources.h"
#include "UI_resources.h"
#include "WM_api.h"
#include "WM_api.h"
#include "WM_types.h"
#include "WM_types.h"
Copia
Copiato
Copia
Copiato
#include "WM_message.h"
#include "WM_toolsystem.h"
#include "RNA_access.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "RNA_define.h"
Copia
Copiato
Copia
Copiato
#include "DEG_depsgraph_query.h"
#include "PIL_time_utildefines.h"
#include "physics_intern.h"
#include "physics_intern.h"
#include "particle_edit_utildefines.h"
#include "particle_edit_utildefines.h"
/**************************** utilities *******************************/
/**************************** utilities *******************************/
bool PE_poll(bContext *C)
bool PE_poll(bContext *C)
{
{
Copia
Copiato
Copia
Copiato
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
Object *ob = CTX_data_active_object(C);
Copia
Copiato
Copia
Copiato
if (!scene || !ob || !(ob->mode & OB_MODE_PARTICLE_EDIT))
{
if (!scene || !ob || !(ob->mode & OB_MODE_PARTICLE_EDIT))
return 0;
return 0;
Copia
Copiato
Copia
Copiato
}
return (PE_get_current(
scene, ob) != NULL);
return (PE_get_current(
bmain,
scene, ob) != NULL);
}
}
bool PE_hair_poll(bContext *C)
bool PE_hair_poll(bContext *C)
{
{
Copia
Copiato
Copia
Copiato
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
Object *ob = CTX_data_active_object(C);
PTCacheEdit *edit;
PTCacheEdit *edit;
Copia
Copiato
Copia
Copiato
if (!scene || !ob || !(ob->mode & OB_MODE_PARTICLE_EDIT))
{
if (!scene || !ob || !(ob->mode & OB_MODE_PARTICLE_EDIT))
return 0;
return 0;
Copia
Copiato
Copia
Copiato
}
edit = PE_get_current(
scene, ob);
edit = PE_get_current(
bmain,
scene, ob);
return (edit && edit->psys);
return (edit && edit->psys);
}
}
bool PE_poll_view3d(bContext *C)
bool PE_poll_view3d(bContext *C)
{
{
ScrArea *sa = CTX_wm_area(C);
ScrArea *sa = CTX_wm_area(C);
ARegion *ar = CTX_wm_region(C);
ARegion *ar = CTX_wm_region(C);
return (PE_poll(C) &&
return (PE_poll(C) &&
(sa && sa->spacetype == SPACE_VIEW3D) &&
(sa && sa->spacetype == SPACE_VIEW3D) &&
(ar && ar->regiontype == RGN_TYPE_WINDOW));
(ar && ar->regiontype == RGN_TYPE_WINDOW));
}
}
void PE_free_ptcache_edit(PTCacheEdit *edit)
void PE_free_ptcache_edit(PTCacheEdit *edit)
{
{
POINT_P;
POINT_P;
if (edit == 0) return;
if (edit == 0) return;
if (edit->points) {
if (edit->points) {
LOOP_POINTS {
LOOP_POINTS {
if (point->keys)
if (point->keys)
MEM_freeN(point->keys);
MEM_freeN(point->keys);
}
}
MEM_freeN(edit->points);
MEM_freeN(edit->points);
}
}
if (edit->mirror_cache)
if (edit->mirror_cache)
MEM_freeN(edit->mirror_cache);
MEM_freeN(edit->mirror_cache);
if (edit->emitter_cosnos) {
if (edit->emitter_cosnos) {
MEM_freeN(edit->emitter_cosnos);
MEM_freeN(edit->emitter_cosnos);
edit->emitter_cosnos = 0;
edit->emitter_cosnos = 0;
}
}
if (edit->emitter_field) {
if (edit->emitter_field) {
Copia
Copiato
Copia
Copiato
BLI_kdtree_
3d_
free(edit->emitter_field);
BLI_kdtree_
free(edit->emitter_field);
edit->emitter_field = 0;
edit->emitter_field = 0;
}
}
psys_free_path_cache(edit->psys, edit);
psys_free_path_cache(edit->psys, edit);
MEM_freeN(edit);
MEM_freeN(edit);
}
}
/************************************************/
/************************************************/
/* Edit Mode Helpers */
/* Edit Mode Helpers */
/************************************************/
/************************************************/
int PE_start_edit(PTCacheEdit *edit)
int PE_start_edit(PTCacheEdit *edit)
{
{
if (edit) {
if (edit) {
edit->edited = 1;
edit->edited = 1;
if (edit->psys)
if (edit->psys)
edit->psys->flag |= PSYS_EDITED;
edit->psys->flag |= PSYS_EDITED;
return 1;
return 1;
}
}
return 0;
return 0;
}
}
ParticleEditSettings *PE_settings(Scene *scene)
ParticleEditSettings *PE_settings(Scene *scene)
{
{
return scene->toolsettings ? &scene->toolsettings->particle : NULL;
return scene->toolsettings ? &scene->toolsettings->particle : NULL;
}
}
static float pe_brush_size_get(const Scene *UNUSED(scene), ParticleBrushData *brush)
static float pe_brush_size_get(const Scene *UNUSED(scene), ParticleBrushData *brush)
{
{
// here we can enable unified brush size, needs more work...
// here we can enable unified brush size, needs more work...
// UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
// UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
// float size = (ups->flag & UNIFIED_PAINT_SIZE) ? ups->size : brush->size;
// float size = (ups->flag & UNIFIED_PAINT_SIZE) ? ups->size : brush->size;
return brush->size * U.pixelsize;
return brush->size * U.pixelsize;
}
}
Copia
Copiato
Copia
Copiato
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;
}
else {
return psys->edit;
}
}
else 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
/* always gets at least the first particlesystem even if PSYS_CURRENT flag is not set
*
*
* note: this function runs on poll, therefor it can runs many times a second
* note: this function runs on poll, therefor it can runs many times a second
* keep it fast! */
* keep it fast! */
Copia
Copiato
Copia
Copiato
static PTCacheEdit *pe_get_current(
static PTCacheEdit *pe_get_current(
Main *bmain
, Scene *scene, Object *ob, int create)
Depsgraph *depsgraph
, Scene *scene, Object *ob, int create)
{
{
ParticleEditSettings *pset = PE_settings(scene);
ParticleEditSettings *pset = PE_settings(scene);
PTCacheEdit *edit = NULL;
PTCacheEdit *edit = NULL;
ListBase pidlist;
ListBase pidlist;
PTCacheID *pid;
PTCacheID *pid;
if (pset == NULL || ob == NULL)
if (pset == NULL || ob == NULL)
return NULL;
return NULL;
pset->scene = scene;
pset->scene = scene;
pset->object = ob;
pset->object = ob;
Copia
Copiato
Copia
Copiato
BKE_ptcache_ids_from_object(
&pidlist, ob, NULL, 0);
BKE_ptcache_ids_from_object(
bmain,
&pidlist, ob, NULL, 0);
/* in the case of only one editable thing, set pset->edittype accordingly */
/* in the case of only one editable thing, set pset->edittype accordingly */
if (BLI_listbase_is_single(&pidlist)) {
if (BLI_listbase_is_single(&pidlist)) {
pid = pidlist.first;
pid = pidlist.first;
switch (pid->type) {
switch (pid->type) {
case PTCACHE_TYPE_PARTICLES:
case PTCACHE_TYPE_PARTICLES:
pset->edittype = PE_TYPE_PARTICLES;
pset->edittype = PE_TYPE_PARTICLES;
break;
break;
case PTCACHE_TYPE_SOFTBODY:
case PTCACHE_TYPE_SOFTBODY:
pset->edittype = PE_TYPE_SOFTBODY;
pset->edittype = PE_TYPE_SOFTBODY;
break;
break;
case PTCACHE_TYPE_CLOTH:
case PTCACHE_TYPE_CLOTH:
pset->edittype = PE_TYPE_CLOTH;
pset->edittype = PE_TYPE_CLOTH;
break;
break;
}
}
}
}
for (pid = pidlist.first; pid; pid = pid->next) {
for (pid = pidlist.first; pid; pid = pid->next) {
if (pset->edittype == PE_TYPE_PARTICLES && pid->type == PTCACHE_TYPE_PARTICLES) {
if (pset->edittype == PE_TYPE_PARTICLES && pid->type == PTCACHE_TYPE_PARTICLES) {
ParticleSystem *psys = pid->calldata;
ParticleSystem *psys = pid->calldata;
if (psys->flag & PSYS_CURRENT) {
if (psys->flag & PSYS_CURRENT) {
if (psys->part && psys->part->type == PART_HAIR) {
if (psys->part && psys->part->type == PART_HAIR) {
if (psys->flag & PSYS_HAIR_DYNAMICS && psys->pointcache->flag & PTCACHE_BAKED) {
if (psys->flag & PSYS_HAIR_DYNAMICS && psys->pointcache->flag & PTCACHE_BAKED) {
if (create && !psys->pointcache->edit)
if (create && !psys->pointcache->edit)
Copia
Copiato
Copia
Copiato
PE_create_particle_edit(
depsgraph
, scene, ob, pid->cache, NULL);
PE_create_particle_edit(
bmain
, scene, ob, pid->cache, NULL);
edit = pid->cache->edit;
edit = pid->cache->edit;
}
}
else {
else {
Copia
Copiato
Copia
Copiato
if (create && !psys->edit
) {
if (create && !psys->edit
&&
psys->flag & PSYS_HAIR_DONE)
if (
psys->flag & PSYS_HAIR_DONE)
{
PE_create_particle_edit(
bmain
, scene, ob, NULL, psys);
PE_create_particle_edit(
depsgraph
, scene, ob, NULL, psys);
}
}
edit = psys->edit;
edit = psys->edit;
}
}
}
}
else {
else {
if (create && pid->cache->flag & PTCACHE_BAKED && !pid->cache->edit)
if (create && pid->cache->flag & PTCACHE_BAKED && !pid->cache->edit)
Copia
Copiato
Copia
Copiato
PE_create_particle_edit(
depsgraph
, scene, ob, pid->cache, psys);
PE_create_particle_edit(
bmain
, scene, ob, pid->cache, psys);
edit = pid->cache->edit;
edit = pid->cache->edit;
}
}
break;
break;
}
}
}
}
else if (pset->edittype == PE_TYPE_SOFTBODY && pid->type == PTCACHE_TYPE_SOFTBODY) {
else if (pset->edittype == PE_TYPE_SOFTBODY && pid->type == PTCACHE_TYPE_SOFTBODY) {
if (create && pid->cache->flag & PTCACHE_BAKED && !pid->cache->edit) {
if (create && pid->cache->flag & PTCACHE_BAKED && !pid->cache->edit) {
pset->flag |= PE_FADE_TIME;
pset->flag |= PE_FADE_TIME;
// NICE TO HAVE but doesn't work: pset->brushtype = PE_BRUSH_COMB;
// NICE TO HAVE but doesn't work: pset->brushtype = PE_BRUSH_COMB;
Copia
Copiato
Copia
Copiato
PE_create_particle_edit(
depsgraph
, scene, ob, pid->cache, NULL);
PE_create_particle_edit(
bmain
, scene, ob, pid->cache, NULL);
}
}
edit = pid->cache->edit;
edit = pid->cache->edit;
break;
break;
}
}
else if (pset->edittype == PE_TYPE_CLOTH && pid->type == PTCACHE_TYPE_CLOTH) {
else if (pset->edittype == PE_TYPE_CLOTH && pid->type == PTCACHE_TYPE_CLOTH) {
if (create && pid->cache->flag & PTCACHE_BAKED && !pid->cache->edit) {
if (create && pid->cache->flag & PTCACHE_BAKED && !pid->cache->edit) {
pset->flag |= PE_FADE_TIME;
pset->flag |= PE_FADE_TIME;
// NICE TO HAVE but doesn't work: pset->brushtype = PE_BRUSH_COMB;
// NICE TO HAVE but doesn't work: pset->brushtype = PE_BRUSH_COMB;
Copia
Copiato
Copia
Copiato
PE_create_particle_edit(
depsgraph
, scene, ob, pid->cache, NULL);
PE_create_particle_edit(
bmain
, scene, ob, pid->cache, NULL);
}
}
edit = pid->cache->edit;
edit = pid->cache->edit;
break;
break;
}
}
}
}
Copia
Copiato
Copia
Copiato
if (edit)
{
if (edit)
edit->pid = *pid;
edit->pid = *pid;
Copia
Copiato
Copia
Copiato
if (edit->flags & PT_CACHE_EDIT_UPDATE_PARTICLE_FROM_EVAL) {
if (edit->psys != 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);
BLI_freelistN(&pidlist);
return edit;
return edit;
}
}
Copia
Copiato
Copia
Copiato
PTCacheEdit *PE_get_current(
Scene *scene, Object *ob)
PTCacheEdit *PE_get_current(
Main *bmain,
Scene *scene, Object *ob)
{
{
Copia
Copiato
Copia
Copiato
return pe_get_current(
NULL
, scene, ob, 0);
return pe_get_current(
bmain
, scene, ob, 0);
}
}
Copia
Copiato
Copia
Copiato
PTCacheEdit *PE_create_current(
Depsgraph *depsgraph
, Scene *scene, Object *ob)
PTCacheEdit *PE_create_current(
Main *bmain
, Scene *scene, Object *ob)
{
{
Copia
Copiato
Copia
Copiato
return pe_get_current(
depsgraph
, scene, ob, 1);
return pe_get_current(
bmain
, scene, ob, 1);
}
}
Copia
Copiato
Copia
Copiato
void PE_current_changed(
Depsgraph *depsgraph
, Scene *scene, Object *ob)
void PE_current_changed(
Main *bmain
, Scene *scene, Object *ob)
{
{
Copia
Copiato
Copia
Copiato
if (ob->mode == OB_MODE_PARTICLE_EDIT)
{
if (ob->mode == OB_MODE_PARTICLE_EDIT)
PE_create_current(
depsgraph
, scene, ob);
PE_create_current(
bmain
, scene, ob);
}
}
}
void PE_hide_keys_time(Scene *scene, PTCacheEdit *edit, float cfra)
void PE_hide_keys_time(Scene *scene, PTCacheEdit *edit, float cfra)
{
{
ParticleEditSettings *pset = PE_settings(scene);
ParticleEditSettings *pset = PE_settings(scene);
POINT_P; KEY_K;
POINT_P; KEY_K;
if (pset->flag & PE_FADE_TIME && pset->selectmode == SCE_SELECT_POINT) {
if (pset->flag & PE_FADE_TIME && pset->selectmode == SCE_SELECT_POINT) {
LOOP_POINTS {
LOOP_POINTS {
LOOP_KEYS {
LOOP_KEYS {
if (fabsf(cfra - *key->time) < pset->fade_frames)
if (fabsf(cfra - *key->time) < pset->fade_frames)
key->flag &= ~PEK_HIDE;
key->flag &= ~PEK_HIDE;
else {
else {
key->flag |= PEK_HIDE;
key->flag |= PEK_HIDE;
//key->flag &= ~PEK_SELECT;
//key->flag &= ~PEK_SELECT;
}
}
}
}
}
}
}
}
else {
else {
LOOP_POINTS {
LOOP_POINTS {
LOOP_KEYS {
LOOP_KEYS {
key->flag &= ~PEK_HIDE;
key->flag &= ~PEK_HIDE;
}
}
}
}
}
}
}
}
static int pe_x_mirror(Object *ob)
static int pe_x_mirror(Object *ob)
{
{
if (ob->type == OB_MESH)
if (ob->type == OB_MESH)
return (((Mesh *)ob->data)->editflag & ME_EDIT_MIRROR_X);
return (((Mesh *)ob->data)->editflag & ME_EDIT_MIRROR_X);
return 0;
return 0;
}
}
/****************** common struct passed to callbacks ******************/
/****************** common struct passed to callbacks ******************/
typedef struct PEData {
typedef struct PEData {
ViewContext vc;
ViewContext vc;
Copia
Copiato
Copia
Copiato
bglMats mats;
Copia
Copiato
Copia
Copiato
const bContext *context;
Main *bmain;
Main *bmain;
Scene *scene;
Scene *scene;
Copia
Copiato
Copia
Copiato
ViewLayer *view_layer;
Object *ob;
Object *ob;
Copia
Copiato
Copia
Copiato
Mesh *mesh
;
DerivedMesh *dm
;
PTCacheEdit *edit;
PTCacheEdit *edit;
BVHTreeFromMesh shape_bvh;
BVHTreeFromMesh shape_bvh;
Copia
Copiato
Copia
Copiato
Depsgraph *depsgraph;
RNG *rng;
const int *mval;
const int *mval;
const rcti *rect;
const rcti *rect;
float rad;
float rad;
Copia
Copiato
Copia
Copiato
float dist;
float dval;
float dval;
int select;
int select;
Copia
Copiato
Copia
Copiato
eSelectOp sel_op;
float *dvec;
float *dvec;
float combfac;
float combfac;
float pufffac;
float pufffac;
float cutfac;
float cutfac;
float smoothfac;
float smoothfac;
float weightfac;
float weightfac;
float growfac;
float growfac;
int totrekey;
int totrekey;
int invert;
int invert;
int tot;
int tot;
float vec[3];
float vec[3];
int select_action;
int select_action;
int select_toggle_action;
int select_toggle_action;
Copia
Copiato
Copia
Copiato
bool is_changed;
} PEData;
} PEData;
static void PE_set_data(bContext *C, PEData *data)
static void PE_set_data(bContext *C, PEData *data)
{
{
memset(data, 0, sizeof(*data));
memset(data, 0, sizeof(*data));
Copia
Copiato
Copia
Copiato
data->context = C;
data->bmain = CTX_data_main(C);
data->bmain = CTX_data_main(C);
data->scene = CTX_data_scene(C);
data->scene = CTX_data_scene(C);
Copia
Copiato
Copia
Copiato
data->view_layer = CTX_data_view_layer(C);
data->ob = CTX_data_active_object(C);
data->ob = CTX_data_active_object(C);
Copia
Copiato
Copia
Copiato
data->depsgraph = CTX_data_depsgraph(C);
data->edit = PE_get_current(
data->bmain,
data->scene, data->ob);
data->edit = PE_get_current(
data->scene, data->ob);
}
}
static void PE_set_view3d_data(bContext *C, PEData *data)
static void PE_set_view3d_data(bContext *C, PEData *data)
{
{
PE_set_data(C, data);
PE_set_data(C, data);
ED_view3d_viewcontext_init(C, &data->vc);
ED_view3d_viewcontext_init(C, &data->vc);
Copia
Copiato
Copia
Copiato
/* note, the object argument means the modelview matrix does not account for the objects matrix, use viewmat rather than (obmat * viewmat) */
view3d_get_transformation(data->vc.ar, data->vc.rv3d, NULL, &data->mats);
if (V3D_IS_ZBUF(data->vc.v3d)) {
if (V3D_IS_ZBUF(data->vc.v3d)) {
if (data->vc.v3d->flag & V3D_INVALID_BACKBUF) {
if (data->vc.v3d->flag & V3D_INVALID_BACKBUF) {
/* needed or else the draw matrix can be incorrect */
/* needed or else the draw matrix can be incorrect */
view3d_operator_needs_opengl(C);
view3d_operator_needs_opengl(C);
Copia
Copiato
Copia
Copiato
ED_view3d_backbuf_
depth_
validate(&data->vc);
ED_view3d_backbuf_
validate(&data->vc);
/* we may need to force an update here by setting the rv3d as dirty
/* we may need to force an update here by setting the rv3d as dirty
* for now it seems ok, but take care!:
* for now it seems ok, but take care!:
* rv3d->depths->dirty = 1; */
* rv3d->depths->dirty = 1; */
ED_view3d_depth_update(data->vc.ar);
ED_view3d_depth_update(data->vc.ar);
}
}
}
}
}
}
static bool PE_create_shape_tree(PEData *data, Object *shapeob)
static bool PE_create_shape_tree(PEData *data, Object *shapeob)
{
{
Copia
Copiato
Copia
Copiato
Mesh *mesh = BKE_object_get_evaluated_mesh(data->depsgraph,
shapeob
)
;
DerivedMesh *dm =
shapeob
->derivedFinal
;
memset(&data->shape_bvh, 0, sizeof(data->shape_bvh));
memset(&data->shape_bvh, 0, sizeof(data->shape_bvh));
Copia
Copiato
Copia
Copiato
if (!
mesh
) {
if (!
dm
) {
return false;
return false;
}
}
Copia
Copiato
Copia
Copiato
return (
BKE_
bvhtree_from_mesh_get(&data->shape_bvh,
mesh
, BVHTREE_FROM_LOOPTRI, 4) != NULL);
return (
bvhtree_from_mesh_get(&data->shape_bvh,
dm
, BVHTREE_FROM_LOOPTRI, 4) != NULL);
}
}
static void PE_free_shape_tree(PEData *data)
static void PE_free_shape_tree(PEData *data)
{
{
free_bvhtree_from_mesh(&data->shape_bvh);
free_bvhtree_from_mesh(&data->shape_bvh);
}
}
Copia
Copiato
Copia
Copiato
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;
Text moved to lines 898-900
}
}
/*************************** selection utilities *******************************/
/*************************** selection utilities *******************************/
static bool key_test_depth(PEData *data, const float co[3], const int screen_co[2])
static bool key_test_depth(PEData *data, const float co[3], const int screen_co[2])
{
{
View3D *v3d = data->vc.v3d;
View3D *v3d = data->vc.v3d;
ViewDepths *vd = data->vc.rv3d->depths;
ViewDepths *vd = data->vc.rv3d->depths;
Copia
Copiato
Copia
Copiato
double ux, uy, uz;
float depth;
float depth;
/* nothing to do */
/* nothing to do */
if (!V3D_IS_ZBUF(v3d))
if (!V3D_IS_ZBUF(v3d))
return true;
return true;
/* used to calculate here but all callers have the screen_co already, so pass as arg */
/* used to calculate here but all callers have the screen_co already, so pass as arg */
#if 0
#if 0
if (ED_view3d_project_int_global(data->vc.ar, co, screen_co,
if (ED_view3d_project_int_global(data->vc.ar, co, screen_co,
V3D_PROJ_TEST_CLIP_BB | V3D_PROJ_TEST_CLIP_WIN | V3D_PROJ_TEST_CLIP_NEAR) != V3D_PROJ_RET_OK)
V3D_PROJ_TEST_CLIP_BB | V3D_PROJ_TEST_CLIP_WIN | V3D_PROJ_TEST_CLIP_NEAR) != V3D_PROJ_RET_OK)
{
{
return 0;
return 0;
}
}
#endif
#endif
Copia
Copiato
Copia
Copiato
gluProject(co[0], co[1], co[2], data->mats.modelview, data->mats.projection,
(GLint *)data->mats.viewport, &ux, &uy, &uz);
/* check if screen_co is within bounds because brush_cut uses out of screen coords */
/* 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) {
if (screen_co[0] >= 0 && screen_co[0] < vd->w && screen_co[1] >= 0 && screen_co[1] < vd->h) {
BLI_assert(vd && vd->depths);
BLI_assert(vd && vd->depths);
/* we know its not clipped */
/* we know its not clipped */
depth = vd->depths[screen_co[1] * vd->w + screen_co[0]];
depth = vd->depths[screen_co[1] * vd->w + screen_co[0]];
}
}
else
else
return 0;
return 0;
Copia
Copiato
Copia
Copiato
float
win[3];
if ((
float
)uz
- 0.00001f > depth)
ED_view3d_project(data->vc.ar, co, win);
if (win[2]
- 0.00001f > depth)
return 0;
return 0;
else
else
return 1;
return 1;
}
}
static bool key_inside_circle(PEData *data, float rad, const float co[3], float *distance)
static bool key_inside_circle(PEData *data, float rad, const float co[3], float *distance)
{
{
float dx, dy, dist;
float dx, dy, dist;
int screen_co[2];
int screen_co[2];
/* TODO, should this check V3D_PROJ_TEST_CLIP_BB too? */
/* TODO, should this check V3D_PROJ_TEST_CLIP_BB too? */
if (ED_view3d_project_int_global(data->vc.ar, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK) {
if (ED_view3d_project_int_global(data->vc.ar, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK) {
return 0;
return 0;
}
}
dx = data->mval[0] - screen_co[0];
dx = data->mval[0] - screen_co[0];
dy = data->mval[1] - screen_co[1];
dy = data->mval[1] - screen_co[1];
dist = sqrtf(dx * dx + dy * dy);
dist = sqrtf(dx * dx + dy * dy);
if (dist > rad)
if (dist > rad)
return 0;
return 0;
if (key_test_depth(data, co, screen_co)) {
if (key_test_depth(data, co, screen_co)) {
if (distance)
if (distance)
*distance = dist;
*distance = dist;
return 1;
return 1;
}
}
return 0;
return 0;
}
}
static bool key_inside_rect(PEData *data, const float co[3])
static bool key_inside_rect(PEData *data, const float co[3])
{
{
int screen_co[2];
int screen_co[2];
if (ED_view3d_project_int_global(data->vc.ar, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK) {
if (ED_view3d_project_int_global(data->vc.ar, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK) {
return 0;
return 0;
}
}
if (screen_co[0] > data->rect->xmin && screen_co[0] < data->rect->xmax &&
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)
screen_co[1] > data->rect->ymin && screen_co[1] < data->rect->ymax)
{
{
return key_test_depth(data, co, screen_co);
return key_test_depth(data, co, screen_co);
}
}
return 0;
return 0;
}
}
static bool key_inside_test(PEData *data, const float co[3])
static bool key_inside_test(PEData *data, const float co[3])
{
{
if (data->mval)
if (data->mval)
return key_inside_circle(data, data->rad, co, NULL);
return key_inside_circle(data, data->rad, co, NULL);
else
else
return key_inside_rect(data, co);
return key_inside_rect(data, co);
}
}
static bool point_is_selected(PTCacheEditPoint *point)
static bool point_is_selected(PTCacheEditPoint *point)
{
{
KEY_K;
KEY_K;
if (point->flag & PEP_HIDE)
if (point->flag & PEP_HIDE)
return 0;
return 0;
LOOP_SELECTED_KEYS {
LOOP_SELECTED_KEYS {
return 1;
return 1;
}
}
return 0;
return 0;
}
}
/*************************** iterators *******************************/
/*************************** iterators *******************************/
Copia
Copiato
Copia
Copiato
typedef void (*ForPointFunc)(
typedef void (*ForPointFunc)(
PEData *data,
int point_index);
PEData *data,
typedef void (*ForKeyFunc)(
PEData *data,
int point_index,
int key_index
);
int point_index);
typedef void (*ForKeyMatFunc)(
PEData *data,
float mat[4][4],
float imat[4][4],
int point_index,
int key_index,
PTCacheEditKey *key);
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,
float mat[4][4],
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),
};
Copia
Copiato
Copia
Copiato
static void for_mouse_hit_keys(PEData *data, ForKeyFunc func,
const enum eParticleSelectFlag flag
)
static void for_mouse_hit_keys(PEData *data, ForKeyFunc func,
bool nearest
)
{
{
ParticleEditSettings *pset = PE_settings(data->scene);
ParticleEditSettings *pset = PE_settings(data->scene);
PTCacheEdit *edit = data->edit;
PTCacheEdit *edit = data->edit;
POINT_P; KEY_K;
POINT_P; KEY_K;
int nearest_point, nearest_key;
int nearest_point, nearest_key;
float dist = data->rad;
float dist = data->rad;
/* in path select mode we have no keys */
/* in path select mode we have no keys */
if (pset->selectmode == SCE_SELECT_PATH)
if (pset->selectmode == SCE_SELECT_PATH)
return;
return;
nearest_point = -1;
nearest_point = -1;
nearest_key = -1;
nearest_key = -1;
LOOP_VISIBLE_POINTS {
LOOP_VISIBLE_POINTS {
if (pset->selectmode == SCE_SELECT_END) {
if (pset->selectmode == SCE_SELECT_END) {
if (point->totkey) {
if (point->totkey) {
/* only do end keys */
/* only do end keys */
key = point->keys + point->totkey - 1;
key = point->keys + point->totkey - 1;
Copia
Copiato
Copia
Copiato
if (
flag & PSEL_NEAREST
) {
if (
nearest
) {
if (key_inside_circle(data, dist, KEY_WCO, &dist)) {
if (key_inside_circle(data, dist, KEY_WCO, &dist)) {
nearest_point = p;
nearest_point = p;
nearest_key = point->totkey - 1;
nearest_key = point->totkey - 1;
}
}
}
}
Copia
Copiato
Copia
Copiato
else
{
else
if (
key_inside_test(data, KEY_WCO)
)
const bool is_inside =
key_inside_test(data, KEY_WCO)
;
func(data, p, point->totkey - 1
);
if (is_inside || (flag & PSEL_ALL_KEYS)) {
func(data, p, point->totkey - 1
, is_inside);
}
}
}
}
}
}
else {
else {
/* do all keys */
/* do all keys */
LOOP_VISIBLE_KEYS {
LOOP_VISIBLE_KEYS {
Copia
Copiato
Copia
Copiato
if (
flag & PSEL_NEAREST
) {
if (
nearest
) {
if (key_inside_circle(data, dist, KEY_WCO, &dist)) {
if (key_inside_circle(data, dist, KEY_WCO, &dist)) {
nearest_point = p;
nearest_point = p;
nearest_key = k;
nearest_key = k;
}
}
}
}
Copia
Copiato
Copia
Copiato
else
{
else
if (
key_inside_test(data, KEY_WCO)
)
const bool is_inside =
key_inside_test(data, KEY_WCO)
;
func(data, p, k);
if (is_inside || (flag & PSEL_ALL_KEYS)) {
func(data, p, k, is_inside);
}
}
}
}
}
}
}
}
/* do nearest only */
/* do nearest only */
Copia
Copiato
Copia
Copiato
if (flag & PSEL_NEAREST) {
if (
nearest &&
nearest_point
>
-1)
if (
nearest_point
!=
-1)
{
func(data, nearest_point, nearest_key
);
func(data, nearest_point, nearest_key
, true);
}
}
}
}
Copia
Copiato
Copia
Copiato
static void foreach_mouse_hit_point(PEData *data, For
Hit
PointFunc func, int selected)
static void foreach_mouse_hit_point(PEData *data, For
PointFunc func, int selected)
{
{
ParticleEditSettings *pset = PE_settings(data->scene);
ParticleEditSettings *pset = PE_settings(data->scene);
PTCacheEdit *edit = data->edit;
PTCacheEdit *edit = data->edit;
POINT_P; KEY_K;
POINT_P; KEY_K;
/* all is selected in path mode */
/* all is selected in path mode */
if (pset->selectmode == SCE_SELECT_PATH)
if (pset->selectmode == SCE_SELECT_PATH)
selected = 0;
selected = 0;
LOOP_VISIBLE_POINTS {
LOOP_VISIBLE_POINTS {
if (pset->selectmode == SCE_SELECT_END) {
if (pset->selectmode == SCE_SELECT_END) {
if (point->totkey) {
if (point->totkey) {
/* only do end keys */
/* only do end keys */
key = point->keys + point->totkey - 1;
key = point->keys + point->totkey - 1;
Copia
Copiato
Copia
Copiato
if (selected == 0 || key->flag & PEK_SELECT)
{
if (selected == 0 || key->flag & PEK_SELECT)
float mouse_distance;
if (key_inside_circle(data, data->rad, KEY_WCO, &
data->
dist
))
if (key_inside_circle(data, data->rad, KEY_WCO, &
mouse_
dist
ance)) {
func(data, p
);
func(data, p
, mouse_distance);
}
}
}
}
}
}
else {
else {
/* do all keys */
/* do all keys */
LOOP_VISIBLE_KEYS {
LOOP_VISIBLE_KEYS {
if (selected == 0 || key->flag & PEK_SELECT) {
if (selected == 0 || key->flag & PEK_SELECT) {
Copia
Copiato
Copia
Copiato
float mouse_distance;
if (key_inside_circle(data, data->rad, KEY_WCO, &
data->
dist
)) {
if (key_inside_circle(data, data->rad, KEY_WCO, &
mouse_
dist
ance
)) {
func(data, p
);
func(data, p
, mouse_distance
);
break;
break;
}
}
}
}
}
}
}
}
}
}
}
}
Copia
Copiato
Copia
Copiato
typedef struct KeyIterData {
static void foreach_mouse_hit_key
(PEData *
data
, ForKeyMatFunc func,
int
selected
)
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 ParallelRangeTLS *__restrict UNUSED(tls)
)
{
{
Copia
Copiato
Copia
Copiato
KeyIterData *iter_data = (KeyIterData *)iter_data_v;
PEData *data = iter_data->data;
PTCacheEdit *edit = data->edit;
PTCacheEdit *edit = data->edit;
Copia
Copiato
Copia
Copiato
PTCacheEditPoint *point = &edit->points[iter];
if (point->flag & PEP_HIDE) {
return;
}
ParticleSystem *psys = edit->psys;
ParticleSystem *psys = edit->psys;
Copia
Copiato
Copia
Copiato
ParticleSystemModifierData *psmd
_eval
=
iter_data->edit->psmd_eval
;
ParticleSystemModifierData *psmd
=
NULL
;
ParticleEditSettings *pset = PE_settings(data->scene);
ParticleEditSettings *pset = PE_settings(data->scene);
Copia
Copiato
Copia
Copiato
const int selected = iter_data->selected
;
POINT_P; KEY_K
;
float mat[4][4], imat[4][4];
float mat[4][4], imat[4][4];
Copia
Copiato
Copia
Copiato
unit_m4(mat);
unit_m4(mat);
unit_m4(imat);
unit_m4(imat);
Copia
Copiato
Copia
Copiato
if (pset->selectmode == SCE_SELECT_END) {
if (point->totkey) {
/* only do end keys */
PTCacheEditKey *key = point->keys + point->totkey - 1;
Copia
Copiato
Copia
Copiato
if (selected == 0 || key->flag & PEK_SELECT) {
if (edit->psys)
float mouse_distance;
psmd = psys_get_modifier(data->ob, edit->psys);
if (key_inside_circle(data, data->rad, KEY_WCO, &
mouse_
dist
ance
)) {
if (edit->psys && !(edit->psys->flag & PSYS_GLOBAL_HAIR)) {
/* all is selected in path mode */
psys_mat_hair_to_global(data->ob, psmd
_eval->mesh
_final, psys->part->from, psys->particles +
iter
, mat);
if (pset->selectmode == SCE_SELECT_PATH)
invert_m4_m4(imat, mat);
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) {
if (key_inside_circle(data, data->rad, KEY_WCO, &
data->
dist
)) {
if (edit->psys && !(edit->psys->flag & PSYS_GLOBAL_HAIR)) {
psys_mat_hair_to_global(data->ob, psmd
->dm
_final, psys->part->from, psys->particles +
p
, mat);
invert_m4_m4(imat, mat);
}
func(data, mat, imat, p, point->totkey - 1, key);
}
}
Copia
Copiato
Copia
Copiato
iter_data->func(data, mat, imat, iter, point->totkey - 1, key, mouse_distance);
}
}
}
}
}
}
Copia
Copiato
Copia
Copiato
}
else {
else {
/* do all keys */
/* do all keys */
LOOP_VISIBLE_KEYS {
PTCacheEditKey *key;
if (selected == 0 || key->flag & PEK_SELECT) {
int k;
if (key_inside_circle(data, data->rad, KEY_WCO, &
data->
dist
)) {
LOOP_VISIBLE_KEYS {
if (edit->psys && !(edit->psys->flag & PSYS_GLOBAL_HAIR)) {
if (selected == 0 || key->flag & PEK_SELECT) {
psys_mat_hair_to_global(data->ob, psmd
->dm
_final, psys->part->from, psys->particles +
p
, mat);
float mouse_distance;
invert_m4_m4(imat, mat);
if (key_inside_circle(data, data->rad, KEY_WCO, &
mouse_
dist
ance
)) {
}
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);
func(data, mat, imat, p, k, key);
invert_m4_m4(imat, mat);
}
}
Copia
Copiato
Copia
Copiato
iter_data->func(data, mat, imat, iter, k, key, mouse_distance);
}
}
}
}
}
}
}
}
}
}
Copia
Copiato
Copia
Copiato
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;
ParallelRangeSettings settings;
BLI_parallel_range_settings_defaults(&settings);
settings.scheduling_mode = TASK_SCHEDULING_DYNAMIC;
BLI_task_parallel_range(0, edit->totpoint, &iter_data, foreach_mouse_hit_key_iter, &settings);
}
static void foreach_selected_point(PEData *data, ForPointFunc func)
static void foreach_selected_point(PEData *data, ForPointFunc func)
{
{
PTCacheEdit *edit = data->edit;
PTCacheEdit *edit = data->edit;
POINT_P;
POINT_P;
LOOP_SELECTED_POINTS {
LOOP_SELECTED_POINTS {
func(data, p);
func(data, p);
}
}
}
}
static void foreach_selected_key(PEData *data, ForKeyFunc func)
static void foreach_selected_key(PEData *data, ForKeyFunc func)
{
{
PTCacheEdit *edit = data->edit;
PTCacheEdit *edit = data->edit;
POINT_P; KEY_K;
POINT_P; KEY_K;
LOOP_VISIBLE_POINTS {
LOOP_VISIBLE_POINTS {
LOOP_SELECTED_KEYS {
LOOP_SELECTED_KEYS {
Copia
Copiato
Copia
Copiato
func(data, p, k
, true
);
func(data, p, k
);
}
}
}
}
}
}
static void foreach_point(PEData *data, ForPointFunc func)
static void foreach_point(PEData *data, ForPointFunc func)
{
{
PTCacheEdit *edit = data->edit;
PTCacheEdit *edit = data->edit;
POINT_P;
POINT_P;
LOOP_POINTS {
LOOP_POINTS {
func(data, p);
func(data, p);
}
}
}
}
static int count_selected_keys(Scene *scene, PTCacheEdit *edit)
static int count_selected_keys(Scene *scene, PTCacheEdit *edit)
{
{
ParticleEditSettings *pset = PE_settings(scene);
ParticleEditSettings *pset = PE_settings(scene);
POINT_P; KEY_K;
POINT_P; KEY_K;
int sel = 0;
int sel = 0;
LOOP_VISIBLE_POINTS {
LOOP_VISIBLE_POINTS {
if (pset->selectmode == SCE_SELECT_POINT) {
if (pset->selectmode == SCE_SELECT_POINT) {
LOOP_SELECTED_KEYS {
LOOP_SELECTED_KEYS {
sel++;
sel++;
}
}
}
}
else if (pset->selectmode == SCE_SELECT_END) {
else if (pset->selectmode == SCE_SELECT_END) {
if (point->totkey) {
if (point->totkey) {
key = point->keys + point->totkey - 1;
key = point->keys + point->totkey - 1;
if (key->flag & PEK_SELECT)
if (key->flag & PEK_SELECT)
sel++;
sel++;
}
}
}
}
}
}
return sel;
return sel;
}
}
/************************************************/
/************************************************/
/* Particle Edit Mirroring */
/* Particle Edit Mirroring */
/************************************************/
/************************************************/
static void PE_update_mirror_cache(Object *ob, ParticleSystem *psys)
static void PE_update_mirror_cache(Object *ob, ParticleSystem *psys)
{
{
PTCacheEdit *edit;
PTCacheEdit *edit;
Copia
Copiato
Copia
Copiato
ParticleSystemModifierData *psmd
_eval
;
ParticleSystemModifierData *psmd
;
KDTree
_3d
*tree;
KDTree
*tree;
KDTreeNearest
_3d
nearest;
KDTreeNearest
nearest;
HairKey *key;
HairKey *key;
PARTICLE_P;
PARTICLE_P;
float mat[4][4], co[3];
float mat[4][4], co[3];
int index, totpart;
int index, totpart;
edit = psys->edit;
edit = psys->edit;
Copia
Copiato
Copia
Copiato
psmd
_eval
=
edit->psmd_eval
;
psmd
=
psys_get_modifier(ob, psys)
;
totpart = psys->totpart;
totpart = psys->totpart;
Copia
Copiato
Copia
Copiato
if (!psmd
_eval->mesh
_final)
if (!psmd
->dm
_final)
return;
return;
Copia
Copiato
Copia
Copiato
tree = BLI_kdtree_
3d_
new(totpart);
tree = BLI_kdtree_
new(totpart);
/* insert particles into kd tree */
/* insert particles into kd tree */
LOOP_PARTICLES {
LOOP_PARTICLES {
key = pa->hair;
key = pa->hair;
Copia
Copiato
Copia
Copiato
psys_mat_hair_to_orco(ob, psmd
_eval->mesh
_final, psys->part->from, pa, mat);
psys_mat_hair_to_orco(ob, psmd
->dm
_final, psys->part->from, pa, mat);
copy_v3_v3(co, key->co);
copy_v3_v3(co, key->co);
mul_m4_v3(mat, co);
mul_m4_v3(mat, co);
Copia
Copiato
Copia
Copiato
BLI_kdtree_
3d_
insert(tree, p, co);
BLI_kdtree_
insert(tree, p, co);
}
}
Copia
Copiato
Copia
Copiato
BLI_kdtree_
3d_
balance(tree);
BLI_kdtree_
balance(tree);
/* lookup particles and set in mirror cache */
/* lookup particles and set in mirror cache */
if (!edit->mirror_cache)
if (!edit->mirror_cache)
edit->mirror_cache = MEM_callocN(sizeof(int) * totpart, "PE mirror cache");
edit->mirror_cache = MEM_callocN(sizeof(int) * totpart, "PE mirror cache");
LOOP_PARTICLES {
LOOP_PARTICLES {
key = pa->hair;
key = pa->hair;
Copia
Copiato
Copia
Copiato
psys_mat_hair_to_orco(ob, psmd
_eval->mesh
_final, psys->part->from, pa, mat);
psys_mat_hair_to_orco(ob, psmd
->dm
_final, psys->part->from, pa, mat);
copy_v3_v3(co, key->co);
copy_v3_v3(co, key->co);
mul_m4_v3(mat, co);
mul_m4_v3(mat, co);
co[0] = -co[0];
co[0] = -co[0];
Copia
Copiato
Copia
Copiato
index = BLI_kdtree_
3d_
find_nearest(tree, co, &nearest);
index = BLI_kdtree_
find_nearest(tree, co, &nearest);
/* this needs a custom threshold still, duplicated for editmode mirror */
/* this needs a custom threshold still, duplicated for editmode mirror */
if (index != -1 && index != p && (nearest.dist <= 0.0002f))
if (index != -1 && index != p && (nearest.dist <= 0.0002f))
edit->mirror_cache[p] = index;
edit->mirror_cache[p] = index;
else
else
edit->mirror_cache[p] = -1;
edit->mirror_cache[p] = -1;
}
}
/* make sure mirrors are in two directions */
/* make sure mirrors are in two directions */
LOOP_PARTICLES {
LOOP_PARTICLES {
if (edit->mirror_cache[p]) {
if (edit->mirror_cache[p]) {
index = edit->mirror_cache[p];
index = edit->mirror_cache[p];
if (edit->mirror_cache[index] != p)
if (edit->mirror_cache[index] != p)
edit->mirror_cache[p] = -1;
edit->mirror_cache[p] = -1;
}
}
}
}
Copia
Copiato
Copia
Copiato
BLI_kdtree_
3d_
free(tree);
BLI_kdtree_
free(tree);
}
}
Copia
Copiato
Copia
Copiato
static void PE_mirror_particle(Object *ob,
Mesh *mesh
, ParticleSystem *psys, ParticleData *pa, ParticleData *mpa)
static void PE_mirror_particle(Object *ob,
DerivedMesh *dm
, ParticleSystem *psys, ParticleData *pa, ParticleData *mpa)
{
{
HairKey *hkey, *mhkey;
HairKey *hkey, *mhkey;
PTCacheEditPoint *point, *mpoint;
PTCacheEditPoint *point, *mpoint;
PTCacheEditKey *key, *mkey;
PTCacheEditKey *key, *mkey;
PTCacheEdit *edit;
PTCacheEdit *edit;
float mat[4][4], mmat[4][4], immat[4][4];
float mat[4][4], mmat[4][4], immat[4][4];
int i, mi, k;
int i, mi, k;
edit = psys->edit;
edit = psys->edit;
i = pa - psys->particles;
i = pa - psys->particles;
/* find mirrored particle if needed */
/* find mirrored particle if needed */
if (!mpa) {
if (!mpa) {
if (!edit->mirror_cache)
if (!edit->mirror_cache)
PE_update_mirror_cache(ob, psys);
PE_update_mirror_cache(ob, psys);
if (!edit->mirror_cache)
if (!edit->mirror_cache)
return; /* something went wrong! */
return; /* something went wrong! */
mi = edit->mirror_cache[i];
mi = edit->mirror_cache[i];
if (mi == -1)
if (mi == -1)
return;
return;
mpa = psys->particles + mi;
mpa = psys->particles + mi;
}
}
else
else
mi = mpa - psys->particles;
mi = mpa - psys->particles;
point = edit->points + i;
point = edit->points + i;
mpoint = edit->points + mi;
mpoint = edit->points + mi;
/* make sure they have the same amount of keys */
/* make sure they have the same amount of keys */
if (pa->totkey != mpa->totkey) {
if (pa->totkey != mpa->totkey) {
if (mpa->hair) MEM_freeN(mpa->hair);
if (mpa->hair) MEM_freeN(mpa->hair);
if (mpoint->keys) MEM_freeN(mpoint->keys);
if (mpoint->keys) MEM_freeN(mpoint->keys);
mpa->hair = MEM_dupallocN(pa->hair);
mpa->hair = MEM_dupallocN(pa->hair);
mpa->totkey = pa->totkey;
mpa->totkey = pa->totkey;
mpoint->keys = MEM_dupallocN(point->keys);
mpoint->keys = MEM_dupallocN(point->keys);
mpoint->totkey = point->totkey;
mpoint->totkey = point->totkey;
mhkey = mpa->hair;
mhkey = mpa->hair;
mkey = mpoint->keys;
mkey = mpoint->keys;
for (k = 0; k < mpa->totkey; k++, mkey++, mhkey++) {
for (k = 0; k < mpa->totkey; k++, mkey++, mhkey++) {
mkey->co = mhkey->co;
mkey->co = mhkey->co;
mkey->time = &mhkey->time;
mkey->time = &mhkey->time;
mkey->flag &= ~PEK_SELECT;
mkey->flag &= ~PEK_SELECT;
}
}
}
}
/* mirror positions and tags */
/* mirror positions and tags */
Copia
Copiato
Copia
Copiato
psys_mat_hair_to_orco(ob,
mesh
, psys->part->from, pa, mat);
psys_mat_hair_to_orco(ob,
dm
, psys->part->from, pa, mat);
psys_mat_hair_to_orco(ob,
mesh
, psys->part->from, mpa, mmat);
psys_mat_hair_to_orco(ob,
dm
, psys->part->from, mpa, mmat);
invert_m4_m4(immat, mmat);
invert_m4_m4(immat, mmat);
hkey = pa->hair;
hkey = pa->hair;
mhkey = mpa->hair;
mhkey = mpa->hair;
key = point->keys;
key = point->keys;
mkey = mpoint->keys;
mkey = mpoint->keys;
for (k = 0; k < pa->totkey; k++, hkey++, mhkey++, key++, mkey++) {
for (k = 0; k < pa->totkey; k++, hkey++, mhkey++, key++, mkey++) {
copy_v3_v3(mhkey->co, hkey->co);
copy_v3_v3(mhkey->co, hkey->co);
mul_m4_v3(mat, mhkey->co);
mul_m4_v3(mat, mhkey->co);
mhkey->co[0] = -mhkey->co[0];
mhkey->co[0] = -mhkey->co[0];
mul_m4_v3(immat, mhkey->co);
mul_m4_v3(immat, mhkey->co);
if (key->flag & PEK_TAG)
if (key->flag & PEK_TAG)
mkey->flag |= PEK_TAG;
mkey->flag |= PEK_TAG;
mkey->length = key->length;
mkey->length = key->length;
}
}
if (point->flag & PEP_TAG)
if (point->flag & PEP_TAG)
mpoint->flag |= PEP_TAG;
mpoint->flag |= PEP_TAG;
if (point->flag & PEP_EDIT_RECALC)
if (point->flag & PEP_EDIT_RECALC)
mpoint->flag |= PEP_EDIT_RECALC;
mpoint->flag |= PEP_EDIT_RECALC;
}
}
static void PE_apply_mirror(Object *ob, ParticleSystem *psys)
static void PE_apply_mirror(Object *ob, ParticleSystem *psys)
{
{
PTCacheEdit *edit;
PTCacheEdit *edit;
Copia
Copiato
Copia
Copiato
ParticleSystemModifierData *psmd
_eval
;
ParticleSystemModifierData *psmd
;
POINT_P;
POINT_P;
if (!psys)
if (!psys)
return;
return;
edit = psys->edit;
edit = psys->edit;
Copia
Copiato
Copia
Copiato
psmd
_eval
=
edit->psmd_eval
;
psmd
=
psys_get_modifier(ob, psys)
;
Copia
Copiato
Copia
Copiato
if (!psmd
_eval->mesh
_final)
if (!psmd
->dm
_final)
return;
return;
if (!edit->mirror_cache)
if (!edit->mirror_cache)
PE_update_mirror_cache(ob, psys);
PE_update_mirror_cache(ob, psys);
if (!edit->mirror_cache)
if (!edit->mirror_cache)
return; /* something went wrong */
return; /* something went wrong */
/* we delay settings the PARS_EDIT_RECALC for mirrored particles
/* we delay settings the PARS_EDIT_RECALC for mirrored particles
* to avoid doing mirror twice */
* to avoid doing mirror twice */
LOOP_POINTS {
LOOP_POINTS {
if (point->flag & PEP_EDIT_RECALC) {
if (point->flag & PEP_EDIT_RECALC) {
Copia
Copiato
Copia
Copiato
PE_mirror_particle(ob, psmd
_eval->mesh
_final, psys, psys->particles + p, NULL);
PE_mirror_particle(ob, psmd
->dm
_final, psys, psys->particles + p, NULL);
Copia
Copiato
Copia
Copiato
i
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;
Text moved from lines 494-496
}
}
/************************************************/
/* Edit Calculation */
/************************************************/
/* 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;
ParticleSystemModifierData *psmd;
POINT_P; KEY_K;
int index;
float *vec, *nor, dvec[3], dot, dist_1st = 0.0f;
float hairimat[4][4], hairmat[4][4];
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;
psmd = psys_get_modifier(ob, psys);
if (!psmd->dm_final)
return;
LOOP_EDITED_POINTS {
psys_mat_hair_to_object(ob, psmd->dm_final, psys->part->from, psys->particles + p, 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 * pset->emitterdist;
}
else {
index = BLI_kdtree_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);
}
}
}
/* force set distances between neighboring keys */
static void PE_apply_lengths(Scene *scene, PTCacheEdit *edit)
{
ParticleEditSettings *pset = PE_settings(scene);
POINT_P; KEY_K;
float dv1[3];
if (edit == 0 || (pset->flag & PE_KEEP_LENGTHS) == 0)
return;
if (edit->psys && edit->psys->flag & PSYS_GLOBAL_HAIR)
return;
LOOP_EDITED_POINTS {
LOOP_KEYS {
if (k) {
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);
}
}
}
}
/* 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);
POINT_P;
PTCacheEditKey *key;
int j, k;
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};
if (edit == 0 || (pset->flag & PE_KEEP_LENGTHS) == 0)
return;
if (edit->psys && edit->psys->flag & PSYS_GLOBAL_HAIR)
return;
LOOP_EDITED_POINTS {
for (j = 1; j < point->totkey; j++) {
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);
}
Diff salvati
Testo originale
Apri file
/* * 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 <stdlib.h> #include <math.h> #include <string.h> #include <assert.h> #include "MEM_guardedalloc.h" #include "DNA_scene_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_view3d_types.h" #include "DNA_screen_types.h" #include "DNA_space_types.h" #include "BLI_math.h" #include "BLI_lasso_2d.h" #include "BLI_listbase.h" #include "BLI_kdtree.h" #include "BLI_rand.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_object.h" #include "ED_physics.h" #include "ED_mesh.h" #include "ED_particle.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_types.h" #include "WM_message.h" #include "WM_toolsystem.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) { Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); if (!scene || !ob || !(ob->mode & OB_MODE_PARTICLE_EDIT)) { return 0; } return (PE_get_current(scene, ob) != NULL); } bool PE_hair_poll(bContext *C) { Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); PTCacheEdit *edit; if (!scene || !ob || !(ob->mode & OB_MODE_PARTICLE_EDIT)) { return 0; } edit = PE_get_current(scene, ob); return (edit && edit->psys); } bool PE_poll_view3d(bContext *C) { ScrArea *sa = CTX_wm_area(C); ARegion *ar = CTX_wm_region(C); return (PE_poll(C) && (sa && sa->spacetype == SPACE_VIEW3D) && (ar && ar->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) { // 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; return brush->size * U.pixelsize; } 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; } else { return psys->edit; } } else 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, therefor 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; } } if (edit) { edit->pid = *pid; if (edit->flags & PT_CACHE_EDIT_UPDATE_PARTICLE_FROM_EVAL) { if (edit->psys != 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(Scene *scene, Object *ob) { return pe_get_current(NULL, 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)->editflag & ME_EDIT_MIRROR_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_depsgraph(C); data->edit = PE_get_current(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); if (V3D_IS_ZBUF(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.ar); } } } static bool PE_create_shape_tree(PEData *data, Object *shapeob) { Mesh *mesh = BKE_object_get_evaluated_mesh(data->depsgraph, shapeob); 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(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 (!V3D_IS_ZBUF(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.ar, 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.ar, co, win); if (win[2] - 0.00001f > depth) return 0; else return 1; } static bool key_inside_circle(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.ar, 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.ar, 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); else 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, float mat[4][4], 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 ParallelRangeTLS *__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; ParallelRangeSettings settings; BLI_parallel_range_settings_defaults(&settings); settings.scheduling_mode = TASK_SCHEDULING_DYNAMIC; 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->mesh_final) 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 ParallelRangeTLS *__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->mesh_final) { 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; ParallelRangeSettings settings; BLI_parallel_range_settings_defaults(&settings); settings.scheduling_mode = TASK_SCHEDULING_DYNAMIC; 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 ParallelRangeTLS *__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; ParallelRangeSettings settings; BLI_parallel_range_settings_defaults(&settings); settings.scheduling_mode = TASK_SCHEDULING_DYNAMIC; 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 ParallelRangeTLS *__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; ParallelRangeSettings settings; BLI_parallel_range_settings_defaults(&settings); settings.scheduling_mode = TASK_SCHEDULING_DYNAMIC; BLI_task_parallel_range(0, edit->totpoint, &iter_data, iterate_lengths_iter, &settings); } /* set current distances to be kept between neighbouting 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(totface * 6 * sizeof(float), "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(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(Depsgraph *UNUSED(depsgraph), 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->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(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(depsgraph, ob, edit); if (pset->flag & PE_AUTO_VELOCITY) update_velocities(edit); 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; } } 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; key->flag |= PEK_SELECT; point->flag |= PEP_EDIT_RECALC; } 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; key->flag &= ~PEK_SELECT; point->flag |= PEP_EDIT_RECALC; } 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; } /************************ de select all operator ************************/ static void select_action_apply(PTCacheEditPoint *point, PTCacheEditKey *key, int action) { switch (action) { case SEL_SELECT: if ((key->flag & PEK_SELECT) == 0) { key->flag |= PEK_SELECT; point->flag |= PEP_EDIT_RECALC; } break; case SEL_DESELECT: if (key->flag & PEK_SELECT) { key->flag &= ~PEK_SELECT; point->flag |= PEP_EDIT_RECALC; } break; case SEL_INVERT: if ((key->flag & PEK_SELECT) == 0) { key->flag |= PEK_SELECT; point->flag |= PEP_EDIT_RECALC; } else { key->flag &= ~PEK_SELECT; point->flag |= PEP_EDIT_RECALC; } break; } } static int pe_select_all_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); Depsgraph *depsgraph = CTX_data_depsgraph(C); Object *ob = CTX_data_active_object(C); PTCacheEdit *edit = PE_get_current(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; } } LOOP_VISIBLE_POINTS { LOOP_VISIBLE_KEYS { select_action_apply(point, key, action); } } 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 ************************/ int 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); PTCacheEdit *edit = PE_get_current(scene, ob); POINT_P; KEY_K; if (!PE_start_edit(edit)) return OPERATOR_CANCELLED; if (!extend && !deselect && !toggle) { LOOP_VISIBLE_POINTS { LOOP_SELECTED_KEYS { key->flag &= ~PEK_SELECT; point->flag |= PEP_EDIT_RECALC; } } } PE_set_view3d_data(C, &data); 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); } PE_update_selection(data.depsgraph, scene, ob, 1); WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, data.ob); return OPERATOR_FINISHED; } /************************ 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) 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); 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); } /************************ 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) 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); 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_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); } /*********************** 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.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 { 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; select_action_apply(point, key, flag); } } break; } BLI_rng_free(rng); 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 *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_invoke(bContext *C, wmOperator *op, const wmEvent *event) { RNA_int_set_array(op->ptr, "location", event->mval); return select_linked_exec(C, op); } void PARTICLE_OT_select_linked(wmOperatorType *ot) { /* identifiers */ ot->name = "Select Linked"; ot->idname = "PARTICLE_OT_select_linked"; ot->description = "Select nearest particle from mouse pointer"; /* api callbacks */ ot->exec = select_linked_exec; ot->invoke = select_linked_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 ************************/ void PE_deselect_all_visible(PTCacheEdit *edit) { POINT_P; KEY_K; LOOP_VISIBLE_POINTS { LOOP_SELECTED_KEYS { key->flag &= ~PEK_SELECT; point->flag |= PEP_EDIT_RECALC; } } } int PE_box_select(bContext *C, const rcti *rect, const int sel_op) { Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); PTCacheEdit *edit = PE_get_current(scene, ob); PEData data; if (!PE_start_edit(edit)) return OPERATOR_CANCELLED; if (SEL_OP_USE_PRE_DESELECT(sel_op)) { PE_deselect_all_visible(edit); } PE_set_view3d_data(C, &data); data.rect = rect; data.sel_op = sel_op; for_mouse_hit_keys(&data, select_key_op, PSEL_ALL_KEYS); PE_update_selection(data.depsgraph, scene, ob, 1); WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, ob); return OPERATOR_FINISHED; } /************************ 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)); Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); PTCacheEdit *edit = PE_get_current(scene, ob); PEData data; if (!PE_start_edit(edit)) { return false; } bool changed = false; if (SEL_OP_USE_PRE_DESELECT(sel_op)) { PE_deselect_all_visible(edit); changed = true; } const bool select = (sel_op != SEL_OP_SUB); PE_set_view3d_data(C, &data); data.mval = mval; data.rad = rad; data.select = select; for_mouse_hit_keys(&data, select_key, 0); changed |= data.is_changed; if (changed) { PE_update_selection(data.depsgraph, scene, ob, 1); WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, ob); } return changed; } /************************ lasso select operator ************************/ int PE_lasso_select(bContext *C, const int mcords[][2], const short moves, const int sel_op) { Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); ARegion *ar = CTX_wm_region(C); ParticleEditSettings *pset = PE_settings(scene); PTCacheEdit *edit = PE_get_current(scene, ob); ParticleSystem *psys = edit->psys; ParticleSystemModifierData *psmd_eval = edit->psmd_eval; 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; if (SEL_OP_USE_PRE_DESELECT(sel_op)) { PE_deselect_all_visible(edit); } /* only for depths */ PE_set_view3d_data(C, &data); 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_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(ar, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) == V3D_PROJ_RET_OK) && BLI_lasso_is_point_inside(mcords, moves, 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; } } } 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(ar, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) == V3D_PROJ_RET_OK) && BLI_lasso_is_point_inside(mcords, moves, 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; } } } } PE_update_selection(data.depsgraph, scene, ob, 1); WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, ob); return OPERATOR_FINISHED; } /*************************** 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_depsgraph(C); PTCacheEdit *edit = PE_get_current(scene, ob); POINT_P; KEY_K; if (RNA_enum_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_depsgraph(C); PTCacheEdit *edit = PE_get_current(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_TAG | PEK_SELECT); point->flag |= PEP_EDIT_RECALC; /* redraw selection only */ } } } 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_TAG; key->flag |= PEK_SELECT; point->flag |= PEP_EDIT_RECALC; /* redraw selection only */ } } } 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) { PTCacheEdit *edit = PE_get_current(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 = CTX_data_depsgraph(C); 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(Object *ob, ParticleSystem *psys) { PTCacheEdit *edit = psys->edit; ParticleData *pa; HairKey *hkey, *nhkey, *new_hkeys = 0; POINT_P; KEY_K; PTCacheEditKey *nkey, *new_keys; ParticleSystemModifierData *psmd_eval; short new_totkey; if (pe_x_mirror(ob)) { /* mirror key tags */ psmd_eval = psys_get_modifier(ob, psys); 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) { Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); PTCacheEdit *edit = PE_get_current(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 particles", 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) { Scene *scene = CTX_data_scene(C); ParticleEditSettings *pset = PE_settings(scene); Object *ob = CTX_data_active_object(C); PTCacheEdit *edit = PE_get_current(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(true); imm_draw_circle_wire_2d(pos, (float)x, (float)y, pe_brush_size_get(scene, brush), 40); GPU_blend(false); GPU_line_smooth(false); immUnbindProgram(); } } static void toggle_particle_cursor(bContext *C, int enable) { ParticleEditSettings *pset = PE_settings(CTX_data_scene(C)); if (pset->paintcursor && !enable) { WM_paint_cursor_end(CTX_wm_manager(C), pset->paintcursor); pset->paintcursor = NULL; } else if (enable) pset->paintcursor = WM_paint_cursor_activate( CTX_wm_manager(C), 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.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); 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( Scene *scene, Object *ob, int tagged) { Mesh *me = (Mesh *)(ob->data); ParticleSystemModifierData *psmd_eval; PTCacheEdit *edit = PE_get_current(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; } else 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)) { Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); PTCacheEdit *edit = PE_get_current(scene, ob); PE_mirror_x(scene, ob, 0); update_world_cos(CTX_data_depsgraph(C), ob, edit); 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_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 = PE_hair_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 *ar = data->vc.ar; 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(ar, 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(ar, 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 [#35406] */ 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 */ { #if 0 /* kindof works but looks worse then 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; int num_added; } 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 ParallelRangeTLS *__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 short 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] + tls_v->thread_id); } /* 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.ar, 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_finalize(void *__restrict userdata_v, void *__restrict userdata_chunk_v) { BrushAddCountIterData *iter_data = (BrushAddCountIterData *)userdata_v; BrushAddCountIterTLSData *tls = (BrushAddCountIterTLSData *)userdata_chunk_v; iter_data->num_added += tls->num_added; 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(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 interseciton * 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; iter_data.num_added = 0; copy_m4_m4(iter_data.imat, imat); BrushAddCountIterTLSData tls = {NULL}; ParallelRangeSettings settings; BLI_parallel_range_settings_defaults(&settings); settings.scheduling_mode = TASK_SCHEDULING_DYNAMIC; settings.userdata_chunk = &tls; settings.userdata_chunk_size = sizeof(BrushAddCountIterTLSData); settings.func_finalize = brush_add_count_iter_finalize; 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 * beginnign of the array. */ n = iter_data.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; initialize_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.f - (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) { 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(scene, ob); ARegion *ar = 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(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(ar->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(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 *ar = 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(ar, 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(scene, ob, 1); update_world_cos(depsgraph, 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, 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; BLI_bvhtree_ray_cast_all( shape_bvh->tree, key->co, 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, key)) { cut_time = -1.0f; cut = true; } else { for (k = 0; k < totkeys; k++, key++) { BVHTreeRayHit hit; float dir[3]; float len; sub_v3_v3v3(dir, (key + 1)->co, key->co); len = normalize_v3(dir); memset(&hit, 0, sizeof(hit)); hit.index = -1; hit.dist = len; BLI_bvhtree_ray_cast(data->shape_bvh.tree, key->co, dir, 0.0f, &hit, data->shape_bvh.raycast_callback, &data->shape_bvh); if (hit.index >= 0) { if (hit.dist < len) { cut_time = (hit.dist / len + (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_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(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(depsgraph, 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(Scene *scene, ViewLayer *view_layer, float min[3], float max[3]) { Object *ob = OBACT(view_layer); PTCacheEdit *edit = PE_get_current(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 *)modifiers_findByName(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 = 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(depsgraph, 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 { if (BKE_ptcache_mem_pointers_seek(p, pm) == 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 = pm->cur[BPHYS_DATA_LOCATION]; key->vel = pm->cur[BPHYS_DATA_VELOCITY]; key->rot = pm->cur[BPHYS_DATA_ROTATION]; key->ftime = (float)pm->frame; key->time = &key->ftime; BKE_ptcache_mem_pointers_incr(pm); point->totkey++; } } psys = NULL; } /* Causes assert on startup. */ #if 0 UI_GetThemeColor3ubv(TH_EDGE_SELECT, edit->sel_col); UI_GetThemeColor3ubv(TH_WIRE, edit->nosel_col); #else memset(edit->sel_col, 0xff, sizeof(edit->sel_col)); memset(edit->nosel_col, 0x00, sizeof(edit->nosel_col)); #endif 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; if (CTX_data_edit_object(C)) return 0; return (ob->particlesystem.first || modifiers_findByType(ob, eModifierType_Cloth) || modifiers_findByType(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; } } } static int particle_edit_toggle_exec(bContext *C, wmOperator *op) { struct wmMsgBus *mbus = CTX_wm_message_bus(C); Depsgraph *depsgraph = CTX_data_depsgraph(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) { PTCacheEdit *edit; ob->mode |= mode_flag; 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 *)modifiers_findByName( object_eval, edit->psmd->modifier.name); recalc_emitter_field(depsgraph, ob, edit->psys); } toggle_particle_cursor(C, 1); WM_event_add_notifier(C, NC_SCENE | ND_MODE | NS_MODE_PARTICLE, NULL); } else { ob->mode &= ~mode_flag; toggle_particle_cursor(C, 0); free_all_psys_edit(ob); WM_event_add_notifier(C, NC_SCENE | ND_MODE | NS_MODE_OBJECT, NULL); } DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); 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); 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; } static int clear_edited_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { return WM_operator_confirm_message(C, op, "Lose changes done in particle mode? (no undo)"); } 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; ot->invoke = clear_edited_invoke; /* 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(C); PTCacheEdit *edit = PE_get_current(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; }
Testo modificato
Apri file
/* * 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 blender/editors/physics/particle_edit.c * \ingroup edphys */ #include <stdlib.h> #include <math.h> #include <string.h> #include <assert.h> #include "MEM_guardedalloc.h" #include "DNA_scene_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_view3d_types.h" #include "DNA_screen_types.h" #include "DNA_space_types.h" #include "BLI_math.h" #include "BLI_lasso_2d.h" #include "BLI_listbase.h" #include "BLI_kdtree.h" #include "BLI_rand.h" #include "BLI_utildefines.h" #include "BKE_context.h" #include "BKE_depsgraph.h" #include "BKE_DerivedMesh.h" #include "BKE_global.h" #include "BKE_object.h" #include "BKE_main.h" #include "BKE_mesh.h" #include "BKE_modifier.h" #include "BKE_particle.h" #include "BKE_report.h" #include "BKE_bvhutils.h" #include "BKE_pointcache.h" #include "BIF_gl.h" #include "BIF_glutil.h" #include "ED_object.h" #include "ED_physics.h" #include "ED_mesh.h" #include "ED_particle.h" #include "ED_view3d.h" #include "UI_resources.h" #include "WM_api.h" #include "WM_types.h" #include "RNA_access.h" #include "RNA_define.h" #include "physics_intern.h" #include "particle_edit_utildefines.h" /**************************** utilities *******************************/ bool PE_poll(bContext *C) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); if (!scene || !ob || !(ob->mode & OB_MODE_PARTICLE_EDIT)) return 0; return (PE_get_current(bmain, scene, ob) != NULL); } bool PE_hair_poll(bContext *C) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); PTCacheEdit *edit; if (!scene || !ob || !(ob->mode & OB_MODE_PARTICLE_EDIT)) return 0; edit = PE_get_current(bmain, scene, ob); return (edit && edit->psys); } bool PE_poll_view3d(bContext *C) { ScrArea *sa = CTX_wm_area(C); ARegion *ar = CTX_wm_region(C); return (PE_poll(C) && (sa && sa->spacetype == SPACE_VIEW3D) && (ar && ar->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_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) { // 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; return brush->size * U.pixelsize; } /* always gets at least the first particlesystem even if PSYS_CURRENT flag is not set * * note: this function runs on poll, therefor it can runs many times a second * keep it fast! */ static PTCacheEdit *pe_get_current(Main *bmain, 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(bmain, &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(bmain, scene, ob, pid->cache, NULL); edit = pid->cache->edit; } else { if (create && !psys->edit && psys->flag & PSYS_HAIR_DONE) PE_create_particle_edit(bmain, scene, ob, NULL, psys); edit = psys->edit; } } else { if (create && pid->cache->flag & PTCACHE_BAKED && !pid->cache->edit) PE_create_particle_edit(bmain, 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(bmain, 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(bmain, scene, ob, pid->cache, NULL); } edit = pid->cache->edit; break; } } if (edit) edit->pid = *pid; BLI_freelistN(&pidlist); return edit; } PTCacheEdit *PE_get_current(Main *bmain, Scene *scene, Object *ob) { return pe_get_current(bmain, scene, ob, 0); } PTCacheEdit *PE_create_current(Main *bmain, Scene *scene, Object *ob) { return pe_get_current(bmain, scene, ob, 1); } void PE_current_changed(Main *bmain, Scene *scene, Object *ob) { if (ob->mode == OB_MODE_PARTICLE_EDIT) PE_create_current(bmain, 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)->editflag & ME_EDIT_MIRROR_X); return 0; } /****************** common struct passed to callbacks ******************/ typedef struct PEData { ViewContext vc; bglMats mats; Main *bmain; Scene *scene; Object *ob; DerivedMesh *dm; PTCacheEdit *edit; BVHTreeFromMesh shape_bvh; const int *mval; const rcti *rect; float rad; float dist; float dval; int select; 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; } PEData; static void PE_set_data(bContext *C, PEData *data) { memset(data, 0, sizeof(*data)); data->bmain = CTX_data_main(C); data->scene = CTX_data_scene(C); data->ob = CTX_data_active_object(C); data->edit = PE_get_current(data->bmain, 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); /* note, the object argument means the modelview matrix does not account for the objects matrix, use viewmat rather than (obmat * viewmat) */ view3d_get_transformation(data->vc.ar, data->vc.rv3d, NULL, &data->mats); if (V3D_IS_ZBUF(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_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.ar); } } } static bool PE_create_shape_tree(PEData *data, Object *shapeob) { DerivedMesh *dm = shapeob->derivedFinal; memset(&data->shape_bvh, 0, sizeof(data->shape_bvh)); if (!dm) { return false; } return (bvhtree_from_mesh_get(&data->shape_bvh, dm, BVHTREE_FROM_LOOPTRI, 4) != NULL); } static void PE_free_shape_tree(PEData *data) { free_bvhtree_from_mesh(&data->shape_bvh); } /*************************** selection utilities *******************************/ static bool key_test_depth(PEData *data, const float co[3], const int screen_co[2]) { View3D *v3d = data->vc.v3d; ViewDepths *vd = data->vc.rv3d->depths; double ux, uy, uz; float depth; /* nothing to do */ if (!V3D_IS_ZBUF(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.ar, 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 gluProject(co[0], co[1], co[2], data->mats.modelview, data->mats.projection, (GLint *)data->mats.viewport, &ux, &uy, &uz); /* 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; if ((float)uz - 0.00001f > depth) return 0; else return 1; } static bool key_inside_circle(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.ar, 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.ar, 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); else 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 (*ForKeyFunc)(PEData *data, int point_index, int key_index); typedef void (*ForKeyMatFunc)(PEData *data, float mat[4][4], float imat[4][4], int point_index, int key_index, PTCacheEditKey *key); static void for_mouse_hit_keys(PEData *data, ForKeyFunc func, bool nearest) { 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 (nearest) { if (key_inside_circle(data, dist, KEY_WCO, &dist)) { nearest_point = p; nearest_key = point->totkey - 1; } } else if (key_inside_test(data, KEY_WCO)) func(data, p, point->totkey - 1); } } else { /* do all keys */ LOOP_VISIBLE_KEYS { if (nearest) { if (key_inside_circle(data, dist, KEY_WCO, &dist)) { nearest_point = p; nearest_key = k; } } else if (key_inside_test(data, KEY_WCO)) func(data, p, k); } } } /* do nearest only */ if (nearest && nearest_point > -1) func(data, nearest_point, nearest_key); } static void foreach_mouse_hit_point(PEData *data, ForPointFunc 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) if (key_inside_circle(data, data->rad, KEY_WCO, &data->dist)) func(data, p); } } else { /* do all keys */ LOOP_VISIBLE_KEYS { if (selected == 0 || key->flag & PEK_SELECT) { if (key_inside_circle(data, data->rad, KEY_WCO, &data->dist)) { func(data, p); break; } } } } } } static void foreach_mouse_hit_key(PEData *data, ForKeyMatFunc func, int selected) { PTCacheEdit *edit = data->edit; ParticleSystem *psys = edit->psys; ParticleSystemModifierData *psmd = NULL; ParticleEditSettings *pset = PE_settings(data->scene); POINT_P; KEY_K; float mat[4][4], imat[4][4]; unit_m4(mat); unit_m4(imat); if (edit->psys) psmd = psys_get_modifier(data->ob, edit->psys); /* 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) { if (key_inside_circle(data, data->rad, KEY_WCO, &data->dist)) { if (edit->psys && !(edit->psys->flag & PSYS_GLOBAL_HAIR)) { psys_mat_hair_to_global(data->ob, psmd->dm_final, psys->part->from, psys->particles + p, mat); invert_m4_m4(imat, mat); } func(data, mat, imat, p, point->totkey - 1, key); } } } } else { /* do all keys */ LOOP_VISIBLE_KEYS { if (selected == 0 || key->flag & PEK_SELECT) { if (key_inside_circle(data, data->rad, KEY_WCO, &data->dist)) { if (edit->psys && !(edit->psys->flag & PSYS_GLOBAL_HAIR)) { psys_mat_hair_to_global(data->ob, psmd->dm_final, psys->part->from, psys->particles + p, mat); invert_m4_m4(imat, mat); } func(data, mat, imat, p, k, key); } } } } } } 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); } } } 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; KDTree *tree; KDTreeNearest nearest; HairKey *key; PARTICLE_P; float mat[4][4], co[3]; int index, totpart; edit = psys->edit; psmd = psys_get_modifier(ob, psys); totpart = psys->totpart; if (!psmd->dm_final) return; tree = BLI_kdtree_new(totpart); /* insert particles into kd tree */ LOOP_PARTICLES { key = pa->hair; psys_mat_hair_to_orco(ob, psmd->dm_final, psys->part->from, pa, mat); copy_v3_v3(co, key->co); mul_m4_v3(mat, co); BLI_kdtree_insert(tree, p, co); } BLI_kdtree_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->dm_final, psys->part->from, pa, mat); copy_v3_v3(co, key->co); mul_m4_v3(mat, co); co[0] = -co[0]; index = BLI_kdtree_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_free(tree); } static void PE_mirror_particle(Object *ob, DerivedMesh *dm, 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, dm, psys->part->from, pa, mat); psys_mat_hair_to_orco(ob, dm, 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; POINT_P; if (!psys) return; edit = psys->edit; psmd = psys_get_modifier(ob, psys); if (!psmd->dm_final) 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->dm_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 */ /************************************************/ /* 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; ParticleSystemModifierData *psmd; POINT_P; KEY_K; int index; float *vec, *nor, dvec[3], dot, dist_1st = 0.0f; float hairimat[4][4], hairmat[4][4]; 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; psmd = psys_get_modifier(ob, psys); if (!psmd->dm_final) return; LOOP_EDITED_POINTS { psys_mat_hair_to_object(ob, psmd->dm_final, psys->part->from, psys->particles + p, 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 * pset->emitterdist; } else { index = BLI_kdtree_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); } } } /* force set distances between neighboring keys */ static void PE_apply_lengths(Scene *scene, PTCacheEdit *edit) { ParticleEditSettings *pset = PE_settings(scene); POINT_P; KEY_K; float dv1[3]; if (edit == 0 || (pset->flag & PE_KEEP_LENGTHS) == 0) return; if (edit->psys && edit->psys->flag & PSYS_GLOBAL_HAIR) return; LOOP_EDITED_POINTS { LOOP_KEYS { if (k) { 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); } } } } /* 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); POINT_P; PTCacheEditKey *key; int j, k; 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}; if (edit == 0 || (pset->flag & PE_KEEP_LENGTHS) == 0) return; if (edit->psys && edit->psys->flag & PSYS_GLOBAL_HAIR) return; LOOP_EDITED_POINTS { for (j = 1; j < point->totkey; j++) { 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); } } } } /* set current distances to be kept between neighbouting 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(Object *ob, ParticleSystem *psys) { DerivedMesh *dm = psys_get_modifier(ob, psys)->dm_final; PTCacheEdit *edit = psys->edit; float *vec, *nor; int i, totface /*, totvert*/; if (!dm) return; if (edit->emitter_cosnos) MEM_freeN(edit->emitter_cosnos); BLI_kdtree_free(edit->emitter_field); totface = dm->getNumTessFaces(dm); /*totvert=dm->getNumVerts(dm);*/ /*UNSUED*/ edit->emitter_cosnos = MEM_callocN(totface * 6 * sizeof(float), "emitter cosnos"); edit->emitter_field = BLI_kdtree_new(totface); vec = edit->emitter_cosnos; nor = vec + 3; for (i = 0; i < totface; i++, vec += 6, nor += 6) { MFace *mface = dm->getTessFaceData(dm, i, CD_MFACE); MVert *mvert; mvert = dm->getVertData(dm, mface->v1, CD_MVERT); copy_v3_v3(vec, mvert->co); VECCOPY(nor, mvert->no); mvert = dm->getVertData(dm, mface->v2, CD_MVERT); add_v3_v3v3(vec, vec, mvert->co); VECADD(nor, nor, mvert->no); mvert = dm->getVertData(dm, mface->v3, CD_MVERT); add_v3_v3v3(vec, vec, mvert->co); VECADD(nor, nor, mvert->no); if (mface->v4) { mvert = dm->getVertData(dm, mface->v4, CD_MVERT); add_v3_v3v3(vec, vec, mvert->co); VECADD(nor, nor, mvert->no); mul_v3_fl(vec, 0.25); } else mul_v3_fl(vec, 1.0f / 3.0f); normalize_v3(nor); BLI_kdtree_insert(edit->emitter_field, i, vec); } BLI_kdtree_balance(edit->emitter_field); } static void PE_update_selection(Main *bmain, Scene *scene, Object *ob, int useflag) { PTCacheEdit *edit = PE_get_current(bmain, 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(scene, ob, edit, CFRA, G.is_rendering); /* disable update flag */ LOOP_POINTS { point->flag &= ~PEP_EDIT_RECALC; } } void update_world_cos(Object *ob, PTCacheEdit *edit) { ParticleSystem *psys = edit->psys; ParticleSystemModifierData *psmd = psys_get_modifier(ob, psys); POINT_P; KEY_K; float hairmat[4][4]; if (psys == 0 || psys->edit == 0 || psmd->dm_final == NULL) return; LOOP_POINTS { if (!(psys->flag & PSYS_GLOBAL_HAIR)) psys_mat_hair_to_global(ob, psmd->dm_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(Main *bmain, 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(bmain, 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); PE_hide_keys_time(scene, edit, CFRA); /* regenerate path caches */ psys_cache_edit_paths(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) { 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; } static void select_keys(PEData *data, int point_index, int UNUSED(key_index)) { 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) { 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; } static void deselect_key_select(PEData *data, int point_index, int key_index) { 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; } static void toggle_key_select(PEData *data, int point_index, int key_index) { 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; } /************************ de select all operator ************************/ static void select_action_apply(PTCacheEditPoint *point, PTCacheEditKey *key, int action) { switch (action) { case SEL_SELECT: if ((key->flag & PEK_SELECT) == 0) { key->flag |= PEK_SELECT; point->flag |= PEP_EDIT_RECALC; } break; case SEL_DESELECT: if (key->flag & PEK_SELECT) { key->flag &= ~PEK_SELECT; point->flag |= PEP_EDIT_RECALC; } break; case SEL_INVERT: if ((key->flag & PEK_SELECT) == 0) { key->flag |= PEK_SELECT; point->flag |= PEP_EDIT_RECALC; } else { key->flag &= ~PEK_SELECT; point->flag |= PEP_EDIT_RECALC; } break; } } static int pe_select_all_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); PTCacheEdit *edit = PE_get_current(bmain, 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; } } LOOP_VISIBLE_POINTS { LOOP_VISIBLE_KEYS { select_action_apply(point, key, action); } } PE_update_selection(bmain, 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 ************************/ int PE_mouse_particles(bContext *C, const int mval[2], bool extend, bool deselect, bool toggle) { Main *bmain = CTX_data_main(C); PEData data; Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); PTCacheEdit *edit = PE_get_current(bmain, scene, ob); POINT_P; KEY_K; if (!PE_start_edit(edit)) return OPERATOR_CANCELLED; if (!extend && !deselect && !toggle) { LOOP_VISIBLE_POINTS { LOOP_SELECTED_KEYS { key->flag &= ~PEK_SELECT; point->flag |= PEP_EDIT_RECALC; } } } PE_set_view3d_data(C, &data); data.mval = mval; data.rad = ED_view3d_select_dist_px(); /* 1 = nearest only */ if (extend) for_mouse_hit_keys(&data, extend_key_select, true); else if (deselect) for_mouse_hit_keys(&data, deselect_key_select, true); else for_mouse_hit_keys(&data, toggle_key_select, true); PE_update_selection(bmain, scene, ob, 1); WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, data.ob); return OPERATOR_FINISHED; } /************************ 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) 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); PE_update_selection(data.bmain, 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); } /************************ 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) 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); PE_update_selection(data.bmain, 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_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); } /*********************** 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.bmain, 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 { 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; select_action_apply(point, key, flag); } } break; } BLI_rng_free(rng); PE_update_selection(data.bmain, 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 *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, true); PE_update_selection(data.bmain, 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_invoke(bContext *C, wmOperator *op, const wmEvent *event) { RNA_int_set_array(op->ptr, "location", event->mval); return select_linked_exec(C, op); } void PARTICLE_OT_select_linked(wmOperatorType *ot) { /* identifiers */ ot->name = "Select Linked"; ot->idname = "PARTICLE_OT_select_linked"; ot->description = "Select nearest particle from mouse pointer"; /* api callbacks */ ot->exec = select_linked_exec; ot->invoke = select_linked_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); } /************************ border select operator ************************/ void PE_deselect_all_visible(PTCacheEdit *edit) { POINT_P; KEY_K; LOOP_VISIBLE_POINTS { LOOP_SELECTED_KEYS { key->flag &= ~PEK_SELECT; point->flag |= PEP_EDIT_RECALC; } } } int PE_border_select(bContext *C, const rcti *rect, bool select, bool extend) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); PTCacheEdit *edit = PE_get_current(bmain, scene, ob); PEData data; if (!PE_start_edit(edit)) return OPERATOR_CANCELLED; if (extend == 0 && select) PE_deselect_all_visible(edit); PE_set_view3d_data(C, &data); data.rect = rect; data.select = select; for_mouse_hit_keys(&data, select_key, false); PE_update_selection(bmain, scene, ob, 1); WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, ob); return OPERATOR_FINISHED; } /************************ circle select operator ************************/ int PE_circle_select(bContext *C, int selecting, const int mval[2], float rad) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); PTCacheEdit *edit = PE_get_current(bmain, scene, ob); PEData data; if (!PE_start_edit(edit)) return OPERATOR_FINISHED; PE_set_view3d_data(C, &data); data.mval = mval; data.rad = rad; data.select = selecting; for_mouse_hit_keys(&data, select_key, false); PE_update_selection(bmain, scene, ob, 1); WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, ob); return OPERATOR_FINISHED; } /************************ lasso select operator ************************/ int PE_lasso_select(bContext *C, const int mcords[][2], const short moves, bool extend, bool select) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); ARegion *ar = CTX_wm_region(C); ParticleEditSettings *pset = PE_settings(scene); PTCacheEdit *edit = PE_get_current(bmain, scene, ob); ParticleSystem *psys = edit->psys; ParticleSystemModifierData *psmd = psys_get_modifier(ob, psys); 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; if (extend == 0 && select) PE_deselect_all_visible(edit); /* only for depths */ PE_set_view3d_data(C, &data); LOOP_VISIBLE_POINTS { if (edit->psys && !(psys->flag & PSYS_GLOBAL_HAIR)) psys_mat_hair_to_global(ob, psmd->dm_final, psys->part->from, psys->particles + p, mat); if (pset->selectmode == SCE_SELECT_POINT) { LOOP_KEYS { copy_v3_v3(co, key->co); mul_m4_v3(mat, co); if ((ED_view3d_project_int_global(ar, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) == V3D_PROJ_RET_OK) && BLI_lasso_is_point_inside(mcords, moves, screen_co[0], screen_co[1], IS_CLIPPED) && key_test_depth(&data, co, screen_co)) { if (select) { if (!(key->flag & PEK_SELECT)) { key->flag |= PEK_SELECT; point->flag |= PEP_EDIT_RECALC; } } else { if (key->flag & PEK_SELECT) { key->flag &= ~PEK_SELECT; point->flag |= PEP_EDIT_RECALC; } } } } } 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); if ((ED_view3d_project_int_global(ar, co, screen_co, V3D_PROJ_TEST_CLIP_WIN) == V3D_PROJ_RET_OK) && BLI_lasso_is_point_inside(mcords, moves, screen_co[0], screen_co[1], IS_CLIPPED) && key_test_depth(&data, co, screen_co)) { if (select) { if (!(key->flag & PEK_SELECT)) { key->flag |= PEK_SELECT; point->flag |= PEP_EDIT_RECALC; } } else { if (key->flag & PEK_SELECT) { key->flag &= ~PEK_SELECT; point->flag |= PEP_EDIT_RECALC; } } } } } } PE_update_selection(bmain, scene, ob, 1); WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_SELECTED, ob); return OPERATOR_FINISHED; } /*************************** hide operator **************************/ static int hide_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); Object *ob = CTX_data_active_object(C); Scene *scene = CTX_data_scene(C); PTCacheEdit *edit = PE_get_current(bmain, scene, ob); POINT_P; KEY_K; if (RNA_enum_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(bmain, 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) { Main *bmain = CTX_data_main(C); Object *ob = CTX_data_active_object(C); Scene *scene = CTX_data_scene(C); PTCacheEdit *edit = PE_get_current(bmain, 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(bmain, 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_TAG | PEK_SELECT); point->flag |= PEP_EDIT_RECALC; /* redraw selection only */ } } } 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.bmain, 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_TAG; key->flag |= PEK_SELECT; point->flag |= PEP_EDIT_RECALC; /* redraw selection only */ } } } 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.bmain, 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.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.bmain, 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(Main *bmain, Scene *scene, Object *ob, int pa_index, float path_time) { PTCacheEdit *edit = PE_get_current(bmain, 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.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; int i, new_totpart = psys->totpart, removed = 0; if (mirror) { /* mirror tags */ psmd = psys_get_modifier(ob, psys); LOOP_TAGGED_POINTS { PE_mirror_particle(ob, psmd->dm_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(Object *ob, ParticleSystem *psys) { PTCacheEdit *edit = psys->edit; ParticleData *pa; HairKey *hkey, *nhkey, *new_hkeys = 0; POINT_P; KEY_K; PTCacheEditKey *nkey, *new_keys; ParticleSystemModifierData *psmd; short new_totkey; if (pe_x_mirror(ob)) { /* mirror key tags */ psmd = psys_get_modifier(ob, psys); LOOP_POINTS { LOOP_TAGGED_KEYS { PE_mirror_particle(ob, psmd->dm_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.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_object(data.bmain, data.scene, data.ob, 1); 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) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); PTCacheEdit *edit = PE_get_current(bmain, scene, ob); ParticleSystem *psys = edit->psys; ParticleSystemModifierData *psmd; KDTree *tree; KDTreeNearest 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 = psys_get_modifier(ob, psys); totremoved = 0; do { removed = 0; tree = BLI_kdtree_new(psys->totpart); /* insert particles into kd tree */ LOOP_SELECTED_POINTS { psys_mat_hair_to_object(ob, psmd->dm_final, psys->part->from, psys->particles + p, mat); copy_v3_v3(co, point->keys->co); mul_m4_v3(mat, co); BLI_kdtree_insert(tree, p, co); } BLI_kdtree_balance(tree); /* tag particles to be removed */ LOOP_SELECTED_POINTS { psys_mat_hair_to_object(ob, psmd->dm_final, psys->part->from, psys->particles + p, mat); copy_v3_v3(co, point->keys->co); mul_m4_v3(mat, co); totn = BLI_kdtree_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_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 particles", totremoved); DAG_id_tag_update(&ob->id, OB_RECALC_DATA); 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) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); ParticleEditSettings *pset = PE_settings(scene); Object *ob = CTX_data_active_object(C); PTCacheEdit *edit = PE_get_current(bmain, 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); } } DAG_id_tag_update(&ob->id, OB_RECALC_DATA); 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 (pset->brushtype < 0) return; brush = &pset->brush[pset->brushtype]; if (brush) { glPushMatrix(); glTranslatef((float)x, (float)y, 0.0f); glColor4ub(255, 255, 255, 128); glEnable(GL_LINE_SMOOTH); glEnable(GL_BLEND); glutil_draw_lined_arc(0.0, M_PI * 2.0, pe_brush_size_get(scene, brush), 40); glDisable(GL_BLEND); glDisable(GL_LINE_SMOOTH); glPopMatrix(); } } static void toggle_particle_cursor(bContext *C, int enable) { ParticleEditSettings *pset = PE_settings(CTX_data_scene(C)); if (pset->paintcursor && !enable) { WM_paint_cursor_end(CTX_wm_manager(C), pset->paintcursor); pset->paintcursor = NULL; } else if (enable) pset->paintcursor = WM_paint_cursor_activate(CTX_wm_manager(C), 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) { 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.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); } DAG_id_tag_update(&data.ob->id, OB_RECALC_DATA); 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(Main *bmain, Scene *scene, Object *ob, int tagged) { Mesh *me = (Mesh *)(ob->data); ParticleSystemModifierData *psmd; PTCacheEdit *edit = PE_get_current(bmain, 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 = psys_get_modifier(ob, psys); if (!psmd->dm_final) return; const bool use_dm_final_indices = (psys->part->use_modifier_stack && !psmd->dm_final->deformedOnly); /* 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 DM tessface indices, we mirror final DM itself, not orig mesh. Avoids an (impossible) * dm -> orig -> dm tessface indices conversion... */ mirrorfaces = mesh_get_x_mirror_faces(ob, NULL, use_dm_final_indices ? psmd->dm_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->dm_final, psys, pa, NULL); continue; } else 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->dm_final->getTessFaceArray(psmd->dm_final) : 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->dm_final, psmd->dm_deformed, 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->dm_final, psys, pa, newpa); newpa++; newpoint++; } } LOOP_POINTS { point->flag &= ~PEP_TAG; } MEM_freeN(mirrorfaces); } static int mirror_exec(bContext *C, wmOperator *UNUSED(op)) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); PTCacheEdit *edit = PE_get_current(bmain, scene, ob); PE_mirror_x(bmain, scene, ob, 0); update_world_cos(ob, edit); WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_EDITED, ob); DAG_id_tag_update(&ob->id, OB_RECALC_DATA); return OPERATOR_FINISHED; } 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 = PE_hair_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) { 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 - data->dist / 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 *ar = data->vc.ar; 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]; /* blunt scissors */ if (BLI_frand() > data->cutfac) return; /* don't cut hidden */ if (edit->points[pa_index].flag & PEP_HIDE) return; if (ED_view3d_project_int_global(ar, 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(ar, 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->bmain, data->scene, ob, pa_index, cut_time); edit->points[pa_index].flag |= PEP_EDIT_RECALC; } } } static void brush_length(PEData *data, int point_index) { 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) { 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->dm, 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_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 - data->dist / 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 [#35406] */ 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 */ { #if 0 /* kindof works but looks worse then 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_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)) { /* 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) { 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 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 a derivedmesh */ static int particle_intersect_dm(Scene *scene, Object *ob, DerivedMesh *dm, 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 (dm == NULL) { psys_disable_all(ob); dm = mesh_get_derived_final(scene, ob, 0); if (dm == NULL) dm = mesh_get_derived_deform(scene, ob, 0); psys_enable_all(ob); if (dm == NULL) return 0; } /* BMESH_ONLY, deform dm may not have tessface */ DM_ensure_tessface(dm); 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 = dm->getNumTessFaces(dm); mface = dm->getTessFaceDataArray(dm, CD_MFACE); mvert = dm->getVertDataArray(dm, CD_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; } static int brush_add(PEData *data, short number) { Scene *scene = data->scene; Object *ob = data->ob; DerivedMesh *dm; PTCacheEdit *edit = data->edit; ParticleSystem *psys = edit->psys; ParticleData *add_pars; ParticleSystemModifierData *psmd = psys_get_modifier(ob, psys); ParticleSimulationData sim = {0}; ParticleEditSettings *pset = PE_settings(scene); int i, k, n = 0, totpart = psys->totpart; float mco[2]; float dmx, dmy; float co1[3], co2[3], min_d, imat[4][4]; float framestep, timestep; short size = pset->brush[PE_BRUSH_ADD].size; short size2 = size * 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.scene = scene; sim.ob = ob; sim.psys = psys; sim.psmd = psmd; timestep = psys_get_timestep(&sim); if (psys->part->use_modifier_stack || psmd->dm_final->deformedOnly) { dm = psmd->dm_final; } else { dm = psmd->dm_deformed; } BLI_assert(dm); for (i = 0; i < number; i++) { if (number > 1) { dmx = size; dmy = size; /* rejection sampling to get points in circle */ while (dmx * dmx + dmy * dmy > size2) { dmx = (2.0f * BLI_rng_get_float(rng) - 1.0f) * size; dmy = (2.0f * BLI_rng_get_float(rng) - 1.0f) * size; } } else { dmx = 0.0f; dmy = 0.0f; } mco[0] = data->mval[0] + dmx; mco[1] = data->mval[1] + dmy; ED_view3d_win_to_segment_clipped(data->vc.ar, data->vc.v3d, mco, co1, co2, true); mul_m4_v3(imat, co1); mul_m4_v3(imat, co2); min_d = 2.0; /* warning, returns the derived mesh face */ if (particle_intersect_dm(scene, ob, dm, 0, co1, co2, &min_d, &add_pars[n].num_dmcache, add_pars[n].fuv, 0, 0, 0, 0)) { if (psys->part->use_modifier_stack && !psmd->dm_final->deformedOnly) { add_pars[n].num = add_pars[n].num_dmcache; add_pars[n].num_dmcache = DMCACHE_ISCHILD; } else if (dm == psmd->dm_deformed) { /* Final DM is not same topology as orig mesh, we have to map num_dmcache to real final dm. */ add_pars[n].num = add_pars[n].num_dmcache; add_pars[n].num_dmcache = psys_particle_dm_face_lookup( psmd->dm_final, psmd->dm_deformed, add_pars[n].num, add_pars[n].fuv, NULL); } else { add_pars[n].num = add_pars[n].num_dmcache; } if (add_pars[n].num != DMCACHE_NOTFOUND) { n++; } } } if (n) { int newtotpart = totpart + n; float hairmat[4][4], cur_co[3]; KDTree *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_new(psys->totpart); for (i = 0, pa = psys->particles; i < totpart; i++, pa++) { psys_particle_on_dm(psmd->dm_final, psys->part->from, pa->num, pa->num_dmcache, pa->fuv, pa->foffset, cur_co, 0, 0, 0, 0, 0); BLI_kdtree_insert(tree, i, cur_co); } BLI_kdtree_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; initialize_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 ptn[3]; int w, maxw; float maxd, totw = 0.0, weight[3]; psys_particle_on_dm(psmd->dm_final, psys->part->from, pa->num, pa->num_dmcache, pa->fuv, pa->foffset, co1, 0, 0, 0, 0, 0); maxw = BLI_kdtree_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.f - (float)k / (float)(pset->totaddkey - 1); } } for (k = 0, hkey = pa->hair; k < pset->totaddkey; k++, hkey++) { psys_mat_hair_to_global(ob, psmd->dm_final, psys->part->from, pa, hairmat); invert_m4_m4(imat, hairmat); mul_m4_v3(imat, hkey->co); } } if (tree) BLI_kdtree_free(tree); } MEM_freeN(add_pars); BLI_rng_free(rng); return n; } /************************* brush edit operator ********************/ typedef struct BrushEdit { Scene *scene; 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) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); ParticleEditSettings *pset = PE_settings(scene); PTCacheEdit *edit = PE_get_current(bmain, scene, ob); ARegion *ar = CTX_wm_region(C); BrushEdit *bedit; float min[3], max[3]; if (pset->brushtype < 0) return 0; /* set the 'distance factor' for grabbing (used in comb etc) */ INIT_MINMAX(min, max); PE_minmax(bmain, scene, min, max); mid_v3_v3v3(min, min, max); bedit = MEM_callocN(sizeof(BrushEdit), "BrushEdit"); bedit->first = 1; op->customdata = bedit; bedit->scene = scene; bedit->ob = ob; bedit->edit = edit; bedit->zfac = ED_view3d_calc_zfac(ar->regiondata, min, NULL); /* cache view depths and settings for re-use */ PE_set_view3d_data(C, &bedit->data); return 1; } static void brush_edit_apply(bContext *C, wmOperator *op, PointerRNA *itemptr) { Main *bmain = CTX_data_main(C); BrushEdit *bedit = op->customdata; Scene *scene = bedit->scene; Object *ob = bedit->ob; PTCacheEdit *edit = bedit->edit; ParticleEditSettings *pset = PE_settings(scene); ParticleSystemModifierData *psmd = edit->psys ? psys_get_modifier(ob, edit->psys) : NULL; ParticleBrushData *brush = &pset->brush[pset->brushtype]; ARegion *ar = 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; 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(ar, 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.dm = psmd->dm_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(&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.dm = psmd->dm_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(bmain, scene, ob, 1); update_world_cos(ob, edit); psys_free_path_cache(NULL, edit); DAG_id_tag_update(&ob->id, OB_RECALC_DATA); } else PE_update_object(bmain, scene, ob, 1); } if (edit->psys) { WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_EDITED, ob); } else { DAG_id_tag_update(&ob->id, OB_RECALC_DATA); 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; 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]; VECCOPY2D(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, 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; BLI_bvhtree_ray_cast_all( shape_bvh->tree, key->co, 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, key)) { cut_time = -1.0f; cut = true; } else { for (k = 0; k < totkeys; k++, key++) { BVHTreeRayHit hit; float dir[3]; float len; sub_v3_v3v3(dir, (key + 1)->co, key->co); len = normalize_v3(dir); memset(&hit, 0, sizeof(hit)); hit.index = -1; hit.dist = len; BLI_bvhtree_ray_cast(data->shape_bvh.tree, key->co, dir, 0.0f, &hit, data->shape_bvh.raycast_callback, &data->shape_bvh); if (hit.index >= 0) { if (hit.dist < len) { cut_time = (hit.dist / len + (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->bmain, 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)) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); ParticleEditSettings *pset = PE_settings(scene); PTCacheEdit *edit = PE_get_current(bmain, 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); DAG_id_tag_update(&ob->id, OB_RECALC_DATA); } else PE_update_object(bmain, scene, ob, 1); if (edit->psys) { WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_EDITED, ob); } else { DAG_id_tag_update(&ob->id, OB_RECALC_DATA); 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(Main *bmain, Scene *scene, float min[3], float max[3]) { Object *ob = OBACT; PTCacheEdit *edit = PE_get_current(bmain, scene, ob); ParticleSystem *psys; ParticleSystemModifierData *psmd = NULL; POINT_P; KEY_K; float co[3], mat[4][4]; int ok = 0; if (!edit) return ok; if ((psys = edit->psys)) psmd = psys_get_modifier(ob, psys); else unit_m4(mat); LOOP_VISIBLE_POINTS { if (psys) psys_mat_hair_to_global(ob, psmd->dm_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(Main *bmain, Scene *scene, Object *ob, PointCache *cache, ParticleSystem *psys) { PTCacheEdit *edit; ParticleSystemModifierData *psmd = (psys) ? psys_get_modifier(ob, psys) : NULL; POINT_P; KEY_K; ParticleData *pa = NULL; HairKey *hkey; int totpoint; /* no psmd->dm happens in case particle system modifier is not enabled */ if (!(psys && psmd && psmd->dm_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) { 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) { psys->edit = edit; edit->psys = psys; 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 { if (BKE_ptcache_mem_pointers_seek(p, pm) == 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 = pm->cur[BPHYS_DATA_LOCATION]; key->vel = pm->cur[BPHYS_DATA_VELOCITY]; key->rot = pm->cur[BPHYS_DATA_ROTATION]; key->ftime = (float)pm->frame; key->time = &key->ftime; BKE_ptcache_mem_pointers_incr(pm); point->totkey++; } } psys = NULL; } UI_GetThemeColor3ubv(TH_EDGE_SELECT, edit->sel_col); UI_GetThemeColor3ubv(TH_WIRE, edit->nosel_col); recalc_lengths(edit); if (psys && !cache) recalc_emitter_field(ob, psys); PE_update_object(bmain, 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; if (CTX_data_edit_object(C)) return 0; return (ob->particlesystem.first || modifiers_findByType(ob, eModifierType_Cloth) || modifiers_findByType(ob, eModifierType_Softbody)); } static int particle_edit_toggle_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(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) { PTCacheEdit *edit; ob->mode |= mode_flag; edit = PE_create_current(bmain, 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) recalc_emitter_field(ob, edit->psys); toggle_particle_cursor(C, 1); WM_event_add_notifier(C, NC_SCENE | ND_MODE | NS_MODE_PARTICLE, NULL); } else { ob->mode &= ~mode_flag; toggle_particle_cursor(C, 0); WM_event_add_notifier(C, NC_SCENE | ND_MODE | NS_MODE_OBJECT, NULL); } DAG_id_tag_update(&ob->id, OB_RECALC_DATA); 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 |= PSYS_RECALC_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); DAG_id_tag_update(&ob->id, OB_RECALC_DATA); } } else { /* some operation might have protected hair from editing so let's clear the flag */ psys->recalc |= PSYS_RECALC_RESET; psys->flag &= ~PSYS_GLOBAL_HAIR; psys->flag &= ~PSYS_EDITED; WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_EDITED, ob); DAG_id_tag_update(&ob->id, OB_RECALC_DATA); } return OPERATOR_FINISHED; } static int clear_edited_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { return WM_operator_confirm_message(C, op, "Lose changes done in particle mode? (no undo)"); } 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; ot->invoke = clear_edited_invoke; /* 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)) { Main *bmain = CTX_data_main(C); Object *ob = CTX_data_active_object(C); Scene *scene = CTX_data_scene(C); PTCacheEdit *edit = PE_get_current(bmain, 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(bmain, scene, ob, 1); if (edit->psys) { WM_event_add_notifier(C, NC_OBJECT | ND_PARTICLE | NA_EDITED, ob); } else { DAG_id_tag_update(&ob->id, OB_RECALC_DATA); 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; }
Trovare la differenza