How Do I Create a Pin Matrix Effect in Maya?
First published in 3DWorld Magazine 106 in 2008 – come on, I’m not that old, surely!? – This tutorial was set in motion by, apparently, someone called Dennis, in an email. Thanks, Dennis, you made me learn a truckload! Now you can all do it too.
Now, keep in mind that this was before Python Scripting was a big thing in Maya and that Komodo Edit is far better than some of the other free text editors out there. For more info on both of those click on the links above.
Right, it’s time to make a pin matrix, from nothing.
You can also get to the archive for this project here:
Sometimes, in the course of doing these Q&As, a question comes up that makes us have to use more than our current knowledge allows. This particular question is a case in point. Starting it, it soon became apparent that the original plan was not going to work out. It took a lot of searching and, in all honesty, a call to a colleague to get me on the right track. I mean, how was I going to get
individual UV sample information from an animation and apply it to an object? Answer: colorAtPoint.
Apparently, it’s been in the kit for quite some time but I have never needed to use it and, therefore, never knew it was there. I can now see a hundred uses for it. The colorAtPoint command is a simple way of extracting pixel information from a file based upon its UV coordinates. By sampling values from specific points across the surface of a texture, you can then pipe this info into an object to move it, scale it, whatever it. And if we use a script we can loop this to do it for every frame. Before we start building our pin matrix, however, there a few things that need to be understood.
We need to make sure that the aspect ration of our pins matches our image sequence. For this reason, I have decided to stick to a 640 by 480 image sequence. It is a standard resolution and is easily divisible. This way we can make a matrix that has 48 pins longitudinally and 64 pins latitudinally, and keep the aspect ratios the same so that our texture looks correct on our matrix. That makes us 3072 pins in all (ouch!) so is also important that you make sure to use NURBS for the pins as well as instances. (actually, you can do this with polygons these days [May 2017], but nurbs were still quite the thing at the time :)
Finally, and then we’ll get down to modelling, scripting and animating, a few words about UV coordinates. Think of them as X and Y in a front view pane. Your U values are like X and run horizontally and the V values are vertical.
Open a new scene in Maya. Now create a polygon plane with a width/height ratio of 64 to 48. Make the subdivisions the same. Now scale up the tile by 10 in all dimensions, rotate it 90 degrees in the X and move is 240 units in the Y. you should have something like the image above.
RMB click on the object to bring up its attribute marking menu and select Assign New Material > Lambert. In the Attribute Editor that pops up click on the Color checkerbox and navigate to your image sequence, or use the one supplied. Press six in the view pane. You should see your texture.
With the Attribute Editor still open, click the tickbox marked Use Image. Set your timeline to the length of your image sequence. In your animation Preferences, set the timeline animation playback speed to Play Every Frame. Now when you playback your animation you should see your animation playing on the tile.
In any view, create a nurbSphere with a radius of 5 at the origin. Rotate it -90 in the Y axis and then select the middle isoparm in the side view. From the Edit Nurbs menu select Detach Surfaces then delete the new back hemisphere. Select the remaining hemisphere and in component mode drag three new isoparms on the surface near the back. Now select Edit Nurbs > Insert Isoparms
In component mode still, switch your selection to Hulls and select the back three, scaling them down to about half their diameter. Now translate the last one back about 50 units until you have something that looks like a pin
In the front view move this new pin -315 in the X and to 5 in the Y until it fits in the bottom left hand square of your map grid. Rename it pin0 – that’s zero – and select Modify > Freeze Transformations followed by Edit > Delete By Type > History. Save your scene.
Selecting pin0, press CTRL+G to group it. Rename the group pins. Opening the group in the Outliner, select your pin and then select Edit > Duplicate Special > Option Box. In this option box be sure to set it to Instance and Parent. Change the translate Y to 10 and the Number of Copies to 47. Press Apply. Whoop-de-doo, you’ve got a vertical column of pins.
Select all 48 of these pins from the bottom up and in the duplicate window change the Y translate back to 0 and set the X to 10. Change the number from 47 to 63 and click Apply again. Eventually, you should have a grid of pins covering your grid. Save your scene. Select your pin group and under Display set Object Display > Bounding Box. Phew, that’s a bit faster to manipulate.
For the rest of the pin matrix, build a round edged front plate and set it about 50 unit in front of the pins. Duplicate it and make it twice as deep then move this behind the pins to make a back. Create four bolts with stems and put these in the corners. When you’re happy, group everything and sit it on the z-x plane for neatness. Or load in pinEdge.mb
colorAtPoint needs to specify an output (-o) of either RGB, RGBA or A where A is alpha, a U and V coordinate or samples(-u,-v and -su, -sv) and a source file. So, colorAtPoint -o RGB -u 0.5 -v 0.5 file1 returns the colour in the middle of file1 or colorAtPoint -o A -su 10 -sv 10 image1 returns 100 alpha samples uniformly spaced in 10 rows of 10.
Let’s try it. Open the script editor and type: colorAtPoint -o A -su 6 -sv 6 file1; Select all the text and press the numeric pad Enter key – This stops the text from being deleted as it runs the command. You should see a resultant string of 36 alpha samples corresponding to your image. They are sampled in the order you can see in the image, starting at the bottom left.
Now we need to find a way to use these results. Clear the Script Editor and type in: float $offsets = `colorAtPoint -o A -su 6 -sv 6 file1`;print $offsets; Press Enter as before and you get the same values but this time they are in an array called $offsets. Change print $offsets; to print $offsets; and you only get back 1 – the value of sample 20.
We have 3072 pins to animate here, so change your mel script to: float $offsets =`colorAtPoint -o A -su 64 -sv 48 file1`; print $offsets; Run this as before. To get these results takes longer, so we don’t want to use print statements. Move the print command onto a new line and put // to stop it from calculating.
Quick point about scripting. Scripting in the Script Editor is fine, but if you get more involved in scripting, use a text editor. You are able to save progressively without copying and pasting, but it can still be sourced and run in the script editor with ease. Notepad++ and Textpad (or Komodo Edit) are freely available and both handle syntax highlighting making commands easier to see.
We need to get this array into our pin’s Z translation. Add a few lines at the top of our script. Type on the first, string $obj = “pin”; and on the second string $attrib; $obj matches our pin name minus the zero. The $attrib will be used later on in our script, but declaring it at the top of a script is good practice in keeping all your variables together.
To use each array offset value in turn, we need to put them into a loop. Add this line: int $i; This sets us up a variable for the loop. Now add this line: for( $i = 0; $i < size($offsets); $i++ ) This loop starts $i at zero and finishes at the last offset array value. On two new lines add an open curly bracket and a close curly bracket.
Between these brackets create two new lines of script, the first: $attrib = ($obj + $i + “\n”);, the second print $attrib; The first takes our $name pin and adds the number of our offset onto the end of it then performs a carriage return starting a new line. The second line prints it. Select all the text and hit Enter. See all those pin names flashing by.
This is all well and good, but what we need $attrib to be is the pin Z transform attribute. Change the last part of the string \n to .translateZ so that the line now reads: $attrib = ($obj + $i + “.translateZ”); If you run this now you won’t have the carriage return, but your script editor will bring back the names pin0.translateZ through to pin3071.translateZ. This we can use.
Now comment out the print $attrib; Underneath it add this new line: setAttr $attrib $offsets[$i]; What this does is set each offset value to each point in turn through the loop. To make sure, move to around frame 75 in the timeline, select the script and hit Enter. If you look through the side view, you will see the points have moved a little in the Z, but not enough.
What we need is to increase the size of the offset. We do this be adding a multiplier to the amount attributed to $attrib. Change the last code line to: setAttr $attrib ($offsets[$i] * 10); Run the script again. The result is still too small. Change the 10 to 50 – after all our pin is 50 units long. Run the script again and check out the results. Dandy.
Assigning info at a single frame is fine, but we need to keyframe this if we want our pins to animate like our texture map. Add a new line under the last command: setKeyframe $attrib; If you run the script now, you’ll notice that it takes a little longer. If you move to another frame and repeat the script, you’ll see the keyframing results appear in the script editor.
What we have is a good script for a single frame. Now we need to loop this per script so it runs at every frame. Under the line $string $attrib add this: for ($frame = 1; $frame <= 100; $frame ++); On the next line put an open curly bracket. This creates a loop as long as our timeline and our animation sequence.
On the line after this curly bracket type in this command: currentTime $frame; The command currentTime is like clicking on a frame on the timeline, but in this loop it acts like a step forwards through the timeline. At the very end of the script put a closed curly bracket. Save your scene. Run your script. Now go have a cup of coffee. Or three. this could take some time to do. REally, a cup of tea…a big mug.
At each run of the setKeyframe command, the calculation time for the keyframing takes longer. This seems to be because of the accumulative effect required to add a new keyframe to your scene, so be prepared for a long wait. A useful idea is to break down the script into manageable chunks, say frames 1 – 20, 21 – 30 and so on so as to give you failsafe points to save your animation. Either that or, like a render, leave it running overnight, and wait for the results in the morning. Remember, in this instance alone, you are making 307,200 saved keyes for just four seconds of animation. Just keep reminding yourself, “Good things come to those who wait.”
When it comes to lighting the pin matrix, remember Chrome shaders do not really hold shadows well, so render off the shadow as a pass and comp it on later. Better than that, you might find that if you add a new coloured light to the right of the pin matrix, you’ll probably get more definition, as long as you make sure it does casts shadows.