== Pivot Constraint ==
authorJoshua Leung <aligorith@gmail.com>
Thu, 27 May 2010 10:50:06 +0000 (10:50 +0000)
committerJoshua Leung <aligorith@gmail.com>
Thu, 27 May 2010 10:50:06 +0000 (10:50 +0000)
This constraint allows an object or bone to have their rotations applied as if their origin/pivot-point was located elsewhere. The most obvious uses include foot-roll, see-saws, but could also include more complicated rolling-box examples.

== Usage Examples ==
=== Foot Roll ===
1. Add 'Pivot' Constraint to the bone without any target.
2. Set the 'Y' value of the offset to the length of the bone. Usually this should be negative (if you rig with feet facing 'forwards' along -Y axis). This gives you a pivot point relative to the bone's (preconstraint) location, which should be at the tip of the bone here. Disabling the 'Use Relative Offset' would make this offset be relative to 0,0,0 instead of to the owner/bone-head.
3. Ensure that the 'Pivot When' setting is set to '-X Rot', (default) which means that the pivot will only used when the rotation on the X-Axis is negative to get tip-toe 'roll'.

=== See Saw ===
1. Add a 'Pivot' constraint too see-saw plank object, this time with a target that you wish to have as the pivot-point. It's possible to do this without too (as before), but is less intuitive.
2. Optionally, if you want the plank slightly raised, set the z-offset value, which should make the pivot-point used to be relative to the target with the z-offset applied.
3. Ensure that 'Pivot When' is set to 'Always', which means that the pivot will always be used, irrespective of the rotation.

== Notes ==
* The 'Pivot When' setting has been integrated in the constraint, since this is something that will often be required for these setups. Having to set up additional drivers to drive the constraint to do this kindof beats the purpose of providing this.

* The 'Offset' functionality is probably not presented as clearly as it could be. We may need to go over this again.

* For foot-roll - if any scaling of the foot is required, simply set up a driver on the y-offset to make this dynamically respond to the "scale" RNA property of the bones (don't use the "Transform Channel" vartype since that won't work correct here). However, this shouldn't be common enough to warrant special treatment.

release/scripts/ui/properties_object_constraint.py
source/blender/blenkernel/intern/constraint.c
source/blender/editors/object/object_constraint.c
source/blender/makesdna/DNA_constraint_types.h
source/blender/makesrna/RNA_access.h
source/blender/makesrna/intern/rna_constraint.c

index f53bac05a7c34642fb3afc27a5296e72e8ad5fe7..11527ad9bda19977f1f48bd9940e50eaf3604998 100644 (file)
@@ -76,7 +76,7 @@ class ConstraintButtonsPanel(bpy.types.Panel):
                 else:
                     layout.prop_object(con, "subtarget", con.target.data, "bones", text="")
 
-                if con.type in ('COPY_LOCATION', 'STRETCH_TO', 'TRACK_TO'):
+                if con.type in ('COPY_LOCATION', 'STRETCH_TO', 'TRACK_TO', 'PIVOT'):
                     row = layout.row()
                     row.label(text="Head/Tail:")
                     row.prop(con, "head_tail", text="")
@@ -730,6 +730,22 @@ class ConstraintButtonsPanel(bpy.types.Panel):
             col.prop(con, "xz_scaling_mode", text="")
         col.prop(con, "use_curve_radius")
 
+    def PIVOT(self, context, layout, con, wide_ui):
+        self.target_template(layout, con, wide_ui)
+
+        if con.target:
+            col = layout.column()
+            col.prop(con, "offset", text="Pivot Offset")
+        else:
+            col = layout.column()
+            col.prop(con, "use_relative_position")
+            if con.use_relative_position:  
+                col.prop(con, "offset", text="Relative Pivot Point")
+            else:
+                col.prop(con, "offset", text="Absolute Pivot Point")
+
+        col = layout.column()
+        col.prop(con, "enabled_rotation_range", text="Pivot When")
 
 class OBJECT_PT_constraints(ConstraintButtonsPanel):
     bl_label = "Object Constraints"
index a3f1cb0cb0c9d41388841fb36d47de217a12662d..1a8d665a91d0542a236c544f392e06d2ed51f5f7 100644 (file)
@@ -3831,6 +3831,118 @@ static bConstraintTypeInfo CTI_SPLINEIK = {
        NULL /* evaluate - solved as separate loop */
 };
 
+/* ----------- Pivot ------------- */
+
+static void pivotcon_id_looper (bConstraint *con, ConstraintIDFunc func, void *userdata)
+{
+       bPivotConstraint *data= con->data;
+       
+       /* target only */
+       func(con, (ID**)&data->tar, userdata);
+}
+
+static int pivotcon_get_tars (bConstraint *con, ListBase *list)
+{
+       if (con && list) {
+               bPivotConstraint *data= con->data;
+               bConstraintTarget *ct;
+               
+               /* standard target-getting macro for single-target constraints */
+               SINGLETARGET_GET_TARS(con, data->tar, data->subtarget, ct, list)
+               
+               return 1;
+       }
+       
+       return 0;
+}
+
+static void pivotcon_flush_tars (bConstraint *con, ListBase *list, short nocopy)
+{
+       if (con && list) {
+               bPivotConstraint *data= con->data;
+               bConstraintTarget *ct= list->first;
+               
+               /* the following macro is used for all standard single-target constraints */
+               SINGLETARGET_FLUSH_TARS(con, data->tar, data->subtarget, ct, list, nocopy)
+       }
+}
+
+static void pivotcon_evaluate (bConstraint *con, bConstraintOb *cob, ListBase *targets)
+{
+       bPivotConstraint *data= con->data;
+       bConstraintTarget *ct= targets->first;
+       
+       float pivot[3], vec[3];
+       float rotMat[3][3];
+       
+       /* firstly, check if pivoting should take place based on the current rotation */
+       if (data->rotAxis != PIVOTCON_AXIS_NONE) {
+               float rot[3];
+               
+               /* extract euler-rotation of target */
+               mat4_to_eulO(rot, cob->rotOrder, cob->matrix);
+               
+               /* check which range might be violated */
+               if (data->rotAxis < PIVOTCON_AXIS_X) {
+                       /* negative rotations (data->rotAxis = 0 -> 2) */
+                       if (rot[data->rotAxis] > 0.0f)
+                               return;
+               }
+               else {
+                       /* positive rotations (data->rotAxis = 3 -> 5 */
+                       if (rot[data->rotAxis - PIVOTCON_AXIS_X] < 0.0f)
+                               return;
+               }
+       }
+       
+       /* find the pivot-point to use  */
+       if (VALID_CONS_TARGET(ct)) {
+               /* apply offset to target location */
+               add_v3_v3v3(pivot, ct->matrix[3], data->offset);
+       }
+       else {
+               /* no targets to worry about... */
+               if ((data->flag & PIVOTCON_FLAG_OFFSET_ABS) == 0) {
+                       /* offset is relative to owner */
+                       add_v3_v3v3(pivot, cob->matrix[3], data->offset);
+               }
+               else {
+                       /* directly use the 'offset' specified as an absolute position instead */
+                       VECCOPY(pivot, data->offset);
+               }
+       }
+       
+       /* get rotation matrix representing the rotation of the owner */
+       // TODO: perhaps we might want to include scaling based on the pivot too?
+       copy_m3_m4(rotMat, cob->matrix);
+       normalize_m3(rotMat);
+       
+       /* perform the pivoting... */
+               /* 1. take the vector from owner to the pivot */
+       sub_v3_v3v3(vec, pivot, cob->matrix[3]);
+               /* 2. rotate this vector by the rotation of the object... */
+       mul_m3_v3(rotMat, vec);
+               /* 3. make the rotation in terms of the pivot now */
+       add_v3_v3v3(cob->matrix[3], pivot, vec);
+}
+
+
+static bConstraintTypeInfo CTI_PIVOT = {
+       CONSTRAINT_TYPE_PIVOT, /* type */
+       sizeof(bPivotConstraint), /* size */
+       "Pivot", /* name */
+       "bPivotConstraint", /* struct name */
+       NULL, /* free data */
+       NULL, /* relink data */
+       pivotcon_id_looper, /* id looper */
+       NULL, /* copy data */
+       NULL, /* new data */ // XXX: might be needed to get 'normal' pivot behaviour...
+       pivotcon_get_tars, /* get constraint targets */
+       pivotcon_flush_tars, /* flush constraint targets */
+       default_get_tarmat, /* get target matrix */
+       pivotcon_evaluate /* evaluate */
+};
+
 /* ************************* Constraints Type-Info *************************** */
 /* All of the constraints api functions use bConstraintTypeInfo structs to carry out
  * and operations that involve constraint specific code.
@@ -3867,6 +3979,7 @@ static void constraints_init_typeinfo () {
        constraintsTypeInfo[22]= &CTI_SPLINEIK;                 /* Spline IK Constraint */
        constraintsTypeInfo[23]= &CTI_TRANSLIKE;                /* Copy Transforms Constraint */
        constraintsTypeInfo[24]= &CTI_SAMEVOL;                  /* Maintain Volume Constraint */
+       constraintsTypeInfo[25]= &CTI_PIVOT;                    /* Pivot Constraint */
 }
 
 /* This function should be used for getting the appropriate type-info when only
index 9163baf606f33268666acfd428bd2d2d18c0e2eb..eaabcd866cd22aaee07bc7bdf764f900b7865db6 100644 (file)
@@ -322,6 +322,23 @@ static void test_constraints (Object *owner, bPoseChannel *pchan)
                                /* targets have already been checked for this */
                                continue;
                        }
+                       else if (curcon->type == CONSTRAINT_TYPE_PIVOT) {
+                               bPivotConstraint *data = curcon->data;
+                               
+                               /* target doesn't have to exist, but if it is non-null, it must exist! */
+                               if (data->tar && exist_object(data->tar)==0) {
+                                       data->tar = NULL;
+                                       curcon->flag |= CONSTRAINT_DISABLE;
+                               }
+                               else if (data->tar == owner) {
+                                       if (!get_named_bone(get_armature(owner), data->subtarget)) {
+                                               curcon->flag |= CONSTRAINT_DISABLE;
+                                       }
+                               }
+                               
+                               /* targets have already been checked for this */
+                               continue;
+                       }
                        else if (curcon->type == CONSTRAINT_TYPE_ACTION) {
                                bActionConstraint *data = curcon->data;
                                
index 6c7bda171eb4e48f18a2b3d7cbaab7ce512fee92..00694a7ea1f3be1ff41826d0179f08f08caed9b5 100644 (file)
@@ -174,6 +174,7 @@ typedef struct bSplineIKConstraint {
 
 
 /* Single-target subobject constraints ---------------------  */
+
 /* Track To Constraint */
 typedef struct bTrackToConstraint {
        Object          *tar;
@@ -336,6 +337,24 @@ typedef struct bTransformConstraint {
        float           to_max[3];      
 } bTransformConstraint;
 
+/* Pivot Constraint */
+typedef struct bPivotConstraint {
+       /* Pivot Point:
+        *      Either target object + offset, or just offset is used
+        */
+       Object          *tar;                   /* target object (optional) */
+       char            subtarget[32];          /* subtarget name (optional) */
+       float           offset[3];              /* offset from the target to use, regardless of whether it exists */
+       
+       /* Rotation-driven activation:
+        *      This option provides easier one-stop setups for footrolls
+        */
+       short           rotAxis;                /* rotation axes to consider for this (ePivotConstraint_Axis) */
+       
+       /* General flags */
+       short           flag;                   /* ePivotConstraint_Flag */
+} bPivotConstraint;
+
 /* transform limiting constraints - zero target ----------------------------  */
 /* Limit Location Constraint */
 typedef struct bLocLimitConstraint {
@@ -419,6 +438,7 @@ typedef enum eBConstraint_Types {
        CONSTRAINT_TYPE_SPLINEIK,                       /* Spline-IK - Align 'n' bones to a curve */
        CONSTRAINT_TYPE_TRANSLIKE,                      /* Copy transform matrix */
        CONSTRAINT_TYPE_SAMEVOL,                        /* Maintain volume during scaling */
+       CONSTRAINT_TYPE_PIVOT,                          /* Pivot Constraint */
        
        /* NOTE: no constraints are allowed to be added after this */
        NUM_CONSTRAINT_TYPES
@@ -469,11 +489,6 @@ typedef enum eConstraintChannel_Flags {
 
 /* -------------------------------------- */
 
-/**
- * The flags for ROTLIKE, LOCLIKE and SIZELIKE should be kept identical
- * (that is, same effect, different name). It simplifies the Python API access a lot.
- */
-
 /* bRotateLikeConstraint.flag */
 typedef enum eCopyRotation_Flags {
        ROTLIKE_X                       = (1<<0),
@@ -688,6 +703,35 @@ typedef enum eChildOf_Flags {
        CHILDOF_SIZEZ   = (1<<8),
 } eChildOf_Flags;
 
+/* Pivot Constraint */
+       /* Restrictions for Pivot Constraint axis to consider for enabling constraint */
+typedef enum ePivotConstraint_Axis {
+       /* do not consider this activity-clamping */
+       PIVOTCON_AXIS_NONE = -1,        
+       
+       /* consider -ve x-axis rotations */
+       PIVOTCON_AXIS_X_NEG,
+       /* consider -ve y-axis rotations */
+       PIVOTCON_AXIS_Y_NEG,
+       /* consider -ve z-axis rotations */
+       PIVOTCON_AXIS_Z_NEG,
+       
+       /* consider +ve x-axis rotations */
+       PIVOTCON_AXIS_X,
+       /* consider +ve y-axis rotations */
+       PIVOTCON_AXIS_Y,
+       /* consider +ve z-axis rotations */
+       PIVOTCON_AXIS_Z,
+} ePivotConstraint_Axis;
+
+       /* settings for Pivot Constraint in general */
+typedef enum ePivotConstraint_Flag {
+       /* offset is to be interpreted as being a fixed-point in space */
+       PIVOTCON_FLAG_OFFSET_ABS        = (1<<0),
+       /* rotation-based activation uses negative rotation to drive result */
+       PIVOTCON_FLAG_ROTACT_NEG        = (1<<1),
+} ePivotConstraint_Flag;
+
 /* Rigid-Body Constraint */
 #define CONSTRAINT_DRAW_PIVOT 0x40
 #define        CONSTRAINT_DISABLE_LINKED_COLLISION 0x80
index f9802e558bbd736166832555c2cd90ef6c3e9c40..3badc401514d703f589123e81091575ba712f3d4 100644 (file)
@@ -356,6 +356,7 @@ extern StructRNA RNA_SPHFluidSettings;
 extern StructRNA RNA_ParticleSystem;
 extern StructRNA RNA_ParticleSystemModifier;
 extern StructRNA RNA_ParticleTarget;
+extern StructRNA RNA_PivotConstraint;
 extern StructRNA RNA_PluginSequence;
 extern StructRNA RNA_PluginTexture;
 extern StructRNA RNA_PointCache;
index b4ae49ae2f4a74ad6dd2d4ffb498244fd9b0a217..b1c5cac1865c0d56a675685181c61fe0779466c5 100644 (file)
@@ -67,6 +67,7 @@ EnumPropertyItem constraint_type_items[] ={
        {CONSTRAINT_TYPE_RIGIDBODYJOINT, "RIGID_BODY_JOINT", ICON_CONSTRAINT_DATA, "Rigid Body Joint", ""},
        {CONSTRAINT_TYPE_PYTHON, "SCRIPT", ICON_CONSTRAINT_DATA, "Script", ""},
        {CONSTRAINT_TYPE_SHRINKWRAP, "SHRINKWRAP", ICON_CONSTRAINT_DATA, "Shrinkwrap", ""},
+       {CONSTRAINT_TYPE_PIVOT, "PIVOT", ICON_CONSTRAINT_DATA, "Pivot", ""},
        {0, NULL, 0, NULL, NULL}};
 
 EnumPropertyItem space_pchan_items[] = {
@@ -157,6 +158,8 @@ static StructRNA *rna_ConstraintType_refine(struct PointerRNA *ptr)
                        return &RNA_SplineIKConstraint;
                case CONSTRAINT_TYPE_TRANSLIKE:
                        return &RNA_CopyTransformsConstraint;
+               case CONSTRAINT_TYPE_PIVOT:
+                       return &RNA_PivotConstraint;
                default:
                        return &RNA_UnknownType;
        }
@@ -1839,6 +1842,62 @@ static void rna_def_constraint_spline_ik(BlenderRNA *brna)
        RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_update");
 }
 
+static void rna_def_constraint_pivot(BlenderRNA *brna)
+{
+       StructRNA *srna;
+       PropertyRNA *prop;
+
+       static EnumPropertyItem pivot_rotAxis_items[] = {
+               {PIVOTCON_AXIS_NONE, "ALWAYS_ACTIVE", 0, "Always", ""},
+               {PIVOTCON_AXIS_X_NEG, "NX", 0, "-X Rot", ""},
+               {PIVOTCON_AXIS_Y_NEG, "NY", 0, "-Y Rot", ""},
+               {PIVOTCON_AXIS_Z_NEG, "NZ", 0, "-Z Rot", ""},
+               {PIVOTCON_AXIS_X, "X", 0, "X Rot", ""},
+               {PIVOTCON_AXIS_Y, "Y", 0, "Y Rot", ""},
+               {PIVOTCON_AXIS_Z, "Z", 0, "Z Rot", ""},
+               {0, NULL, 0, NULL, NULL}};
+
+       srna= RNA_def_struct(brna, "PivotConstraint", "Constraint");
+       RNA_def_struct_ui_text(srna, "Pivot Constraint", "Rotate around a different point");
+       
+       prop= RNA_def_property(srna, "head_tail", PROP_FLOAT, PROP_FACTOR);
+       RNA_def_property_float_sdna(prop, "bConstraint", "headtail");
+       RNA_def_property_ui_text(prop, "Head/Tail", "Target along length of bone: Head=0, Tail=1");
+       RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_update");
+       
+       RNA_def_struct_sdna_from(srna, "bPivotConstraint", "data");
+       
+       /* target-defined pivot */
+       prop= RNA_def_property(srna, "target", PROP_POINTER, PROP_NONE);
+       RNA_def_property_pointer_sdna(prop, NULL, "tar");
+       RNA_def_property_ui_text(prop, "Target", "Target Object, defining the position of the pivot when defined");
+       RNA_def_property_flag(prop, PROP_EDITABLE);
+       RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_dependency_update");
+       
+       prop= RNA_def_property(srna, "subtarget", PROP_STRING, PROP_NONE);
+       RNA_def_property_string_sdna(prop, NULL, "subtarget");
+       RNA_def_property_ui_text(prop, "Sub-Target", "");
+       RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_dependency_update");
+       
+       /* pivot offset */
+       prop= RNA_def_property(srna, "use_relative_position", PROP_BOOLEAN, PROP_NONE);
+       RNA_def_property_boolean_negative_sdna(prop, NULL, "flag", PIVOTCON_FLAG_OFFSET_ABS);
+       RNA_def_property_ui_text(prop, "Use Relative Offset", "Offset will be an absolute point in space instead of relative to the target");
+       RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_update");
+       
+       prop= RNA_def_property(srna, "offset", PROP_FLOAT, PROP_XYZ);
+       RNA_def_property_float_sdna(prop, NULL, "offset");
+       RNA_def_property_ui_text(prop, "Offset", "Offset of pivot from target (when set), or from owner's location (when Fixed Position is off), or the absolute pivot point");
+       RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_update");
+       
+       /* rotation-based activation */
+       prop= RNA_def_property(srna, "enabled_rotation_range", PROP_ENUM, PROP_NONE);
+       RNA_def_property_enum_sdna(prop, NULL, "rotAxis");
+       RNA_def_property_enum_items(prop, pivot_rotAxis_items);
+       RNA_def_property_ui_text(prop, "Enabled Rotation Range", "Rotation range on which pivoting should occur");
+       RNA_def_property_update(prop, NC_OBJECT|ND_CONSTRAINT, "rna_Constraint_update");
+}
+
 /* base struct for constraints */
 void RNA_def_constraint(BlenderRNA *brna)
 {
@@ -1943,6 +2002,7 @@ void RNA_def_constraint(BlenderRNA *brna)
        rna_def_constraint_shrinkwrap(brna);
        rna_def_constraint_damped_track(brna);
        rna_def_constraint_spline_ik(brna);
+       rna_def_constraint_pivot(brna);
 }
 
 #endif