Simple hair children: Initial implementation of twist control

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.
This commit is contained in:
Sergey Sharybin 2018-02-14 14:33:53 +01:00
parent bb3efe6127
commit ffde74a878
12 changed files with 172 additions and 0 deletions

View 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")

View 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]);

View 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);

View 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);

View 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)

View 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) {

View 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,

View 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)

View 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.) */

View 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;

View 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 */

View 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)