Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/blenkernel/intern/particle.c')
-rw-r--r--source/blender/blenkernel/intern/particle.c824
1 files changed, 305 insertions, 519 deletions
diff --git a/source/blender/blenkernel/intern/particle.c b/source/blender/blenkernel/intern/particle.c
index 6c354c59d7a..e7534741c89 100644
--- a/source/blender/blenkernel/intern/particle.c
+++ b/source/blender/blenkernel/intern/particle.c
@@ -65,6 +65,7 @@
#include "BKE_boids.h"
#include "BKE_cloth.h"
#include "BKE_colortools.h"
+#include "BKE_editstrands.h"
#include "BKE_effect.h"
#include "BKE_global.h"
#include "BKE_group.h"
@@ -362,6 +363,38 @@ int psys_uses_gravity(ParticleSimulationData *sim)
{
return sim->scene->physics_settings.flag & PHYS_GLOBAL_GRAVITY && sim->psys->part && sim->psys->part->effector_weights->global_gravity != 0.0f;
}
+
+KeyBlock *BKE_psys_insert_shape_key(Scene *scene, Object *ob, ParticleSystem *psys, const char *name, const bool from_mix)
+{
+ Key *key = psys->key;
+ KeyBlock *kb;
+ int newkey = 0;
+
+ if (key == NULL) {
+ key = psys->key = BKE_key_add_particles(ob, psys);
+ key->type = KEY_RELATIVE;
+ newkey = 1;
+ }
+
+ if (newkey || !from_mix) {
+ /* create from particles */
+ kb = BKE_keyblock_add_ctime(key, name, false);
+ BKE_keyblock_convert_from_hair_keys(ob, psys, kb);
+ }
+ else {
+ /* copy from current values */
+ int totelem;
+ float *data = BKE_key_evaluate_particles(ob, psys, scene ? scene->r.cfra : 0.0f, &totelem);
+
+ /* create new block with prepared data */
+ kb = BKE_keyblock_add_ctime(key, name, false);
+ kb->data = data;
+ kb->totelem = totelem;
+ }
+
+ return kb;
+}
+
/************************************************/
/* Freeing stuff */
/************************************************/
@@ -546,6 +579,11 @@ void psys_free(Object *ob, ParticleSystem *psys)
if (psys->edit && psys->free_edit)
psys->free_edit(psys->edit);
+ if (psys->hairedit) {
+ BKE_editstrands_free(psys->hairedit);
+ MEM_freeN(psys->hairedit);
+ psys->hairedit = NULL;
+ }
if (psys->child) {
MEM_freeN(psys->child);
@@ -570,6 +608,10 @@ void psys_free(Object *ob, ParticleSystem *psys)
psys->part->id.us--;
psys->part = NULL;
}
+ if (psys->key) {
+ id_us_min(&psys->key->id);
+ psys->key = NULL;
+ }
BKE_ptcache_free_list(&psys->ptcaches);
psys->pointcache = NULL;
@@ -769,400 +811,6 @@ bool psys_render_simplify_params(ParticleSystem *psys, ChildParticle *cpa, float
}
/************************************************/
-/* Interpolation */
-/************************************************/
-static float interpolate_particle_value(float v1, float v2, float v3, float v4, const float w[4], int four)
-{
- float value;
-
- value = w[0] * v1 + w[1] * v2 + w[2] * v3;
- if (four)
- value += w[3] * v4;
-
- CLAMP(value, 0.f, 1.f);
-
- return value;
-}
-
-void psys_interpolate_particle(short type, ParticleKey keys[4], float dt, ParticleKey *result, bool velocity)
-{
- float t[4];
-
- if (type < 0) {
- interp_cubic_v3(result->co, result->vel, keys[1].co, keys[1].vel, keys[2].co, keys[2].vel, dt);
- }
- else {
- key_curve_position_weights(dt, t, type);
-
- interp_v3_v3v3v3v3(result->co, keys[0].co, keys[1].co, keys[2].co, keys[3].co, t);
-
- if (velocity) {
- float temp[3];
-
- if (dt > 0.999f) {
- key_curve_position_weights(dt - 0.001f, t, type);
- interp_v3_v3v3v3v3(temp, keys[0].co, keys[1].co, keys[2].co, keys[3].co, t);
- sub_v3_v3v3(result->vel, result->co, temp);
- }
- else {
- key_curve_position_weights(dt + 0.001f, t, type);
- interp_v3_v3v3v3v3(temp, keys[0].co, keys[1].co, keys[2].co, keys[3].co, t);
- sub_v3_v3v3(result->vel, temp, result->co);
- }
- }
- }
-}
-
-
-typedef struct ParticleInterpolationData {
- HairKey *hkey[2];
-
- DerivedMesh *dm;
- MVert *mvert[2];
-
- int keyed;
- ParticleKey *kkey[2];
-
- PointCache *cache;
- PTCacheMem *pm;
-
- PTCacheEditPoint *epoint;
- PTCacheEditKey *ekey[2];
-
- float birthtime, dietime;
- int bspline;
-} ParticleInterpolationData;
-/* Assumes pointcache->mem_cache exists, so for disk cached particles call psys_make_temp_pointcache() before use */
-/* It uses ParticleInterpolationData->pm to store the current memory cache frame so it's thread safe. */
-static void get_pointcache_keys_for_time(Object *UNUSED(ob), PointCache *cache, PTCacheMem **cur, int index, float t, ParticleKey *key1, ParticleKey *key2)
-{
- static PTCacheMem *pm = NULL;
- int index1, index2;
-
- if (index < 0) { /* initialize */
- *cur = cache->mem_cache.first;
-
- if (*cur)
- *cur = (*cur)->next;
- }
- else {
- if (*cur) {
- while (*cur && (*cur)->next && (float)(*cur)->frame < t)
- *cur = (*cur)->next;
-
- pm = *cur;
-
- index2 = BKE_ptcache_mem_index_find(pm, index);
- index1 = BKE_ptcache_mem_index_find(pm->prev, index);
-
- BKE_ptcache_make_particle_key(key2, index2, pm->data, (float)pm->frame);
- if (index1 < 0)
- copy_particle_key(key1, key2, 1);
- else
- BKE_ptcache_make_particle_key(key1, index1, pm->prev->data, (float)pm->prev->frame);
- }
- else if (cache->mem_cache.first) {
- pm = cache->mem_cache.first;
- index2 = BKE_ptcache_mem_index_find(pm, index);
- BKE_ptcache_make_particle_key(key2, index2, pm->data, (float)pm->frame);
- copy_particle_key(key1, key2, 1);
- }
- }
-}
-static int get_pointcache_times_for_particle(PointCache *cache, int index, float *start, float *end)
-{
- PTCacheMem *pm;
- int ret = 0;
-
- for (pm = cache->mem_cache.first; pm; pm = pm->next) {
- if (BKE_ptcache_mem_index_find(pm, index) >= 0) {
- *start = pm->frame;
- ret++;
- break;
- }
- }
-
- for (pm = cache->mem_cache.last; pm; pm = pm->prev) {
- if (BKE_ptcache_mem_index_find(pm, index) >= 0) {
- *end = pm->frame;
- ret++;
- break;
- }
- }
-
- return ret == 2;
-}
-
-float psys_get_dietime_from_cache(PointCache *cache, int index)
-{
- PTCacheMem *pm;
- int dietime = 10000000; /* some max value so that we can default to pa->time+lifetime */
-
- for (pm = cache->mem_cache.last; pm; pm = pm->prev) {
- if (BKE_ptcache_mem_index_find(pm, index) >= 0)
- return (float)pm->frame;
- }
-
- return (float)dietime;
-}
-
-static void init_particle_interpolation(Object *ob, ParticleSystem *psys, ParticleData *pa, ParticleInterpolationData *pind)
-{
-
- if (pind->epoint) {
- PTCacheEditPoint *point = pind->epoint;
-
- pind->ekey[0] = point->keys;
- pind->ekey[1] = point->totkey > 1 ? point->keys + 1 : NULL;
-
- pind->birthtime = *(point->keys->time);
- pind->dietime = *((point->keys + point->totkey - 1)->time);
- }
- else if (pind->keyed) {
- ParticleKey *key = pa->keys;
- pind->kkey[0] = key;
- pind->kkey[1] = pa->totkey > 1 ? key + 1 : NULL;
-
- pind->birthtime = key->time;
- pind->dietime = (key + pa->totkey - 1)->time;
- }
- else if (pind->cache) {
- float start = 0.0f, end = 0.0f;
- get_pointcache_keys_for_time(ob, pind->cache, &pind->pm, -1, 0.0f, NULL, NULL);
- pind->birthtime = pa ? pa->time : pind->cache->startframe;
- pind->dietime = pa ? pa->dietime : pind->cache->endframe;
-
- if (get_pointcache_times_for_particle(pind->cache, pa - psys->particles, &start, &end)) {
- pind->birthtime = MAX2(pind->birthtime, start);
- pind->dietime = MIN2(pind->dietime, end);
- }
- }
- else {
- HairKey *key = pa->hair;
- pind->hkey[0] = key;
- pind->hkey[1] = key + 1;
-
- pind->birthtime = key->time;
- pind->dietime = (key + pa->totkey - 1)->time;
-
- if (pind->dm) {
- pind->mvert[0] = CDDM_get_vert(pind->dm, pa->hair_index);
- pind->mvert[1] = pind->mvert[0] + 1;
- }
- }
-}
-static void edit_to_particle(ParticleKey *key, PTCacheEditKey *ekey)
-{
- copy_v3_v3(key->co, ekey->co);
- if (ekey->vel) {
- copy_v3_v3(key->vel, ekey->vel);
- }
- key->time = *(ekey->time);
-}
-static void hair_to_particle(ParticleKey *key, HairKey *hkey)
-{
- copy_v3_v3(key->co, hkey->co);
- key->time = hkey->time;
-}
-
-static void mvert_to_particle(ParticleKey *key, MVert *mvert, HairKey *hkey)
-{
- copy_v3_v3(key->co, mvert->co);
- key->time = hkey->time;
-}
-
-static void do_particle_interpolation(ParticleSystem *psys, int p, ParticleData *pa, float t, ParticleInterpolationData *pind, ParticleKey *result)
-{
- PTCacheEditPoint *point = pind->epoint;
- ParticleKey keys[4];
- int point_vel = (point && point->keys->vel);
- float real_t, dfra, keytime, invdt = 1.f;
-
- /* billboards wont fill in all of these, so start cleared */
- memset(keys, 0, sizeof(keys));
-
- /* interpret timing and find keys */
- if (point) {
- if (result->time < 0.0f)
- real_t = -result->time;
- else
- real_t = *(pind->ekey[0]->time) + t * (*(pind->ekey[0][point->totkey - 1].time) - *(pind->ekey[0]->time));
-
- while (*(pind->ekey[1]->time) < real_t)
- pind->ekey[1]++;
-
- pind->ekey[0] = pind->ekey[1] - 1;
- }
- else if (pind->keyed) {
- /* we have only one key, so let's use that */
- if (pind->kkey[1] == NULL) {
- copy_particle_key(result, pind->kkey[0], 1);
- return;
- }
-
- if (result->time < 0.0f)
- real_t = -result->time;
- else
- real_t = pind->kkey[0]->time + t * (pind->kkey[0][pa->totkey - 1].time - pind->kkey[0]->time);
-
- if (psys->part->phystype == PART_PHYS_KEYED && psys->flag & PSYS_KEYED_TIMING) {
- ParticleTarget *pt = psys->targets.first;
-
- pt = pt->next;
-
- while (pt && pa->time + pt->time < real_t)
- pt = pt->next;
-
- if (pt) {
- pt = pt->prev;
-
- if (pa->time + pt->time + pt->duration > real_t)
- real_t = pa->time + pt->time;
- }
- else
- real_t = pa->time + ((ParticleTarget *)psys->targets.last)->time;
- }
-
- CLAMP(real_t, pa->time, pa->dietime);
-
- while (pind->kkey[1]->time < real_t)
- pind->kkey[1]++;
-
- pind->kkey[0] = pind->kkey[1] - 1;
- }
- else if (pind->cache) {
- if (result->time < 0.0f) /* flag for time in frames */
- real_t = -result->time;
- else
- real_t = pa->time + t * (pa->dietime - pa->time);
- }
- else {
- if (result->time < 0.0f)
- real_t = -result->time;
- else
- real_t = pind->hkey[0]->time + t * (pind->hkey[0][pa->totkey - 1].time - pind->hkey[0]->time);
-
- while (pind->hkey[1]->time < real_t) {
- pind->hkey[1]++;
- pind->mvert[1]++;
- }
-
- pind->hkey[0] = pind->hkey[1] - 1;
- }
-
- /* set actual interpolation keys */
- if (point) {
- edit_to_particle(keys + 1, pind->ekey[0]);
- edit_to_particle(keys + 2, pind->ekey[1]);
- }
- else if (pind->dm) {
- pind->mvert[0] = pind->mvert[1] - 1;
- mvert_to_particle(keys + 1, pind->mvert[0], pind->hkey[0]);
- mvert_to_particle(keys + 2, pind->mvert[1], pind->hkey[1]);
- }
- else if (pind->keyed) {
- memcpy(keys + 1, pind->kkey[0], sizeof(ParticleKey));
- memcpy(keys + 2, pind->kkey[1], sizeof(ParticleKey));
- }
- else if (pind->cache) {
- get_pointcache_keys_for_time(NULL, pind->cache, &pind->pm, p, real_t, keys + 1, keys + 2);
- }
- else {
- hair_to_particle(keys + 1, pind->hkey[0]);
- hair_to_particle(keys + 2, pind->hkey[1]);
- }
-
- /* set secondary interpolation keys for hair */
- if (!pind->keyed && !pind->cache && !point_vel) {
- if (point) {
- if (pind->ekey[0] != point->keys)
- edit_to_particle(keys, pind->ekey[0] - 1);
- else
- edit_to_particle(keys, pind->ekey[0]);
- }
- else if (pind->dm) {
- if (pind->hkey[0] != pa->hair)
- mvert_to_particle(keys, pind->mvert[0] - 1, pind->hkey[0] - 1);
- else
- mvert_to_particle(keys, pind->mvert[0], pind->hkey[0]);
- }
- else {
- if (pind->hkey[0] != pa->hair)
- hair_to_particle(keys, pind->hkey[0] - 1);
- else
- hair_to_particle(keys, pind->hkey[0]);
- }
-
- if (point) {
- if (pind->ekey[1] != point->keys + point->totkey - 1)
- edit_to_particle(keys + 3, pind->ekey[1] + 1);
- else
- edit_to_particle(keys + 3, pind->ekey[1]);
- }
- else if (pind->dm) {
- if (pind->hkey[1] != pa->hair + pa->totkey - 1)
- mvert_to_particle(keys + 3, pind->mvert[1] + 1, pind->hkey[1] + 1);
- else
- mvert_to_particle(keys + 3, pind->mvert[1], pind->hkey[1]);
- }
- else {
- if (pind->hkey[1] != pa->hair + pa->totkey - 1)
- hair_to_particle(keys + 3, pind->hkey[1] + 1);
- else
- hair_to_particle(keys + 3, pind->hkey[1]);
- }
- }
-
- dfra = keys[2].time - keys[1].time;
- keytime = (real_t - keys[1].time) / dfra;
-
- /* convert velocity to timestep size */
- if (pind->keyed || pind->cache || point_vel) {
- invdt = dfra * 0.04f * (psys ? psys->part->timetweak : 1.f);
- mul_v3_fl(keys[1].vel, invdt);
- mul_v3_fl(keys[2].vel, invdt);
- interp_qt_qtqt(result->rot, keys[1].rot, keys[2].rot, keytime);
- }
-
- /* now we should have in chronologiacl order k1<=k2<=t<=k3<=k4 with keytime between [0, 1]->[k2, k3] (k1 & k4 used for cardinal & bspline interpolation)*/
- psys_interpolate_particle((pind->keyed || pind->cache || point_vel) ? -1 /* signal for cubic interpolation */
- : (pind->bspline ? KEY_BSPLINE : KEY_CARDINAL),
- keys, keytime, result, 1);
-
- /* the velocity needs to be converted back from cubic interpolation */
- if (pind->keyed || pind->cache || point_vel)
- mul_v3_fl(result->vel, 1.f / invdt);
-}
-
-static void interpolate_pathcache(ParticleCacheKey *first, float t, ParticleCacheKey *result)
-{
- int i = 0;
- ParticleCacheKey *cur = first;
-
- /* scale the requested time to fit the entire path even if the path is cut early */
- t *= (first + first->segments)->time;
-
- while (i < first->segments && cur->time < t)
- cur++;
-
- if (cur->time == t)
- *result = *cur;
- else {
- float dt = (t - (cur - 1)->time) / (cur->time - (cur - 1)->time);
- interp_v3_v3v3(result->co, (cur - 1)->co, cur->co, dt);
- interp_v3_v3v3(result->vel, (cur - 1)->vel, cur->vel, dt);
- interp_qt_qtqt(result->rot, (cur - 1)->rot, cur->rot, dt);
- result->time = t;
- }
-
- /* first is actual base rotation, others are incremental from first */
- if (cur == first || cur - 1 == first)
- copy_qt_qt(result->rot, first->rot);
- else
- mul_qt_qtqt(result->rot, first->rot, result->rot);
-}
-
-/************************************************/
/* Particles on a dm */
/************************************************/
/* interpolate a location on a face based on face coordinates */
@@ -1333,6 +981,19 @@ void psys_interpolate_mcol(const MCol *mcol, int quad, const float w[4], MCol *m
}
}
+static float interpolate_particle_value(float v1, float v2, float v3, float v4, const float w[4], int four)
+{
+ float value;
+
+ value = w[0] * v1 + w[1] * v2 + w[2] * v3;
+ if (four)
+ value += w[3] * v4;
+
+ CLAMP(value, 0.f, 1.f);
+
+ return value;
+}
+
static float psys_interpolate_value_from_verts(DerivedMesh *dm, short from, int index, const float fw[4], const float *values)
{
if (values == 0 || index == -1)
@@ -1520,6 +1181,11 @@ static int psys_map_index_on_dm(DerivedMesh *dm, int from, int index, int index_
return 1;
}
+int psys_get_index_on_dm(ParticleSystem *psys, DerivedMesh *dm, ParticleData *pa, int *mapindex, float mapfw[4])
+{
+ return psys_map_index_on_dm(dm, psys->part->from, pa->num, pa->num_dmcache, pa->fuv, pa->foffset, mapindex, mapfw);
+}
+
/* interprets particle data to get a point on a mesh in object space */
void psys_particle_on_dm(DerivedMesh *dm, int from, int index, int index_dmcache,
const float fw[4], float foffset, float vec[3], float nor[3], float utan[3], float vtan[3],
@@ -1709,8 +1375,8 @@ void psys_particle_on_emitter(ParticleSystemModifierData *psmd, int from, int in
extern void do_kink(ParticleKey *state, const float par_co[3], const float par_vel[3], const float par_rot[4], float time, float freq, float shape, float amplitude, float flat,
short type, short axis, float obmat[4][4], int smooth_start);
-extern float do_clump(ParticleKey *state, const float par_co[3], float time, const float orco_offset[3], float clumpfac, float clumppow, float pa_clump,
- bool use_clump_noise, float clump_noise_size, CurveMapping *clumpcurve);
+extern float do_clump(ParticleKey *state, const float par_co[3], const float par_orco[3], float time, const float orco[3], float clumpfac, float clumppow, float pa_clump,
+ bool use_clump_noise, float clump_noise_size, CurveMapping *clumpcurve, float clump_noise_random, float clump_noise_random_size, float mat[4][4]);
void precalc_guides(ParticleSimulationData *sim, ListBase *effectors)
{
@@ -1834,12 +1500,14 @@ int do_guides(ParticleSettings *part, ListBase *effectors, ParticleKey *state, i
float par_co[3] = {0.0f, 0.0f, 0.0f};
float par_vel[3] = {0.0f, 0.0f, 0.0f};
float par_rot[4] = {1.0f, 0.0f, 0.0f, 0.0f};
- float orco_offset[3] = {0.0f, 0.0f, 0.0f};
+ float par_orco[3] = {0.0f, 0.0f, 0.0f};
+ float orco[3] = {0.0f, 0.0f, 0.0f};
copy_v3_v3(key.co, vec_to_point);
do_kink(&key, par_co, par_vel, par_rot, guidetime, pd->kink_freq, pd->kink_shape, pd->kink_amp, 0.f, pd->kink, pd->kink_axis, 0, 0);
- do_clump(&key, par_co, guidetime, orco_offset, pd->clump_fac, pd->clump_pow, 1.0f,
- part->child_flag & PART_CHILD_USE_CLUMP_NOISE, part->clump_noise_size, clumpcurve);
+ do_clump(&key, par_co, par_orco, guidetime, orco, pd->clump_fac, pd->clump_pow, 1.0f,
+ part->child_flag & PART_CHILD_USE_CLUMP_NOISE, part->clump_noise_size, clumpcurve,
+ part->clump_noise_random, part->clump_noise_random_size, NULL);
copy_v3_v3(vec_to_point, key.co);
}
@@ -1868,7 +1536,6 @@ int do_guides(ParticleSettings *part, ListBase *effectors, ParticleKey *state, i
}
return 0;
}
-
static void do_path_effectors(ParticleSimulationData *sim, int i, ParticleCacheKey *ca, int k, int steps, float *UNUSED(rootco), float effector, float UNUSED(dfra), float UNUSED(cfra), float *length, float *vec)
{
float force[3] = {0.0f, 0.0f, 0.0f};
@@ -2068,53 +1735,26 @@ static void psys_task_init_path(ParticleTask *task, ParticleSimulationData *sim)
task->rng_path = BLI_rng_new(seed);
}
-/* note: this function must be thread safe, except for branching! */
-static void psys_thread_create_path(ParticleTask *task, struct ChildParticle *cpa, ParticleCacheKey *child_keys, int i)
+static void psys_calc_child_parent_weights(ParticleTask *task, struct ChildParticle *cpa,
+ float orco[3], float ornor[3], float hairmat[4][4], int *cpa_num, float **cpa_fuv, short *cpa_from,
+ ParticleCacheKey *key[4], float weight[4], float offset[4][3])
{
ParticleThreadContext *ctx = task->ctx;
Object *ob = ctx->sim.ob;
ParticleSystem *psys = ctx->sim.psys;
ParticleSettings *part = psys->part;
- ParticleCacheKey **cache = psys->childcache;
ParticleCacheKey **pcache = psys_in_edit_mode(ctx->sim.scene, psys) && psys->edit ? psys->edit->pathcache : psys->pathcache;
- ParticleCacheKey *child, *key[4];
- ParticleTexture ptex;
- float *cpa_fuv = 0, *par_rot = 0, rot[4];
- float orco[3], ornor[3], hairmat[4][4], dvec[3], off1[4][3], off2[4][3];
- float eff_length, eff_vec[3], weight[4];
- int k, cpa_num;
- short cpa_from;
-
- if (!pcache)
- return;
-
+
if (ctx->between) {
ParticleData *pa = psys->particles + cpa->pa[0];
- int w, needupdate;
- float foffset, wsum = 0.f;
+ int w;
+ float wsum = 0.f;
float co[3];
float p_min = part->parting_min;
float p_max = part->parting_max;
/* Virtual parents don't work nicely with parting. */
float p_fac = part->parents > 0.f ? 0.f : part->parting_fac;
- if (ctx->editupdate) {
- needupdate = 0;
- w = 0;
- while (w < 4 && cpa->pa[w] >= 0) {
- if (psys->edit->points[cpa->pa[w]].flag & PEP_EDIT_RECALC) {
- needupdate = 1;
- break;
- }
- w++;
- }
-
- if (!needupdate)
- return;
- else
- memset(child_keys, 0, sizeof(*child_keys) * (ctx->segments + 1));
- }
-
/* get parent paths */
for (w = 0; w < 4; w++) {
if (cpa->pa[w] >= 0) {
@@ -2128,9 +1768,9 @@ static void psys_thread_create_path(ParticleTask *task, struct ChildParticle *cp
}
/* modify weights to create parting */
- if (p_fac > 0.f) {
+ if (p_fac > 0.f && key[0]->segments != -1) {
for (w = 0; w < 4; w++) {
- if (w && weight[w] > 0.f) {
+ if (w && weight[w] > 0.f && key[w]->segments != -1) {
float d;
if (part->flag & PART_CHILD_LONG_HAIR) {
/* For long hair use tip distance/root distance as parting factor instead of root to tip angle. */
@@ -2168,46 +1808,83 @@ static void psys_thread_create_path(ParticleTask *task, struct ChildParticle *cp
}
/* get the original coordinates (orco) for texture usage */
- cpa_num = cpa->num;
-
- foffset = cpa->foffset;
- cpa_fuv = cpa->fuv;
- cpa_from = PART_FROM_FACE;
+ *cpa_from = PART_FROM_FACE;
+ *cpa_num = cpa->num;
+ *cpa_fuv = cpa->fuv;
- psys_particle_on_emitter(ctx->sim.psmd, cpa_from, cpa_num, DMCACHE_ISCHILD, cpa->fuv, foffset, co, ornor, 0, 0, orco, 0);
+ psys_particle_on_emitter(ctx->sim.psmd, *cpa_from, *cpa_num, DMCACHE_ISCHILD, *cpa_fuv, cpa->foffset, co, ornor, 0, 0, orco, 0);
mul_m4_v3(ob->obmat, co);
for (w = 0; w < 4; w++)
- sub_v3_v3v3(off1[w], co, key[w]->co);
+ sub_v3_v3v3(offset[w], co, key[w]->co);
psys_mat_hair_to_global(ob, ctx->sim.psmd->dm, psys->part->from, pa, hairmat);
}
else {
ParticleData *pa = psys->particles + cpa->parent;
float co[3];
- if (ctx->editupdate) {
- if (!(psys->edit->points[cpa->parent].flag & PEP_EDIT_RECALC))
- return;
-
- memset(child_keys, 0, sizeof(*child_keys) * (ctx->segments + 1));
- }
/* get the parent path */
key[0] = pcache[cpa->parent];
/* get the original coordinates (orco) for texture usage */
- cpa_from = part->from;
- cpa_num = pa->num;
+ *cpa_from = part->from;
+ *cpa_num = pa->num;
/* XXX hack to avoid messed up particle num and subsequent crash (#40733) */
- if (cpa_num > ctx->sim.psmd->dm->getNumTessFaces(ctx->sim.psmd->dm))
- cpa_num = 0;
- cpa_fuv = pa->fuv;
+ if (*cpa_num > ctx->sim.psmd->dm->getNumTessFaces(ctx->sim.psmd->dm))
+ *cpa_num = 0;
+ *cpa_fuv = pa->fuv;
- psys_particle_on_emitter(ctx->sim.psmd, cpa_from, cpa_num, DMCACHE_ISCHILD, cpa_fuv, pa->foffset, co, ornor, 0, 0, orco, 0);
+ psys_particle_on_emitter(ctx->sim.psmd, *cpa_from, *cpa_num, DMCACHE_ISCHILD, *cpa_fuv, pa->foffset, co, ornor, 0, 0, orco, 0);
psys_mat_hair_to_global(ob, ctx->sim.psmd->dm, psys->part->from, pa, hairmat);
}
+}
+
+/* note: this function must be thread safe, except for branching! */
+static void psys_thread_create_path(ParticleTask *task, struct ChildParticle *cpa, ParticleCacheKey *child_keys, int i)
+{
+ ParticleThreadContext *ctx = task->ctx;
+ ParticleSystem *psys = ctx->sim.psys;
+ ParticleSettings *part = psys->part;
+ ParticleCacheKey **cache = psys->childcache;
+ ParticleCacheKey **pcache = psys_in_edit_mode(ctx->sim.scene, psys) && psys->edit ? psys->edit->pathcache : psys->pathcache;
+ ParticleCacheKey *child, *key[4];
+ ParticleTexture ptex;
+ float *cpa_fuv = 0, *par_rot = 0, rot[4];
+ float orco[3], ornor[3], hairmat[4][4], dvec[3], off1[4][3], off2[4][3];
+ float eff_length, eff_vec[3], weight[4];
+ int k, cpa_num;
+ short cpa_from;
+
+ if (!pcache)
+ return;
+
+ if (ctx->editupdate) {
+ bool need_update;
+
+ if (ctx->between) {
+ int w;
+ need_update = false;
+ for (w = 0; w < 4 && cpa->pa[w] >= 0; ++w) {
+ if (psys->edit->points[cpa->pa[w]].flag & PEP_EDIT_RECALC) {
+ need_update = true;
+ break;
+ }
+ }
+ }
+ else {
+ need_update = psys->edit->points[cpa->parent].flag & PEP_EDIT_RECALC;
+ }
+
+ if (need_update)
+ memset(child_keys, 0, sizeof(*child_keys) * (ctx->segments + 1));
+ else
+ return;
+ }
+
+ psys_calc_child_parent_weights(task, cpa, orco, ornor, hairmat, &cpa_num, &cpa_fuv, &cpa_from, key, weight, off1);
child_keys->segments = ctx->segments;
@@ -2221,6 +1898,8 @@ static void psys_thread_create_path(ParticleTask *task, struct ChildParticle *cp
/* create the child path */
for (k = 0, child = child_keys; k <= ctx->segments; k++, child++) {
+ child->hull_parent = cpa->hull_parent;
+
if (ctx->between) {
int w = 0;
@@ -2453,11 +2132,11 @@ void psys_cache_paths(ParticleSimulationData *sim, float cfra)
ParticleSystem *psys = sim->psys;
ParticleSettings *part = psys->part;
ParticleCacheKey *ca, **cache;
+ const bool keyed = psys->flag & PSYS_KEYED;
+ const bool baked = psys->pointcache->mem_cache.first && psys->part->type != PART_HAIR;
DerivedMesh *hair_dm = (psys->part->type == PART_HAIR && psys->flag & PSYS_HAIR_DYNAMICS) ? psys->hair_out_dm : NULL;
- ParticleKey result;
-
Material *ma;
ParticleInterpolationData pind;
ParticleTexture ptex;
@@ -2475,7 +2154,6 @@ void psys_cache_paths(ParticleSimulationData *sim, float cfra)
float length, vec[3];
float *vg_effector = NULL;
float *vg_length = NULL, pa_length = 1.0f;
- int keyed, baked;
/* we don't have anything valid to create paths from so let's quit here */
if ((psys->flag & PSYS_HAIR_DONE || psys->flag & PSYS_KEYED || psys->pointcache) == 0)
@@ -2485,9 +2163,6 @@ void psys_cache_paths(ParticleSimulationData *sim, float cfra)
if (psys->renderdata == 0 && (psys->edit == NULL || pset->flag & PE_DRAW_PART) == 0)
return;
- keyed = psys->flag & PSYS_KEYED;
- baked = psys->pointcache->mem_cache.first && psys->part->type != PART_HAIR;
-
/* clear out old and create new empty path cache */
psys_free_path_cache(psys, psys->edit);
cache = psys->pathcache = psys_alloc_path_cache_buffers(&psys->pathcachebufs, totpart, segments + 1);
@@ -2557,6 +2232,7 @@ void psys_cache_paths(ParticleSimulationData *sim, float cfra)
/*--interpolate actual path from data points--*/
for (k = 0, ca = cache[p]; k <= segments; k++, ca++) {
+ ParticleKey result;
time = (float)k / (float)segments;
t = birthtime + time * (dietime - birthtime);
result.time = -t;
@@ -2573,14 +2249,6 @@ void psys_cache_paths(ParticleSimulationData *sim, float cfra)
copy_v3_v3(ca->col, col);
}
- if (part->type == PART_HAIR) {
- HairKey *hkey;
-
- for (k = 0, hkey = pa->hair; k < pa->totkey; ++k, ++hkey) {
- mul_v3_m4v3(hkey->world_co, hairmat, hkey->co);
- }
- }
-
/*--modify paths and calculate rotation & velocity--*/
if (!(psys->flag & PSYS_GLOBAL_HAIR)) {
@@ -2965,7 +2633,7 @@ static void psys_face_mat(Object *ob, DerivedMesh *dm, ParticleData *pa, float m
triatomat(v[0], v[1], v[2], (osface) ? osface->uv : NULL, mat);
}
-void psys_mat_hair_to_object(Object *UNUSED(ob), DerivedMesh *dm, short from, ParticleData *pa, float hairmat[4][4])
+void psys_mat_hair_to_object(Object *ob, DerivedMesh *dm, short from, ParticleData *pa, float hairmat[4][4])
{
float vec[3];
@@ -2975,7 +2643,7 @@ void psys_mat_hair_to_object(Object *UNUSED(ob), DerivedMesh *dm, short from, Pa
return;
}
- psys_face_mat(0, dm, pa, hairmat, 0);
+ psys_face_mat(ob, dm, pa, hairmat, 0);
psys_particle_on_dm(dm, from, pa->num, pa->num_dmcache, pa->fuv, pa->foffset, vec, 0, 0, 0, 0, 0);
copy_v3_v3(hairmat[3], vec);
}
@@ -3011,6 +2679,25 @@ void psys_mat_hair_to_global(Object *ob, DerivedMesh *dm, short from, ParticleDa
mul_m4_m4m4(hairmat, ob->obmat, facemat);
}
+void psys_child_mat_to_object(Object *ob, ParticleSystem *psys, ParticleSystemModifierData *psmd, ChildParticle *cpa, float hairmat[4][4])
+{
+ const bool between = (psys->part->childtype == PART_CHILD_FACES);
+ float co[3];
+
+ if (between) {
+ ParticleData *pa = &psys->particles[cpa->pa[0]];
+ psys_particle_on_emitter(psmd, PART_FROM_FACE, cpa->num, DMCACHE_ISCHILD, cpa->fuv, cpa->foffset, co, NULL, NULL, NULL, NULL, NULL);
+ psys_mat_hair_to_object(ob, psmd->dm, psys->part->from, pa, hairmat);
+ }
+ else {
+ ParticleData *pa = &psys->particles[cpa->parent];
+ psys_particle_on_emitter(psmd, psys->part->from, pa->num, pa->num_dmcache, pa->fuv, pa->foffset, co, NULL, NULL, NULL, NULL, NULL);
+ psys_mat_hair_to_object(ob, psmd->dm, psys->part->from, pa, hairmat);
+ }
+
+ copy_v3_v3(hairmat[3], co);
+}
+
/************************************************/
/* ParticleSettings handling */
/************************************************/
@@ -3032,6 +2719,8 @@ ModifierData *object_add_particle_system(Scene *scene, Object *ob, const char *n
BLI_addtail(&ob->particlesystem, psys);
psys->part = psys_new_settings(DATA_("ParticleSettings"), NULL);
+ psys->key = BKE_key_add_particles(ob, psys);
+ psys->key->type = KEY_RELATIVE;
if (BLI_listbase_count_ex(&ob->particlesystem, 2) > 1)
BLI_snprintf(psys->name, sizeof(psys->name), DATA_("ParticleSystem %i"), BLI_listbase_count(&ob->particlesystem));
@@ -3054,6 +2743,8 @@ ModifierData *object_add_particle_system(Scene *scene, Object *ob, const char *n
psys->flag = PSYS_CURRENT;
psys->cfra = BKE_scene_frame_get_from_ctime(scene, CFRA + 1);
+ psys->hair_preview_factor = 100.0f;
+
DAG_relations_tag_update(G.main);
DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
@@ -3095,7 +2786,7 @@ void object_remove_particle_system(Scene *UNUSED(scene), Object *ob)
if (ob->particlesystem.first)
((ParticleSystem *) ob->particlesystem.first)->flag |= PSYS_CURRENT;
else
- ob->mode &= ~OB_MODE_PARTICLE_EDIT;
+ ob->mode &= ~(OB_MODE_PARTICLE_EDIT | OB_MODE_HAIR_EDIT);
DAG_relations_tag_update(G.main);
DAG_id_tag_update(&ob->id, OB_RECALC_DATA);
@@ -3372,6 +3063,11 @@ static int get_particle_uv(DerivedMesh *dm, ParticleData *pa, int face_index, co
pvalue = texture_value_blend(def, pvalue, value, texfac, blend); \
} (void)0
+#define SET_PARTICLE_TEXTURE_RGB(type, prgb, texfac) \
+ if ((event & mtex->mapto) & type) { \
+ texture_rgb_blend(prgb, rgba, prgb, value, texfac, blend); \
+ } (void)0
+
#define CLAMP_PARTICLE_TEXTURE_POS(type, pvalue) \
if (event & type) { \
if (pvalue < 0.0f) \
@@ -3384,6 +3080,21 @@ static int get_particle_uv(DerivedMesh *dm, ParticleData *pa, int face_index, co
CLAMP(pvalue, -1.0f, 1.0f); \
} (void)0
+#define CLAMP_PARTICLE_TEXTURE_RGB(type, prgb) \
+ if (event & type) { \
+ CLAMP3(prgb, 0.0f, 1.0f); \
+ } (void)0
+
+/* actual usable texco mode for particles */
+BLI_INLINE int particle_texco(ParticleSettings *part, MTex *mtex)
+{
+ short texco = mtex->texco;
+ if (ELEM(texco, TEXCO_UV, TEXCO_ORCO) &&
+ (!ELEM(part->from, PART_FROM_FACE, PART_FROM_VOLUME) || part->distr == PART_DISTR_GRID))
+ texco = TEXCO_GLOB;
+ return texco;
+}
+
static void get_cpa_texture(DerivedMesh *dm, ParticleSystem *psys, ParticleSettings *part, ParticleData *par, int child_index, int face_index, const float fw[4], float *orco, ParticleTexture *ptex, int event, float cfra)
{
MTex *mtex, **mtexp = part->mtex;
@@ -3393,6 +3104,7 @@ static void get_cpa_texture(DerivedMesh *dm, ParticleSystem *psys, ParticleSetti
ptex->ivel = ptex->life = ptex->exist = ptex->size = ptex->damp =
ptex->gravity = ptex->field = ptex->time = ptex->clump = ptex->kink_freq = ptex->kink_amp =
ptex->effector = ptex->rough1 = ptex->rough2 = ptex->roughe = 1.0f;
+ ptex->color[0] = ptex->color[1] = ptex->color[2] = 1.0f;
ptex->length = 1.0f - part->randlength * psys_frand(psys, child_index + 26);
ptex->length *= part->clength_thres < psys_frand(psys, child_index + 27) ? part->clength : 1.0f;
@@ -3402,10 +3114,7 @@ static void get_cpa_texture(DerivedMesh *dm, ParticleSystem *psys, ParticleSetti
if (mtex && mtex->tex && mtex->mapto) {
float def = mtex->def_var;
short blend = mtex->blendtype;
- short texco = mtex->texco;
-
- if (ELEM(texco, TEXCO_UV, TEXCO_ORCO) && (ELEM(part->from, PART_FROM_FACE, PART_FROM_VOLUME) == 0 || part->distr == PART_DISTR_GRID))
- texco = TEXCO_GLOB;
+ short texco = particle_texco(part, mtex);
switch (texco) {
case TEXCO_GLOB:
@@ -3441,6 +3150,7 @@ static void get_cpa_texture(DerivedMesh *dm, ParticleSystem *psys, ParticleSetti
SET_PARTICLE_TEXTURE(PAMAP_KINK_AMP, ptex->kink_amp, mtex->kinkampfac);
SET_PARTICLE_TEXTURE(PAMAP_KINK_FREQ, ptex->kink_freq, mtex->kinkfac);
SET_PARTICLE_TEXTURE(PAMAP_DENS, ptex->exist, mtex->padensfac);
+ SET_PARTICLE_TEXTURE_RGB(PAMAP_COLOR, ptex->color, mtex->pacolfac);
}
}
@@ -3450,71 +3160,87 @@ static void get_cpa_texture(DerivedMesh *dm, ParticleSystem *psys, ParticleSetti
CLAMP_PARTICLE_TEXTURE_POS(PAMAP_KINK_FREQ, ptex->kink_freq);
CLAMP_PARTICLE_TEXTURE_POS(PAMAP_ROUGH, ptex->rough1);
CLAMP_PARTICLE_TEXTURE_POS(PAMAP_DENS, ptex->exist);
+ CLAMP_PARTICLE_TEXTURE_RGB(PAMAP_COLOR, ptex->color);
}
-void psys_get_texture(ParticleSimulationData *sim, ParticleData *pa, ParticleTexture *ptex, int event, float cfra)
+
+bool particle_mtex_eval(ParticleSimulationData *sim, MTex *mtex, ParticleData *pa, float cfra, float *value, float rgba[4])
{
Object *ob = sim->ob;
Mesh *me = (Mesh *)ob->data;
ParticleSettings *part = sim->psys->part;
+ short texco;
+
+ float texvec[3];
+
+ if (!(mtex->tex && mtex->mapto))
+ return false;
+
+ texco = particle_texco(part, mtex);
+ switch (texco) {
+ case TEXCO_GLOB:
+ copy_v3_v3(texvec, pa->state.co);
+ break;
+ case TEXCO_OBJECT:
+ copy_v3_v3(texvec, pa->state.co);
+ if (mtex->object)
+ mul_m4_v3(mtex->object->imat, texvec);
+ break;
+ case TEXCO_UV:
+ if (get_particle_uv(sim->psmd->dm, pa, 0, pa->fuv, mtex->uvname, texvec))
+ break;
+ /* no break, failed to get uv's, so let's try orco's */
+ case TEXCO_ORCO: {
+ float co[3];
+
+ psys_particle_on_emitter(sim->psmd, sim->psys->part->from, pa->num, pa->num_dmcache, pa->fuv, pa->foffset, co, 0, 0, 0, texvec, 0);
+
+ if (me->bb == NULL || (me->bb->flag & BOUNDBOX_DIRTY)) {
+ BKE_mesh_texspace_calc(me);
+ }
+ sub_v3_v3(texvec, me->loc);
+ if (me->size[0] != 0.0f) texvec[0] /= me->size[0];
+ if (me->size[1] != 0.0f) texvec[1] /= me->size[1];
+ if (me->size[2] != 0.0f) texvec[2] /= me->size[2];
+ break;
+ }
+ case TEXCO_PARTICLE:
+ /* texture coordinates in range [-1, 1] */
+ texvec[0] = 2.f * (cfra - pa->time) / (pa->dietime - pa->time) - 1.f;
+ if (sim->psys->totpart > 0)
+ texvec[1] = 2.f * (float)(pa - sim->psys->particles) / (float)sim->psys->totpart - 1.f;
+ else
+ texvec[1] = 0.0f;
+ texvec[2] = 0.f;
+ break;
+ }
+
+ externtex(mtex, texvec, value, rgba, rgba + 1, rgba + 2, rgba + 3, 0, NULL, false);
+
+ return true;
+}
+
+void psys_get_texture(ParticleSimulationData *sim, ParticleData *pa, ParticleTexture *ptex, int event, float cfra)
+{
+ ParticleSettings *part = sim->psys->part;
MTex **mtexp = part->mtex;
MTex *mtex;
int m;
- float value, rgba[4], co[3], texvec[3];
+ float value, rgba[4];
int setvars = 0;
/* initialize ptex */
ptex->ivel = ptex->life = ptex->exist = ptex->size = ptex->damp =
ptex->gravity = ptex->field = ptex->length = ptex->clump = ptex->kink_freq = ptex->kink_amp =
ptex->effector = ptex->rough1 = ptex->rough2 = ptex->roughe = 1.0f;
+ ptex->color[0] = ptex->color[1] = ptex->color[2] = 1.0f;
ptex->time = (float)(pa - sim->psys->particles) / (float)sim->psys->totpart;
for (m = 0; m < MAX_MTEX; m++, mtexp++) {
mtex = *mtexp;
- if (mtex && mtex->tex && mtex->mapto) {
+ if (mtex && particle_mtex_eval(sim, mtex, pa, cfra, &value, rgba)) {
float def = mtex->def_var;
short blend = mtex->blendtype;
- short texco = mtex->texco;
-
- if (texco == TEXCO_UV && (ELEM(part->from, PART_FROM_FACE, PART_FROM_VOLUME) == 0 || part->distr == PART_DISTR_GRID))
- texco = TEXCO_GLOB;
-
- switch (texco) {
- case TEXCO_GLOB:
- copy_v3_v3(texvec, pa->state.co);
- break;
- case TEXCO_OBJECT:
- copy_v3_v3(texvec, pa->state.co);
- if (mtex->object)
- mul_m4_v3(mtex->object->imat, texvec);
- break;
- case TEXCO_UV:
- if (get_particle_uv(sim->psmd->dm, pa, 0, pa->fuv, mtex->uvname, texvec))
- break;
- /* no break, failed to get uv's, so let's try orco's */
- case TEXCO_ORCO:
- psys_particle_on_emitter(sim->psmd, sim->psys->part->from, pa->num, pa->num_dmcache, pa->fuv, pa->foffset, co, 0, 0, 0, texvec, 0);
-
- if (me->bb == NULL || (me->bb->flag & BOUNDBOX_DIRTY)) {
- BKE_mesh_texspace_calc(me);
- }
- sub_v3_v3(texvec, me->loc);
- if (me->size[0] != 0.0f) texvec[0] /= me->size[0];
- if (me->size[1] != 0.0f) texvec[1] /= me->size[1];
- if (me->size[2] != 0.0f) texvec[2] /= me->size[2];
- break;
- case TEXCO_PARTICLE:
- /* texture coordinates in range [-1, 1] */
- texvec[0] = 2.f * (cfra - pa->time) / (pa->dietime - pa->time) - 1.f;
- if (sim->psys->totpart > 0)
- texvec[1] = 2.f * (float)(pa - sim->psys->particles) / (float)sim->psys->totpart - 1.f;
- else
- texvec[1] = 0.0f;
- texvec[2] = 0.f;
- break;
- }
-
- externtex(mtex, texvec, &value, rgba, rgba + 1, rgba + 2, rgba + 3, 0, NULL, false);
if ((event & mtex->mapto) & PAMAP_TIME) {
/* the first time has to set the base value for time regardless of blend mode */
@@ -3536,6 +3262,7 @@ void psys_get_texture(ParticleSimulationData *sim, ParticleData *pa, ParticleTex
SET_PARTICLE_TEXTURE(PAMAP_GRAVITY, ptex->gravity, mtex->gravityfac);
SET_PARTICLE_TEXTURE(PAMAP_DAMP, ptex->damp, mtex->dampfac);
SET_PARTICLE_TEXTURE(PAMAP_LENGTH, ptex->length, mtex->lengthfac);
+ SET_PARTICLE_TEXTURE_RGB(PAMAP_COLOR, ptex->color, mtex->pacolfac);
}
}
@@ -3548,7 +3275,37 @@ void psys_get_texture(ParticleSimulationData *sim, ParticleData *pa, ParticleTex
CLAMP_PARTICLE_TEXTURE_POSNEG(PAMAP_GRAVITY, ptex->gravity);
CLAMP_PARTICLE_TEXTURE_POS(PAMAP_DAMP, ptex->damp);
CLAMP_PARTICLE_TEXTURE_POS(PAMAP_LENGTH, ptex->length);
+ CLAMP_PARTICLE_TEXTURE_RGB(PAMAP_COLOR, ptex->color);
}
+
+/* specialized texture eval for shapekey influences */
+float psys_get_texture_shapefac(ParticleSimulationData *sim, ParticleData *pa, float cfra, const char *shapekey)
+{
+ ParticleSettings *part = sim->psys->part;
+ MTex **mtexp = part->mtex;
+ MTex *mtex;
+ int m;
+ float value, rgba[4];
+
+ float result = 1.0f;
+
+ for (m = 0; m < MAX_MTEX; m++, mtexp++) {
+ mtex = *mtexp;
+ if (mtex && (mtex->mapto & PAMAP_SHAPEKEY) && STREQ(mtex->shapekey, shapekey)) {
+ float def = mtex->def_var;
+ short blend = mtex->blendtype;
+
+ if (particle_mtex_eval(sim, mtex, pa, cfra, &value, rgba)) {
+ result = texture_value_blend(def, result, value, mtex->shapefac, blend);
+ }
+ }
+ }
+
+ CLAMP(result, 0.0f, 1.0f);
+
+ return result;
+}
+
/************************************************/
/* Particle State */
/************************************************/
@@ -3628,6 +3385,35 @@ static void get_child_modifier_parameters(ParticleSettings *part, ParticleThread
if (ctx->vg_effector)
ptex->effector *= psys_interpolate_value_from_verts(ctx->dm, cpa_from, cpa_num, cpa_fuv, ctx->vg_effector);
}
+
+static void interpolate_pathcache(ParticleCacheKey *first, float t, ParticleCacheKey *result)
+{
+ int i = 0;
+ ParticleCacheKey *cur = first;
+
+ /* scale the requested time to fit the entire path even if the path is cut early */
+ t *= (first + first->segments)->time;
+
+ while (i < first->segments && cur->time < t)
+ cur++;
+
+ if (cur->time == t)
+ *result = *cur;
+ else {
+ float dt = (t - (cur - 1)->time) / (cur->time - (cur - 1)->time);
+ interp_v3_v3v3(result->co, (cur - 1)->co, cur->co, dt);
+ interp_v3_v3v3(result->vel, (cur - 1)->vel, cur->vel, dt);
+ interp_qt_qtqt(result->rot, (cur - 1)->rot, cur->rot, dt);
+ result->time = t;
+ }
+
+ /* first is actual base rotation, others are incremental from first */
+ if (cur == first || cur - 1 == first)
+ copy_qt_qt(result->rot, first->rot);
+ else
+ mul_qt_qtqt(result->rot, first->rot, result->rot);
+}
+
/* get's hair (or keyed) particles state at the "path time" specified in state->time */
void psys_get_particle_on_path(ParticleSimulationData *sim, int p, ParticleKey *state, const bool vel)
{