Simple hair children: Initial implementation of twist control
authorSergey Sharybin <sergey.vfx@gmail.com>
Wed, 14 Feb 2018 13:33:53 +0000 (14:33 +0100)
committerSergey Sharybin <sergey.vfx@gmail.com>
Thu, 15 Feb 2018 10:53:58 +0000 (11:53 +0100)
It allows to have children hair to be twisted around parent curve, which is
quite an essential feature when creating hair braids.

There are currently two controls:

- Number of turns around parent children.
- Influence curve, which allows to modify "twistness" along the strand.

12 files changed:
release/scripts/startup/bl_ui/properties_particle.py
source/blender/blenkernel/BKE_particle.h
source/blender/blenkernel/intern/particle.c
source/blender/blenkernel/intern/particle_child.c
source/blender/blenkernel/intern/particle_system.c
source/blender/blenkernel/intern/smoke.c
source/blender/blenkernel/particle_private.h
source/blender/blenloader/intern/readfile.c
source/blender/blenloader/intern/writefile.c
source/blender/editors/space_view3d/drawobject.c
source/blender/makesdna/DNA_particle_types.h
source/blender/makesrna/intern/rna_particle.c

index b01d1ebbfa33cdd2e77db4a76e24817206269c23..e552ff35ffacdbe756ca686a40f54bf8dcda9c04 100644 (file)
@@ -1198,6 +1198,12 @@ class PARTICLE_PT_children(ParticleButtonsPanel, Panel):
         col.label(text="Effects:")
 
         sub = col.column(align=True)
+        if part.child_type == 'SIMPLE':
+            sub.prop(part, "twist")
+            sub.prop(part, "use_twist_curve")
+            if part.use_twist_curve:
+                sub.template_curve_mapping(part, "twist_curve")
+
         sub.prop(part, "use_clump_curve")
         if part.use_clump_curve:
             sub.template_curve_mapping(part, "clump_curve")
index 2c72373c2a91207c816a15c8e3a19a0b087b69ca..747a6b65bb03155b0b0d580ce109cf641a68bbe4 100644 (file)
@@ -162,6 +162,7 @@ typedef struct ParticleThreadContext {
 
        struct CurveMapping *clumpcurve;
        struct CurveMapping *roughcurve;
+       struct CurveMapping *twistcurve;
 } ParticleThreadContext;
 
 typedef struct ParticleTask {
@@ -349,6 +350,7 @@ int psys_get_particle_state(struct ParticleSimulationData *sim, int p, struct Pa
 /* child paths */
 void BKE_particlesettings_clump_curve_init(struct ParticleSettings *part);
 void BKE_particlesettings_rough_curve_init(struct ParticleSettings *part);
+void BKE_particlesettings_twist_curve_init(struct ParticleSettings *part);
 void psys_apply_child_modifiers(struct ParticleThreadContext *ctx, struct ListBase *modifiers,
                                 struct ChildParticle *cpa, struct ParticleTexture *ptex, const float orco[3], const float ornor[3], float hairmat[4][4],
                                 struct ParticleCacheKey *keys, struct ParticleCacheKey *parent_keys, const float parent_orco[3]);
index 8aa7b167b85b4f0d2e533b6a1ceeb06ea8f001d3..ea27e23ed2f7e43f0e9a9c88ce0ded87c8535730 100644 (file)
@@ -405,6 +405,8 @@ void BKE_particlesettings_free(ParticleSettings *part)
                curvemapping_free(part->clumpcurve);
        if (part->roughcurve)
                curvemapping_free(part->roughcurve);
+       if (part->twistcurve)
+               curvemapping_free(part->twistcurve);
        
        free_partdeflect(part->pd);
        free_partdeflect(part->pd2);
@@ -2152,6 +2154,13 @@ static bool psys_thread_context_init_path(
        else {
                ctx->roughcurve = NULL;
        }
+       if ((part->child_flag & PART_CHILD_USE_TWIST_CURVE) && part->twistcurve) {
+               ctx->twistcurve = curvemapping_copy(part->twistcurve);
+               curvemapping_changed_all(ctx->twistcurve);
+       }
+       else {
+               ctx->twistcurve = NULL;
+       }
 
        return true;
 }
@@ -3337,6 +3346,18 @@ void BKE_particlesettings_rough_curve_init(ParticleSettings *part)
        part->roughcurve = cumap;
 }
 
+void BKE_particlesettings_twist_curve_init(ParticleSettings *part)
+{
+       CurveMapping *cumap = curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f);
+
+       cumap->cm[0].curve[0].x = 0.0f;
+       cumap->cm[0].curve[0].y = 1.0f;
+       cumap->cm[0].curve[1].x = 1.0f;
+       cumap->cm[0].curve[1].y = 1.0f;
+
+       part->twistcurve = cumap;
+}
+
 /**
  * Only copy internal data of ParticleSettings ID from source to already allocated/initialized destination.
  * You probably nerver want to use that directly, use id_copy or BKE_id_copy_ex for typical needs.
@@ -3359,6 +3380,9 @@ void BKE_particlesettings_copy_data(
        if (part_src->roughcurve) {
                part_dst->roughcurve = curvemapping_copy(part_src->roughcurve);
        }
+       if (part_src->twistcurve) {
+               part_dst->twistcurve = curvemapping_copy(part_src->twistcurve);
+       }
 
        part_dst->boids = boid_copy_settings(part_src->boids);
 
@@ -3935,6 +3959,7 @@ void psys_get_particle_on_path(ParticleSimulationData *sim, int p, ParticleKey *
                        modifier_ctx.par_vel = par->vel;
                        modifier_ctx.par_rot = par->rot;
                        modifier_ctx.par_orco = par_orco;
+                       modifier_ctx.parent_keys = psys->childcache ? psys->childcache[p - totpart] : NULL;
                        do_child_modifiers(&modifier_ctx, hairmat, state, t);
 
                        /* try to estimate correct velocity */
@@ -4048,6 +4073,7 @@ int psys_get_particle_state(ParticleSimulationData *sim, int p, ParticleKey *sta
                        modifier_ctx.par_vel = key1->vel;
                        modifier_ctx.par_rot = key1->rot;
                        modifier_ctx.par_orco = par_orco;
+                       modifier_ctx.parent_keys = psys->childcache ? psys->childcache[p - totpart] : NULL;
 
                        do_child_modifiers(&modifier_ctx, mat, state, t);
 
index 70e635e16cffe23d8bed52f85cfa84301203b14d..53bf577d4f4d5a26b18f0ad26f2ae3c3bd6386b4 100644 (file)
@@ -242,6 +242,7 @@ static void do_kink_spiral(ParticleThreadContext *ctx, ParticleTexture *ptex, co
        modifier_ctx.ptex = ptex;
        modifier_ctx.cpa = cpa;
        modifier_ctx.orco = orco;
+       modifier_ctx.parent_keys = parent_keys;
 
        for (k = 0, key = keys; k < end_index; k++, key++) {
                float par_time;
@@ -352,6 +353,7 @@ void psys_apply_child_modifiers(ParticleThreadContext *ctx, struct ListBase *mod
                modifier_ctx.ptex = ptex;
                modifier_ctx.cpa = cpa;
                modifier_ctx.orco = orco;
+               modifier_ctx.parent_keys = parent_keys;
 
                totkeys = ctx->segments + 1;
                max_length = ptex->length;
@@ -688,6 +690,89 @@ static void do_rough_curve(const float loc[3], float mat[4][4], float time, floa
        madd_v3_v3fl(state->co, mat[2], fac * rough[2]);
 }
 
+static int twist_num_segments(const ParticleChildModifierContext *modifier_ctx)
+{
+       ParticleThreadContext *thread_ctx = modifier_ctx->thread_ctx;
+       return (thread_ctx != NULL) ? thread_ctx->segments
+                                   : modifier_ctx->sim->psys->part->draw_step;
+}
+
+static void twist_get_axis(const ParticleChildModifierContext *modifier_ctx,
+                           const float time, float r_axis[3])
+{
+       const int num_segments = twist_num_segments(modifier_ctx);
+       const int index = clamp_i(time * num_segments, 0, num_segments);
+       if (index > 0) {
+               sub_v3_v3v3(r_axis,
+                           modifier_ctx->parent_keys[index].co,
+                           modifier_ctx->parent_keys[index - 1].co);
+       }
+       else {
+               sub_v3_v3v3(r_axis,
+                           modifier_ctx->parent_keys[index + 1].co,
+                           modifier_ctx->parent_keys[index].co);
+       }
+}
+
+static float curvemapping_integrate_clamped(CurveMapping *curve,
+                                            float start, float end, float step)
+{
+       float integral = 0.0f;
+       float x = start;
+       while (x < end) {
+               float y = curvemapping_evaluateF(curve, 0, x);
+               y = clamp_f(y, 0.0f, 1.0f);
+               /* TODO(sergey): Clamp last step to end. */
+               integral += y * step;
+               x += step;
+       }
+       return integral;
+}
+
+static void do_twist(const ParticleChildModifierContext *modifier_ctx,
+                     ParticleKey *state, const float time)
+{
+       ParticleThreadContext *thread_ctx = modifier_ctx->thread_ctx;
+       ParticleSimulationData *sim = modifier_ctx->sim;
+       ParticleSettings *part = sim->psys->part;
+       /* Early output checks. */
+       if (part->childtype != PART_CHILD_PARTICLES) {
+               /* Interpolated children behave weird with twist. */
+               return;
+       }
+       if (part->twist == 0.0f) {
+               /* No twist along the strand.  */
+               return;
+       }
+       /* Dependent on whether it's threaded update or not, curve comes
+        * from different places.
+        */
+       CurveMapping *twist_curve = NULL;
+       if (part->child_flag & PART_CHILD_USE_TWIST_CURVE) {
+               twist_curve = (thread_ctx != NULL) ? thread_ctx->twistcurve
+                                                  : part->twistcurve;
+       }
+       /* Axis of rotation. */
+       float axis[3];
+       twist_get_axis(modifier_ctx, time, axis);
+       /* Angle of rotation. */
+       float angle = part->twist;
+       if (twist_curve != NULL) {
+               const int num_segments = twist_num_segments(modifier_ctx);
+               angle *= curvemapping_integrate_clamped(twist_curve,
+                                                       0.0f, time,
+                                                       1.0f / num_segments);
+       }
+       else {
+               angle *= time;
+       }
+       /* Perform rotation around parent curve. */
+       float vec[3];
+       sub_v3_v3v3(vec, state->co, modifier_ctx->par_co);
+       rotate_v3_v3v3fl(state->co, vec, axis, angle * 2.0f * M_PI);
+       add_v3_v3(state->co, modifier_ctx->par_co);
+}
+
 void do_child_modifiers(const ParticleChildModifierContext *modifier_ctx,
                         float mat[4][4], ParticleKey *state, float t)
 {
@@ -723,6 +808,8 @@ void do_child_modifiers(const ParticleChildModifierContext *modifier_ctx,
                rough_end *= ptex->roughe;
        }
 
+       do_twist(modifier_ctx, state, t);
+
        if (part->flag & PART_CHILD_EFFECT)
                /* state is safe to cast, since only co and vel are used */
                guided = do_guides(sim->psys->part, sim->psys->effectors, (ParticleKey *)state, cpa->parent, t);
index dfc732dcb7448e818f60e6e7ed474619514c5836..bae76354705ce709a43ad6baace318181e1c36f1 100644 (file)
@@ -519,6 +519,9 @@ void psys_thread_context_free(ParticleThreadContext *ctx)
        if (ctx->roughcurve != NULL) {
                curvemapping_free(ctx->roughcurve);
        }
+       if (ctx->twistcurve != NULL) {
+               curvemapping_free(ctx->twistcurve);
+       }
 }
 
 static void initialize_particle_texture(ParticleSimulationData *sim, ParticleData *pa, int p)
index 3cf98b284b1169acb5a6afc979747838d25f6714..34ff9a155b45b1f36b99df06329c913977297dc0 100644 (file)
@@ -1285,6 +1285,8 @@ static void emit_from_particles(
                        curvemapping_changed_all(psys->part->clumpcurve);
                if ((psys->part->child_flag & PART_CHILD_USE_ROUGH_CURVE) && psys->part->roughcurve)
                        curvemapping_changed_all(psys->part->roughcurve);
+               if ((psys->part->child_flag & PART_CHILD_USE_TWIST_CURVE) && psys->part->twistcurve)
+                       curvemapping_changed_all(psys->part->twistcurve);
 
                /* initialize particle cache */
                if (psys->part->type == PART_HAIR) {
index 00a7637e246935e7aa73b6bd65064e8dffc87442..2189527118b2f2f29a9b71bb27e2a1bda1f1cfa4 100644 (file)
@@ -41,6 +41,7 @@ typedef struct ParticleChildModifierContext {
        const float *par_rot;   /* float4 */
        const float *par_orco;  /* float3 */
        const float *orco;      /* float3 */
+       ParticleCacheKey *parent_keys;
 } ParticleChildModifierContext;
 
 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,
index 2a25de121b37e638d68deac0a53973a3ac945d28..9aa67e8651f5a03c16b0231b8c599384cdbe0822 100644 (file)
@@ -4253,6 +4253,9 @@ static void direct_link_particlesettings(FileData *fd, ParticleSettings *part)
        part->roughcurve = newdataadr(fd, part->roughcurve);
        if (part->roughcurve)
                direct_link_curvemapping(fd, part->roughcurve);
+       part->twistcurve = newdataadr(fd, part->twistcurve);
+       if (part->twistcurve)
+               direct_link_curvemapping(fd, part->twistcurve);
 
        part->effector_weights = newdataadr(fd, part->effector_weights);
        if (!part->effector_weights)
index ceb1233596a05444b440c980fcdb841dd497749f..5941f96041c2a1d1de3e811dfdf4b21e72443e8d 100644 (file)
@@ -1304,6 +1304,9 @@ static void write_particlesettings(WriteData *wd, ParticleSettings *part)
                if (part->roughcurve) {
                        write_curvemapping(wd, part->roughcurve);
                }
+               if (part->twistcurve) {
+                       write_curvemapping(wd, part->twistcurve);
+               }
 
                for (ParticleDupliWeight *dw = part->dupliweights.first; dw; dw = dw->next) {
                        /* update indices, but only if dw->ob is set (can be NULL after loading e.g.) */
index 828617d14470d245e498ec496a0b7316eadb8657..c3823627009cc98c06911d27c887e0f5c0b74bdc 100644 (file)
@@ -5044,6 +5044,8 @@ static void draw_new_particle_system(Scene *scene, View3D *v3d, RegionView3D *rv
                curvemapping_changed_all(psys->part->clumpcurve);
        if ((psys->part->child_flag & PART_CHILD_USE_ROUGH_CURVE) && psys->part->roughcurve)
                curvemapping_changed_all(psys->part->roughcurve);
+       if ((psys->part->child_flag & PART_CHILD_USE_TWIST_CURVE) && psys->part->twistcurve)
+               curvemapping_changed_all(psys->part->twistcurve);
 
 /* 2. */
        sim.scene = scene;
index f6bed37dfa284c886ac27edc4fc40a24c9f71f09..371b53567a66b2453624129db15ffbc845813491 100644 (file)
@@ -261,6 +261,10 @@ typedef struct ParticleSettings {
        short use_modifier_stack;
        short pad5[3];
 
+       float twist;
+       float pad6;
+       struct CurveMapping *twistcurve;
+       void *pad7;
 } ParticleSettings;
 
 typedef struct ParticleSystem {
@@ -435,6 +439,7 @@ typedef enum eParticleChildFlag {
        PART_CHILD_USE_CLUMP_NOISE  = (1<<0),
        PART_CHILD_USE_CLUMP_CURVE  = (1<<1),
        PART_CHILD_USE_ROUGH_CURVE  = (1<<2),
+       PART_CHILD_USE_TWIST_CURVE  = (1<<3),
 } eParticleChildFlag;
 
 /* part->draw_col */
index 9e52457d32a79a29b1bb0aef5636baacfb0aafa4..e9bbc981a1342223382a18bccd6b0f7c30fb433b 100644 (file)
@@ -944,6 +944,19 @@ static void rna_ParticleSettings_use_roughness_curve_update(Main *bmain, Scene *
        rna_Particle_redo_child(bmain, scene, ptr);
 }
 
+static void rna_ParticleSettings_use_twist_curve_update(Main *bmain, Scene *scene, PointerRNA *ptr)
+{
+       ParticleSettings *part = ptr->data;
+
+       if (part->child_flag & PART_CHILD_USE_TWIST_CURVE) {
+               if (!part->twistcurve) {
+                       BKE_particlesettings_twist_curve_init(part);
+               }
+       }
+
+       rna_Particle_redo_child(bmain, scene, ptr);
+}
+
 static void rna_ParticleSystem_name_set(PointerRNA *ptr, const char *value)
 {
        Object *ob = ptr->id.data;
@@ -3170,6 +3183,25 @@ static void rna_def_particle_settings(BlenderRNA *brna)
        RNA_def_property_struct_type(prop, "FieldSettings");
        RNA_def_property_pointer_funcs(prop, "rna_Particle_field2_get", NULL, NULL, NULL);
        RNA_def_property_ui_text(prop, "Force Field 2", "");
+
+       /* twist */
+       prop = RNA_def_property(srna, "twist", PROP_FLOAT, PROP_NONE);
+       RNA_def_property_range(prop, -100000.0f, 100000.0f);
+       RNA_def_property_ui_range(prop, -10.0f, 10.0f, 0.1, 3);
+       RNA_def_property_ui_text(prop, "Twist", "Number of turns around parent allong the strand");
+       RNA_def_property_update(prop, 0, "rna_Particle_redo_child");
+
+       prop = RNA_def_property(srna, "use_twist_curve", PROP_BOOLEAN, PROP_NONE);
+       RNA_def_property_boolean_sdna(prop, NULL, "child_flag", PART_CHILD_USE_TWIST_CURVE);
+       RNA_def_property_ui_text(prop, "Use Twist Curve", "Use a curve to define twist");
+       RNA_def_property_update(prop, 0, "rna_ParticleSettings_use_twist_curve_update");
+
+       prop = RNA_def_property(srna, "twist_curve", PROP_POINTER, PROP_NONE);
+       RNA_def_property_pointer_sdna(prop, NULL, "twistcurve");
+       RNA_def_property_struct_type(prop, "CurveMapping");
+       RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+       RNA_def_property_ui_text(prop, "Twist Curve", "Curve defining twist");
+       RNA_def_property_update(prop, 0, "rna_Particle_redo_child");
 }
 
 static void rna_def_particle_target(BlenderRNA *brna)