I meant to post this for a while, but never got around to finish a demo of it. Inspired by Mike Best's eggroll setup, I wanted to do a similar setup because it was a good challenge. After my previously failed attempt I switched to a special matrix(found from google) to calculate rotation of the object around an axis using maya expression. This worked very well and I finally got the rolling working. However I still had some minor problem with small offset from the ground in some positions, it might be the pivot location that might be off. But more challenging is the sliding problem, though it's not very noticeable in the demo. I would like to come back to it later. Deformation of the egg should not be difficult to add to this setup as I am already counting in the bounding box volume in nodes calculation.
Later Mike was kind enough to share the script which gave me insight into his setup. Mine is not as good as his, but I was happy to get the egg rolling :)
This challenge sparked my interest in rigid body simulation. Hopefully in the future I will be able to take time into reading more about it (and begin to understand something!) to write my own little simulator. I am looking particularly for a real egg rolling simulation where the center of mass of rigid body is shifting based on the properties of the fluid (egg yolk and egg white).
15 October, 2011
Bare essential commands that create Maya's scene
If you open .MA maya file in notepad, you will see many lines of code based on your scene size. If you go through the code, no matter how big the scene is or how many nodes are there, the whole scene is created by only 4 commands! That's it! It's very easy to find this information, but what amazes me is that the programmers were able to abstract the logic of scene creation to the following atomic tasks:
- Creating nodes with parenting info
- Adding attributes
- Setting changed attributes
- Connecting the nodes
14 April, 2011
Making Animatable Pivot: Part 2
In the first part, we looked at the basics of how pivot works and what is the main challenge in making pivot animatable. In this part we take a look at the implementation detail to build a system which will calculate movement around given pivot point. Here is the hierarchy I am using for this system.
Instead of step by step tutorial, I will try to describe main logic of how it works. First, let's examine our goal and see what kind of result we are looking for. Here is an image that shows the initial status and what we expect after rotating the cube.
If we rotate the cube right now in our current initial state, then what we get is shown in the image below. It's what you would expect since our pivot is parented under the cube. But this is not what we want.
So how do we go from our initial state to the final result? There are different ways to solve this problem. Here, we look at one way to do it, which may not necessarily be the easiest.
First thing we need to keep in mind is that our pivot is parented under the cube, that means our pivot is operating under cube's coordinate space (V.IMP.). Now, let's see the steps to get the final result.
#1) We move the pivot point so that it lies on the origin. (Keep in mind, this origin is in our cube's space)
Why do we do this? So we can apply rotation matrix which always rotates around (0,0,0)
#2) Now, we apply the rotation, which will be around the origin by default.
See it already looks closer to the result we want!
#3) Then we reverse the first step. We move everything back so that pivot goes to it's original location.
And we have our final result!
The matrix equation to get the above result is:
Now prepare the network to calculate the object movement around pivot using above matrix equation. Plug object's rotation into object_rot which will be our rotation matrix. Once you have the matrix calculation network ready, plug the final .matrixSum to a decomposeMatrix node's .inputMatrix attribute. What we will get here (in .outputTranslate) is the translation value that the object should move to get the pivoting movement around pivot_loc. Just to test the result you can plug .outputTranslate attribute of decomposeMatrix node to netPivotShift node and see how it works. Give it a try, it's fun! :)
Phew! That took the whole brain out of me! Now, remains the offsetting logic. Let's keep it for the next post to give my brain processor some rest :)
netPivotShift
|_object
|_ pivot_loc
|_ object_rotInstead of step by step tutorial, I will try to describe main logic of how it works. First, let's examine our goal and see what kind of result we are looking for. Here is an image that shows the initial status and what we expect after rotating the cube.
If we rotate the cube right now in our current initial state, then what we get is shown in the image below. It's what you would expect since our pivot is parented under the cube. But this is not what we want.
So how do we go from our initial state to the final result? There are different ways to solve this problem. Here, we look at one way to do it, which may not necessarily be the easiest.
First thing we need to keep in mind is that our pivot is parented under the cube, that means our pivot is operating under cube's coordinate space (V.IMP.). Now, let's see the steps to get the final result.
#1) We move the pivot point so that it lies on the origin. (Keep in mind, this origin is in our cube's space)
Why do we do this? So we can apply rotation matrix which always rotates around (0,0,0)
#2) Now, we apply the rotation, which will be around the origin by default.
See it already looks closer to the result we want!
#3) Then we reverse the first step. We move everything back so that pivot goes to it's original location.
And we have our final result!
The matrix equation to get the above result is:
shift = pivot_loc.inverseMatrix * object_rot.matrix * pivot_loc.matrix
(this can be a bit confusing if compared to original equation in first post, because we are taking a bit different approach here)Now prepare the network to calculate the object movement around pivot using above matrix equation. Plug object's rotation into object_rot which will be our rotation matrix. Once you have the matrix calculation network ready, plug the final .matrixSum to a decomposeMatrix node's .inputMatrix attribute. What we will get here (in .outputTranslate) is the translation value that the object should move to get the pivoting movement around pivot_loc. Just to test the result you can plug .outputTranslate attribute of decomposeMatrix node to netPivotShift node and see how it works. Give it a try, it's fun! :)
Phew! That took the whole brain out of me! Now, remains the offsetting logic. Let's keep it for the next post to give my brain processor some rest :)
09 April, 2011
WIP: Unsuccessful attempt at rolling an egg(unknown type)
I can think of two ways to implement rolling egg.
1. Use aim constraint to point in the direction of movement and have rolling axis as your up vector. The object can then roll about this up vector. I will implement this approach later.
2. Calculate rolling rotation around the rolling axis using matrix calculations.
!These approaches do not simulate the actual physics.!
I am currently working on 2nd approach. Here are some notes for both the approaches:
# First one will follow a path and will need some work to blend the direction changes so the object orientation does not jump.
# First method is more like driving a car so the object's front/rear side follows the direction of movement, egg however may or may not follow this kind of movement.
# Second method adds the rotation on top of the current rotation, so egg starts rolling in the direction of movement without any jump in it's orientation.
# First one will rely on expression only for small amount of work, hence reliable undo operations.
# Second method depends mainly on expression to avoid cycle and use of getAttr, setAttr is required. This leads to non-user friendly undo.
The main difficulty in 2nd method is in getting the rotation around the rolling axis. I understand the theoretical part of it, but I am having a bit difficult time implementing it. May be I am doing something wrong, but it's hard to debug it at this stage. Here is what I am doing:
- Find the direction of movement
- Find rolling axis using cross product
- Find a matrix difference between so that z-axis of egg rotation matrix matches rolling axis (Diff)
- Do this operation to rotate egg around rolling axis by R
F = Egg * Diff * R * Diff(-1)
- In above equation we first take the current egg rotation and then multiply it by "Diff" matrix to align z-axis with rolling axis. Then apply the rotation amount around z-axis and then just apply inverse of Diff matrix.
And here is the result.
You can see that in the beginning it rolls fine, but when direction changes rotation does not follow the rolling axis.
03 April, 2011
Making Animatable Pivot: Part 1
First concept here to understand is the rotation operation and how it is affected by pivot point. Here is a post I wrote on my blog last year to understand how pivot works.
First we will look at how rotation around pivot point is calculated in simple steps.
1. Translate the object from its current location to the pivot point location
2. Rotate the object
3. Translate the object back to its place from pivot point location
What the last step does is that it moves the object in the rotated transform axis. Hence it moves the object to the final location which gives the effect of rotation around given pivot point. Mathematically here is what we will do using matrices to get the final result:
F = T * R * T'
Where,
T = translation matrix to move from current position to the pivot position
R = rotation matrix
T' = inverse matrix of T
This was just the first step, in calculating the rotation around arbitrary point. However, the main challenge is to make pivot point animatabale.
If you have rotated the object first and then try to change the pivot location, the object will move as well. What happens here is that the object already had some rotation applied to it based on previous pivot position. Now, we change the pivot location which will cause the final matrix to be recalculated based on existing rotation and new pivot point. This results in object shifting to a new location. This seems quite obvious, but in many cases it is unwanted. Maya deals with this problem by storing an offset value in ".translateRotatePivot" attribute when you are moving the pivot point. This stops object from shifting.
But the problem with Maya's approach is that all the rotation calculations are always based on single pivot location. So even if object does not move while moving pivot point, it does affect all the previously keyframed transform values.
So to solve this problem we calculate the offset at each change of pivot location and then store this offset which can be animated on a pivot control. I will write about this in detail in my next post.
First we will look at how rotation around pivot point is calculated in simple steps.
1. Translate the object from its current location to the pivot point location
2. Rotate the object
3. Translate the object back to its place from pivot point location
What the last step does is that it moves the object in the rotated transform axis. Hence it moves the object to the final location which gives the effect of rotation around given pivot point. Mathematically here is what we will do using matrices to get the final result:
F = T * R * T'
Where,
T = translation matrix to move from current position to the pivot position
R = rotation matrix
T' = inverse matrix of T
This was just the first step, in calculating the rotation around arbitrary point. However, the main challenge is to make pivot point animatabale.
If you have rotated the object first and then try to change the pivot location, the object will move as well. What happens here is that the object already had some rotation applied to it based on previous pivot position. Now, we change the pivot location which will cause the final matrix to be recalculated based on existing rotation and new pivot point. This results in object shifting to a new location. This seems quite obvious, but in many cases it is unwanted. Maya deals with this problem by storing an offset value in ".translateRotatePivot" attribute when you are moving the pivot point. This stops object from shifting.
But the problem with Maya's approach is that all the rotation calculations are always based on single pivot location. So even if object does not move while moving pivot point, it does affect all the previously keyframed transform values.
So to solve this problem we calculate the offset at each change of pivot location and then store this offset which can be animated on a pivot control. I will write about this in detail in my next post.
20 March, 2011
Removing multiple items from textScrollList
I have started working more with Maya UI and I can see why people get frustrated sometimes with handling UI interaction using mel. For example, there is no function to get an item at a given index.
Deletion of multiple items from a list or an array is always tricky because, while deleting multiple items the indices are updated automatically. One solution is to start deleting at the highest index so that the indices to be deleted later are always unaffected.
Here is the function I wrote for the extended textScrollList class. Python's built-in function to sort a list comes in handy here.
Deletion of multiple items from a list or an array is always tricky because, while deleting multiple items the indices are updated automatically. One solution is to start deleting at the highest index so that the indices to be deleted later are always unaffected.
Here is the function I wrote for the extended textScrollList class. Python's built-in function to sort a list comes in handy here.
def removeAt(self, inds): """ Remove item(s) at given indices from textScrollList @inds: takes a single int or a list of int """ if not isinstance(inds,list): inds = [inds] # indices should be sorted first into descending order # so that the indices to be deleted next are unaffected inds.sort(reverse=True) for i in inds: self.edit(removeIndexedItem=i)
14 March, 2011
Rigenerator Update
I have switched back to rigging related work. I have resumed my work on Rigenerator by analyzing my existing code. It can be very helpful to take a break from the work and revisit it later. I found many ideas to improve and more importantly simplify the logic for Rigenerator. Earlier it was not very bad, but I always felt that the code and logic I had written was a bit twisted and difficult to understand. I have put so many comments in the code, but still I was not satisfactory with the logical structure. So I have gone through the code many times as I kept refining it and simplifying the logic.
The first thing I did was to divide the code into proper modules (which I had to do anyway). While doing so I also kept the UI in mind. This will make plugging the UI process easier. There are some changes in logic for exploring the graph. It will allow more possibilities in conditional gathering of graph nodes. Finally I have moved Rigenerator out of my rigging framework project and it is a tool by itself now.
The major changes are in the graph rule structure. It is much more flexible and easy to follow now. The new rule structure allows the following,
# A value can be a node type or a node name
# Neighbor node(s) can be specified for rule condition
# Also, connection attributes can be specified for the rule
# Any value in the above conditions can use simple regular expression (RegEx.)
# Simple RegEx. can be extended in the future to allow more complicated RegEx. syntax
# Rule conditions can be grouped to force AND operation (all conditions should match)
Since I have made a lot of changes, this also requires for more testing. But I am looking forward to finishing the new algorithm and start testing the results!
The first thing I did was to divide the code into proper modules (which I had to do anyway). While doing so I also kept the UI in mind. This will make plugging the UI process easier. There are some changes in logic for exploring the graph. It will allow more possibilities in conditional gathering of graph nodes. Finally I have moved Rigenerator out of my rigging framework project and it is a tool by itself now.
The major changes are in the graph rule structure. It is much more flexible and easy to follow now. The new rule structure allows the following,
# A value can be a node type or a node name
# Neighbor node(s) can be specified for rule condition
# Also, connection attributes can be specified for the rule
# Any value in the above conditions can use simple regular expression (RegEx.)
# Simple RegEx. can be extended in the future to allow more complicated RegEx. syntax
# Rule conditions can be grouped to force AND operation (all conditions should match)
Since I have made a lot of changes, this also requires for more testing. But I am looking forward to finishing the new algorithm and start testing the results!
12 February, 2011
New Year New Adventure
It's been very very long since I posted any updates. I have been working on many projects simultaneously so it's been difficult to find time. I have done facial rigging for a friend of mine and I am also working on my Rigenerator project. For Rigenerator, I have started working on converting code for special nodes into the code that uses node's own command, e.g. cluster. Later I have a plan to add support for Python. But there is a lot to be done before that.
However, I have taken a break from my rigging work and I have started diving into two other development projects, a web app and an IPhone game.
However, I have taken a break from my rigging work and I have started diving into two other development projects, a web app and an IPhone game.
I was already building my website using CodeIgniter framework for php and during that time my wife suggested some great ideas for building a web application. Since then I have become passionate about this idea. So I have started building some parts of the app as a prototype. I am using PHP (with CodeIgniter as framework), jQuery and MySQL to build this web app. It is going to be a long term project so I will be switching between other projects and this one.
I am also exploring some game ideas for iphone while learning Objective C along the way. There are many aspects to consider when designing a game. A good game should keep users interested from beginning to the end and also a good game users will want to play again and again. I am considering coscos2d-iphone game engine for the game and later plan to add box2d physics engine. This will also help me with my technical animation reel.
Here is another blog I started to take notes about the new projects and programming in general.
http://maulik13tech.blogspot.com
Here is another blog I started to take notes about the new projects and programming in general.
http://maulik13tech.blogspot.com
02 August, 2010
MSyntax Flag Definition & Invalid Flag Error
I had some frustrating moments in making flags work for a command using MSyntax. A part of it was my ignorance in reading the function detail, but the other one I am not sure is mentioned in docs. Here is what it says in the doc. for MSyntax::addFlag(const char* shortFlag, const char* longFlag, ......)
=> Short flag name should always be < 4 characters.
=> I defined one flag name like this: syntax.addFlag("-it", "-infType", MSyntax::kDouble);
and used it with my command like this: myCmd -infType 2 Maya gave me an error, "invalid flag -infType"!
Then I changed the flag long name from -infType to -influenceType it all worked fine. So I wonder there is a problem with defining a long flag name beginning with three lower case characters, followed by an upper case character.
shortName the string representing the short (< 4 character) version of the flag
longName the string representing the int (> 3 character) version of the flag
longName the string representing the int (> 3 character) version of the flag
=> I defined one flag name like this: syntax.addFlag("-it", "-infType", MSyntax::kDouble);
and used it with my command like this: myCmd -infType 2 Maya gave me an error, "invalid flag -infType"!
Then I changed the flag long name from -infType to -influenceType it all worked fine. So I wonder there is a problem with defining a long flag name beginning with three lower case characters, followed by an upper case character.
20 July, 2010
Rigenerator: Update 1
The major change in the new version is the rule based DG traversal. Earlier DG traversal was unrestricted and it will fetch all the nodes involved in the scene. In the new version I have added rules that are stored in a simple dictionary structure which can be updated dynamically. This allows me to select a DAG structure and Rigenerator will only get the rig involved in that selected DAG tree.
Rules are used by algorithm to decide if it needs to ignore a node, or ignore a branch or end at a particular node in DG. These conditions are triggered when we encounter specified node type or name. I will add regular expression to both, node name and type, so that we can target more generalized types of nodes. Later on, my plan is to be able to specify node patterns and levels in the rules. This rule based DG traversal is very powerful as I can fine tune the algorithm dynamically to extract any kind of rig.
I have also fixed problems with DG connection sequence in the code. So now there will be no connection dependency problems like this:
Next addition is the option to include top hierarchy nodes of the selected rig. There are three options for this:
1. Recreate the top hierarchy
2. Use existing top hierarchy
3. Just group the rig under a separate node
For example, you Rignerate the Arm setup and also want all the top hierarchy nodes, e.g. shoulder, upper_body, root etc, then you can include those transform nodes, so it maintains the hierarchy. Using existing hierarchy helps, if you are mirroring the rig. And you can group the rig under a single node, in case you want to move it somewhere else.
Right now the code generation is still in .MA format and even if its simplicity is very useful it adds limitations because certain type of code cannot be executed with MEL! I can understand the reasons behind it, but it has added complexity to the code generation.
For example,
Hence the next step is to add special processing for special nodes, in this case animCurveUA. So instead of using MPlug.getSetAttrCmds() I will query attribute using normal mel commands. But, I need to generalize this into a proper system, instead of going and adding so many specific if conditions. This will also allow me to generate special commands for deformers as I have posted in one of my earlier posts. So that's the next goal.
Rules are used by algorithm to decide if it needs to ignore a node, or ignore a branch or end at a particular node in DG. These conditions are triggered when we encounter specified node type or name. I will add regular expression to both, node name and type, so that we can target more generalized types of nodes. Later on, my plan is to be able to specify node patterns and levels in the rules. This rule based DG traversal is very powerful as I can fine tune the algorithm dynamically to extract any kind of rig.
I have also fixed problems with DG connection sequence in the code. So now there will be no connection dependency problems like this:
connectAttr nurbsCurveShape.worldSpace[0] curveInfo1.inputCurve; connectAttr makeNurbsCircle1.outputCurve nurbsCurveShape.create; // This will be an error since nurbsCurveShape is not created // when doing first connection // Shape is created when we make the second connection
Next addition is the option to include top hierarchy nodes of the selected rig. There are three options for this:
1. Recreate the top hierarchy
2. Use existing top hierarchy
3. Just group the rig under a separate node
For example, you Rignerate the Arm setup and also want all the top hierarchy nodes, e.g. shoulder, upper_body, root etc, then you can include those transform nodes, so it maintains the hierarchy. Using existing hierarchy helps, if you are mirroring the rig. And you can group the rig under a single node, in case you want to move it somewhere else.
Right now the code generation is still in .MA format and even if its simplicity is very useful it adds limitations because certain type of code cannot be executed with MEL! I can understand the reasons behind it, but it has added complexity to the code generation.
For example,
createNode animCurveUA -n L_Index2_rotateZ; setAttr "L_Index2_rotateZ.wgt" no; setAttr "L_Index2_rotateZ.tan" 10; setAttr -s 3 "L_Index2_rotateZ.ktv[0:2]" -10 26.247651337441738 0 0 10 -77.759996825174923;Last line will silently fail and also the code following that line. This thread explains this: LINK
Hence the next step is to add special processing for special nodes, in this case animCurveUA. So instead of using MPlug.getSetAttrCmds() I will query attribute using normal mel commands. But, I need to generalize this into a proper system, instead of going and adding so many specific if conditions. This will also allow me to generate special commands for deformers as I have posted in one of my earlier posts. So that's the next goal.
Subscribe to:
Posts (Atom)




