Particle dupliobject rotation changes:
authorJanne Karhu <jhkarh@gmail.com>
Sun, 16 Oct 2011 16:14:36 +0000 (16:14 +0000)
committerJanne Karhu <jhkarh@gmail.com>
Sun, 16 Oct 2011 16:14:36 +0000 (16:14 +0000)
There has been quite a bit of fuss about particle dupliobject rotation in 2.59, so here are some changes to make things work a bit more consistently and predictably in 2.60.

Much of the confusion has been about what the "Initial rotation" for particles actually means. Simply put it's just a vector that that the particles (and the dupliobjects) are aligned to and around which they can be rotated with the phase controls. I've now renamed these controls under a label "Rotation axis".

In 2.59 and previous versions the dupliobject's global x-axis was aligned to the particle rotation axis for non-hair particles. This meant that the object's own rotation (in addition to the particle rotation) could effect the dupliobjects' rotations. This old behavior can still be used with the "Rotation" option in the particle render panel when object/group is set as the visualization. This option is also activated automatically for old files to maintain backwards compatibility.

Now the default dupliobject rotations ignore the object's own rotation completely and align the object's tracking axis to the particle rotation axis. The tracking axis can be found under the Object tab -> Animation Hacks panel.

In 2.58 the way of calculating the rotation for hair didn't work as intended and enabled many non-functional combinations of options. For this reason I removed most of the rotation options for hair in 2.59. Now the options have been reimplemented better and the dupliobject's tracking axis is aligned to the hair direction by default (Rotation axis = Velocity / Hair). All the other axis options work too along with the phase controls.

release/scripts/startup/bl_ui/properties_particle.py
source/blender/blenkernel/intern/anim.c
source/blender/blenkernel/intern/particle.c
source/blender/blenloader/intern/readfile.c
source/blender/makesdna/DNA_particle_types.h
source/blender/makesrna/intern/rna_particle.c

index eceefc70b5cdecee852f10bdc33fad1708376a1b..d24aef56a8b76ddd35fc161501ea8a65bf5d9f00 100644 (file)
@@ -404,10 +404,13 @@ class PARTICLE_PT_rotation(ParticleButtonsPanel, Panel):
             part = context.space_data.pin_id
 
         layout.enabled = particle_panel_enabled(context, psys)
+        
+        layout.prop(part, "use_dynamic_rotation")
 
-        row = layout.row()
-        row.label(text="Initial Rotation:")
-        row.prop(part, "use_dynamic_rotation")
+        if part.use_dynamic_rotation:
+            layout.label(text="Initial Rotation Axis:")
+        else:
+            layout.label(text="Rotation Axis:")
 
         split = layout.split()
 
@@ -419,12 +422,18 @@ class PARTICLE_PT_rotation(ParticleButtonsPanel, Panel):
         col.prop(part, "phase_factor", slider=True)
         col.prop(part, "phase_factor_random", text="Random", slider=True)
 
-        col = layout.column()
-        col.label(text="Angular Velocity:")
-        col.row().prop(part, "angular_velocity_mode", expand=True)
-
-        if part.angular_velocity_mode != 'NONE':
-            col.prop(part, "angular_velocity_factor", text="")
+        if part.type != 'HAIR':
+            col = layout.column()
+            if part.use_dynamic_rotation:
+                col.label(text="Initial Angular Velocity:")
+            else:
+                col.label(text="Angular Velocity:")
+            sub = col.row(align=True)
+            sub.prop(part, "angular_velocity_mode", text="")
+            subsub = sub.column()
+            subsub.active = part.angular_velocity_mode != 'NONE'
+            subsub.prop(part, "angular_velocity_factor", text="")
+            
 
 
 class PARTICLE_PT_physics(ParticleButtonsPanel, Panel):
@@ -832,7 +841,9 @@ class PARTICLE_PT_render(ParticleButtonsPanel, Panel):
 
         elif part.render_type == 'OBJECT':
             col.prop(part, "dupli_object")
-            col.prop(part, "use_global_dupli")
+            sub = col.row()
+            sub.prop(part, "use_global_dupli")
+            sub.prop(part, "use_rotation_dupli")
         elif part.render_type == 'GROUP':
             col.prop(part, "dupli_group")
             split = layout.split()
@@ -841,13 +852,14 @@ class PARTICLE_PT_render(ParticleButtonsPanel, Panel):
             col.prop(part, "use_whole_group")
             sub = col.column()
             sub.active = (part.use_whole_group is False)
+            sub.prop(part, "use_group_pick_random")
             sub.prop(part, "use_group_count")
 
             col = split.column()
             sub = col.column()
             sub.active = (part.use_whole_group is False)
             sub.prop(part, "use_global_dupli")
-            sub.prop(part, "use_group_pick_random")
+            sub.prop(part, "use_rotation_dupli")
 
             if part.use_group_count and not part.use_whole_group:
                 row = layout.row()
index cd2c272a1c22a1915a1375fbf9f38a09e80be914..da6dd5bd39daad0fe8452e32400babd7ff6719d6 100644 (file)
@@ -1430,6 +1430,16 @@ static void new_particle_duplilist(ListBase *lb, ID *id, Scene *scene, Object *p
 
                                VECCOPY(vec, obmat[3]);
                                obmat[3][0] = obmat[3][1] = obmat[3][2] = 0.0f;
+
+                               /* particle rotation uses x-axis as the aligned axis, so pre-rotate the object accordingly */
+                               if((part->draw & PART_DRAW_ROTATE_OB) == 0) {
+                                       float xvec[3], q[4];
+                                       xvec[0] = -1.f;
+                                       xvec[1] = xvec[2] = 0;
+                                       vec_to_quat(q, xvec, ob->trackflag, ob->upflag);
+                                       quat_to_mat4(obmat, q);
+                                       obmat[3][3]= 1.0f;
+                               }
                                
                                /* Normal particles and cached hair live in global space so we need to
                                 * remove the real emitter's transformation before 2nd order duplication.
@@ -1448,7 +1458,7 @@ static void new_particle_duplilist(ListBase *lb, ID *id, Scene *scene, Object *p
                                        copy_m4_m4(mat, tmat);
 
                                if(part->draw & PART_DRAW_GLOBAL_OB)
-                                       VECADD(mat[3], mat[3], vec);
+                                       add_v3_v3v3(mat[3], mat[3], vec);
 
                                dob= new_dupli_object(lb, ob, mat, ob->lay, counter, GS(id->name) == ID_GR ? OB_DUPLIGROUP : OB_DUPLIPARTS, animated);
                                copy_m4_m4(dob->omat, oldobmat);
index 806a7871948065c100adc22931d81abbb5a97c4e..8669c4e0efd916ffd2208f3b899a9235fde16e24 100644 (file)
@@ -4384,33 +4384,50 @@ void psys_get_dupli_path_transform(ParticleSimulationData *sim, ParticleData *pa
                psys_particle_on_emitter(psmd,sim->psys->part->from,pa->num,pa->num_dmcache,pa->fuv,pa->foffset,loc,nor,0,0,0,0);
        else
                psys_particle_on_emitter(psmd,PART_FROM_FACE,cpa->num,DMCACHE_ISCHILD,cpa->fuv,cpa->foffset,loc,nor,0,0,0,0);
-               
-       copy_m3_m4(nmat, ob->imat);
-       transpose_m3(nmat);
-       mul_m3_v3(nmat, nor);
-       normalize_v3(nor);
-
-       /* make sure that we get a proper side vector */
-       if(fabs(dot_v3v3(nor,vec))>0.999999) {
-               if(fabs(dot_v3v3(nor,xvec))>0.999999) {
-                       nor[0] = 0.0f;
-                       nor[1] = 1.0f;
-                       nor[2] = 0.0f;
+
+       if(psys->part->rotmode == PART_ROT_VEL) {
+               copy_m3_m4(nmat, ob->imat);
+               transpose_m3(nmat);
+               mul_m3_v3(nmat, nor);
+               normalize_v3(nor);
+
+               /* make sure that we get a proper side vector */
+               if(fabs(dot_v3v3(nor,vec))>0.999999) {
+                       if(fabs(dot_v3v3(nor,xvec))>0.999999) {
+                               nor[0] = 0.0f;
+                               nor[1] = 1.0f;
+                               nor[2] = 0.0f;
+                       }
+                       else {
+                               nor[0] = 1.0f;
+                               nor[1] = 0.0f;
+                               nor[2] = 0.0f;
+                       }
                }
-               else {
-                       nor[0] = 1.0f;
-                       nor[1] = 0.0f;
-                       nor[2] = 0.0f;
+               cross_v3_v3v3(side, nor, vec);
+               normalize_v3(side);
+
+               /* rotate side vector around vec */
+               if(psys->part->phasefac != 0) {
+                       float q_phase[4];
+                       float phasefac = psys->part->phasefac;
+                       if(psys->part->randphasefac != 0.0f)
+                               phasefac += psys->part->randphasefac * PSYS_FRAND((pa-psys->particles) + 20);
+                       axis_angle_to_quat( q_phase, vec, phasefac*(float)M_PI);
+
+                       mul_qt_v3(q_phase, side);
                }
-       }
-       cross_v3_v3v3(side, nor, vec);
-       normalize_v3(side);
-       cross_v3_v3v3(nor, vec, side);
 
-       unit_m4(mat);
-       VECCOPY(mat[0], vec);
-       VECCOPY(mat[1], side);
-       VECCOPY(mat[2], nor);
+               cross_v3_v3v3(nor, vec, side);
+
+               unit_m4(mat);
+               VECCOPY(mat[0], vec);
+               VECCOPY(mat[1], side);
+               VECCOPY(mat[2], nor);
+       }
+       else {
+               quat_to_mat4(mat, pa->state.rot);
+       }
 
        *scale= len;
 }
index 76070e42f29f097d1a07630b051fe8fef1597076..84b9021d080158fbacc861827a7a644fd6a54fb0 100644 (file)
@@ -12176,6 +12176,20 @@ static void do_versions(FileData *fd, Library *lib, Main *main)
                        for (ntree=main->nodetree.first; ntree; ntree=ntree->id.next)
                                do_versions_nodetree_image_default_alpha_output(ntree);
                }
+
+               {
+                       /* support old particle dupliobject rotation settings */
+                       ParticleSettings *part;
+
+                       for (part=main->particle.first; part; part=part->id.next) {
+                               if(ELEM(part->ren_as, PART_DRAW_OB, PART_DRAW_GR)) {
+                                       part->draw |= PART_DRAW_ROTATE_OB;
+
+                                       if(part->rotmode == 0)
+                                               part->rotmode = PART_ROT_VEL;
+                               }
+                       }
+               }
        }
 
        /* WATCH IT!!!: pointers from libdata have not been converted yet here! */
index 9fec5207dbb266d7e28ece89e3e26bb7a6824221..da2fce4da82f0a7d74c24791d3eeaeee6955b9f9 100644 (file)
@@ -386,7 +386,8 @@ typedef struct ParticleSystem{                              /* note, make sure all (runtime) are NULL's in
 #define PART_DRAW_HEALTH       16
 #define PART_ABS_PATH_TIME  32
 #define PART_DRAW_COUNT_GR     64
-#define PART_DRAW_BB_LOCK      128
+#define PART_DRAW_BB_LOCK      128     /* used with billboards */
+#define PART_DRAW_ROTATE_OB 128 /* used with dupliobjects/groups */
 #define PART_DRAW_PARENT       256
 #define PART_DRAW_NUM          512
 #define PART_DRAW_RAND_GR      1024
index 49005e56367471d8aeb1a6fd6588680ef1a6d949..43d5f87f0d5001d0e1e32d903d33ee2cc18a8844 100644 (file)
@@ -1478,7 +1478,7 @@ static void rna_def_particle_settings(BlenderRNA *brna)
        static EnumPropertyItem rot_mode_items[] = {
                {0, "NONE", 0, "None", ""},
                {PART_ROT_NOR, "NOR", 0, "Normal", ""},
-               {PART_ROT_VEL, "VEL", 0, "Velocity", ""},
+               {PART_ROT_VEL, "VEL", 0, "Velocity / Hair", ""},
                {PART_ROT_GLOB_X, "GLOB_X", 0, "Global X", ""},
                {PART_ROT_GLOB_Y, "GLOB_Y", 0, "Global Y", ""},
                {PART_ROT_GLOB_Z, "GLOB_Z", 0, "Global Z", ""},
@@ -1733,7 +1733,7 @@ static void rna_def_particle_settings(BlenderRNA *brna)
        RNA_def_property_enum_sdna(prop, NULL, "rotmode");
        RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
        RNA_def_property_enum_items(prop, rot_mode_items);
-       RNA_def_property_ui_text(prop, "Rotation", "Particles initial rotation");
+       RNA_def_property_ui_text(prop, "Rotation", "Particle rotation axis");
        RNA_def_property_update(prop, 0, "rna_Particle_reset");
 
        prop= RNA_def_property(srna, "angular_velocity_mode", PROP_ENUM, PROP_NONE);
@@ -1798,7 +1798,12 @@ static void rna_def_particle_settings(BlenderRNA *brna)
 
        prop= RNA_def_property(srna, "use_global_dupli", PROP_BOOLEAN, PROP_NONE);
        RNA_def_property_boolean_sdna(prop, NULL, "draw", PART_DRAW_GLOBAL_OB);
-       RNA_def_property_ui_text(prop, "Use Global", "Use object's global coordinates for duplication");
+       RNA_def_property_ui_text(prop, "Global", "Use object's global coordinates for duplication");
+       RNA_def_property_update(prop, 0, "rna_Particle_redo");
+
+       prop= RNA_def_property(srna, "use_rotation_dupli", PROP_BOOLEAN, PROP_NONE);
+       RNA_def_property_boolean_sdna(prop, NULL, "draw", PART_DRAW_ROTATE_OB);
+       RNA_def_property_ui_text(prop, "Rotation", "Use object's rotation for duplication (global x-axis is aligned particle rotation axis)");
        RNA_def_property_update(prop, 0, "rna_Particle_redo");
 
        prop= RNA_def_property(srna, "use_render_adaptive", PROP_BOOLEAN, PROP_NONE);