If you haven't already watched our video for Delta Heavy's track Ghost then...do.
Lots of fun to make, one of the parts I was most excited by when Chris walked us through the animatic was working out how to recreate some classic Windows screensavers in Maya. I know that anyone who used a computer in the late 90s spent an unhealthy amount of time staring at these so I knew they'd have to feel authentic.
Firstly, the most famous one is probably 3D Pipes:
A: No fun
B: Incredibly time consuming
We only had 4 weeks start to finish on this job and there were over 70 shots to get done, so I opted to write a nice script to build the pipes for me. Unfortunately, time constraints meant I ended up building an embarrassingly messy script to build the pipes for me... Well, that's the realities of production, and it was a one use script so, hey-ho.
Fortunately, it's possible to actually get the original source for all the windows screensavers by downloading the NT 4.0 SDK. This gave me a lot of the parameters I needed to get the pipes looking right.
- We have 3 objects, longPipe, shortPipeJoint and endSphere.
- Step forward placing longPipes,
- Every step there's a 1/5 chance we'll turn in a random direction
- If the next step would collide with an existing pipe we have to turn
- When we turn there's a 1/3 chance that we place a sphere and a long pipe rather than a joint
- If there's nowhere left to go, stop
Putting this into Maya I wanted to use some premade meshes I'd created, place them in space, and animate their visibilty. I also needed to record their positions so we could keep running the script to create new pipes.
So, to jump straight into the code:
def makePipe(steps=10, startpos=(0,0,0), starttime=0, timestep=1, length=7, turnChance=0.2): longPipe = "longPipe" shortPipeJoint = "shortPipeJoint" endSphere = "endSphere"So, starting off the main function, we've got a bunch of default arguments and define the names of the objects we're going to place.
pos = startpos DHPipes.append(pos) dir = random.choice(directions) prevdir=dir t=starttime grp = cmds.createNode("transform", n="pipes1") last = grpWe initialise our variables we're going to update in the main loop with the arguments passed into the function. Also, we create a group to keep everything nice and tidy.
for s in range(steps): mv = getMove(dir, length) nextPos = addTuple(pos, mv) if random.random()<turnChance or nextPos in DHPipes: prevdir=dir dir=random.choice(possibleDirs(dir)) mv=getMove(dir, length) nextPos = addTuple(pos, mv)For each step we work out what the move will be. getMove just multiplies each item in dir by length. In hindsight I'm sure there's a neater way of doing that, maybe someone will tell me in the comments. Really it should just be called scaleTuple... Then, get the next position with addTuple (guess what that does)
while nextPos in DHPipes and i<20: dir=random.choice(possibleDirs(dir)) mv=getMove(dir, length) nextPos = addTuple(pos, mv) i+=1Next we check that we're not about to hit an existing pipe. DHPipes is a list of all positions of existing pipes. If we are we get a random direction to turn in (possibleDirs returns a list of directions which aren't forward or back from our current direction). I iterate i and check against it in the while because...well. I tend to always mess up whiles, cause an infinite loop and crash Maya. I just have that for safety.
if nextPos in DHPipes: print "Nowhere to go, so I'm stopping" break #give up, we can find a way to go if random.random() < 0.3333: new = cmds.duplicate(endSphere)[0] cmds.parent(new, grp) cmds.setAttr(new+".translateX", pos[0]) cmds.setAttr(new+".translateY", pos[1]) cmds.setAttr(new+".translateZ", pos[2]) cmds.setKeyframe(new+".visibility", v=0, t=t-1) cmds.setKeyframe(new+".visibility", v=1, t=t) obj = longPipe rot = longRot(dir) elif dir!=prevdir: obj = shortPipeJoint rot=elbowRot(dir, prevdir) else: obj = longPipe rot = longRot(dir) else: obj = longPipe rot = longRot(dir) new = cmds.duplicate(obj)[0] cmds.parent(new, grp) cmds.setAttr(new+".translateX", pos[0]) cmds.setAttr(new+".translateY", pos[1]) cmds.setAttr(new+".translateZ", pos[2]) cmds.setAttr(new+".rotateX", rot[0]) cmds.setAttr(new+".rotateY", rot[1]) cmds.setAttr(new+".rotateZ", rot[2]) cmds.setKeyframe(new+".visibility", v=0, t=t-1) cmds.setKeyframe(new+".visibility", v=1, t=t) pos = nextPos DHPipes.append(pos) t+=timestepThis last bit does the meat, it decides which object we're going to place based on the direction we're now moving in, then duplicates the base object, moves it to the right position and rotates it. longRot and elbowRot gets the rotation needed. I'd like to say I did the maths for allthat, but it was quicker to just do it by hand and hard code it. Deadlines!
That's about it for 3D Pipes. I'm slightly concerned I just wrote quite a long, boring blog post on what is quite a simple script, but... it's my first time! Hope someone finds it useful/interesting. I'll try to get better at this. I'm write up a quickie about the flowerbox screensaver shortly. That one's less code, more Maya, and it's pretty straightforward so should be a shorter post.