The little purple dude can wave his arms and legs about all he likes, he isn’t actually going anywhere currently. This is partly because there just isn’t anywhere for him to go and partly because we aren’t moving him at all, so even if there was somewhere for him to go, he wouldn’t go there. This will all be remedied in the next stage. What we will do is load in our landscape from a while back along with the morphing dude from the last section and allow him to move around against that ground. He won’t be able to get up or down the hills in the scenery, but at least you’ll be able to see that he is moving.
The first thing we need to do is extend the WalkMorph from the last example into a class I will call MoveMorph. I don’t mean extend in the Object Oriented context here- more in the “create a new class that contains most of the same code and a few different bits”kind of way.
There are quite a few significant changes we have made here; we have added a “step”and “oldTime”variable. The Step is the distance our figure moves in a certain time ( currently 1 millisecond). Initially I just set the step value in my system to a value that let the little purple dude walk at about the right speed. This seemed fine until I ran the program on my work computer. The shift from a GeForce 3 to an ATI rage Pro did absolutely no favours for my Java3D performance and although the animation ran at the right speed the size of step the purple dude made every frame resulted in a really slow movement at that framerate. The solution to this I used was to create a getStep method that gets the current time, compares it with the time last time the method was called and returns that time multiplied by the size of the step. That means that however many frames per second we are running at we have a reasonably consistent step size.
We will see it in action shortly.
Next up we have added a TransformGroup to the object, this will be set up to refer to the TransformGroup that our little purple dude belongs to, and it is this that we will perform all our actions on.
We also have a few new variables- three Booleans to represent the direction keys that are currently held down: rightDown, leftDown and fwDown each represent exactly what you would expect them to. We also have a new int called “upSum”which is used to keep an eye on how many keys are currently pressed. It is used because while a key is held down it seems to keep on firing awt keyevents. These are all used in the new processAWTEvents method:
You will notice that this doesn’t actually do much more than what we had before- it just resets the Booleans that we are using to measure whether the forward, left and right keys are pressed and adjusts the marker that tells us that there are keys being pressed –when upSum == 0 we know that our character should not be moving at all.
If we look at the ProcessStimulus method it is almost exactly the same as before, the only difference is that it has these two lines:
if (upSum>0) moveStep();These basically say that if any of the keys are currently down we call the moveStep method to walk us along otherwise we reset the wakeUpCriterion to be the initial keydown one.
So the main bulk of the changes happen in moveStep, which looks like this:
private void moveStep()There are a couple of things that are worth commenting on here. First up we reset upSum to 0. This is because the KEY_PRESSED events tend to trigger repeatedly while the key is pressed which increments upSum repeatedly but the KEY_RELEASED events don’t so it doesn’t get decremented back to zero so we find ourselves stuck in a loop where moveStep keeps being called but there is no command being given.
Next up we get the step value, as previously discussed, and then create a couple of Transform3D objects. One of these is to carry the current viewplatform transform, the other is to create the transforms we will combine it with to find our new position. What is worth noticing here is that we create ourTrans then we set it’s value through the TransformGroup.getTransform(transform3D) method, rather than having something more predictable like ourTrans = ourTG.getTransform(). The reason for this is that it avoids creating unnecessary objects and if they are never created then they won’t have to be garbage collected, which is a serious problem if we are trying to optimise for speed.
“But wait,”I hear you say, “You are creating those two Transform3D objects there anyway.”You are completely right. I have said time and again that the programs here are illustrative, not optimal. It is much easier to explain if you see them created and used than if I have created them elsewhere and they suddenly appear. You aren’t supposed to copy this code for your own work, you’re supposed to learn from it. And that includes learning from the stuff I do wrong.
if (rightDown || leftDown)We have now checked whether the right or left key is pressed and if it is we have rotated the tempTrans (which initialises to an identity matrix) around the Y axis by -stepper radians. Otherwise if the left key is pressed we rotate around the Y axis by +stepper radians. We assume that the user is not pressing left and right at once, but of course they will and if we are writing production code we should probably cater for this. We are reincrementing upSum every time so that if we don’t get the KEY_PRESSED triggers we have the right sum here.
if (fwDown)Finally we check whether the forward key is pressed. If it is we move tempTrans a step along the z axis. We do this after we have performed the rotation because then we rotate and then move a step in the direction we are facing, rather than moving a step in the direction we were facing already and then turning round afterwards. You can try this for yourself by taking a step and then turning and comparing it with turning then taking a step. Although we are combining the transformations we still perform them in a certain order to start with.
Finally we multiply our current transform3D by the one we have just generated which has the effect of transforming it to the new position. We use the setTransform of ourTG to apply the transform to the TransformGroup and set the wakeup criterion to be the next frame.
That is just about it for the MoveMorph behaviour so we will look at the WalkDude program that calls it.
Again, this is very similar to the last few examples so we will look at the differences. First up in the createSceneGraph() method we are creating a new TransformGroup to house our walking dude:
TransformGroup dudeGroup=new TransformGroup();The two Capability Bits we are setting here are used to tell Java3D that we want to be able to move the TransformGroup around the place once it has been created. This is used by BranchGroup.compile() to prevent it creating optimisations that would prevent us being able to do this. A little later we add our Morph (created exactly the same as before) into the group then create the MoveMorph using the TransformGroup as one of it’s parameters and then add the behaviour as a child of the TransformGroup. This looks superficially a bit circular and as though it shouldn’t work, but the way the MoveMorph works it is absolutely fine.
dudeGroup.addChild(ourMorph);Finally we create a SimpleLand from earlier and add everything to our BranchGroup:
String texturePath=filePath+"stone.jpg";Aside from that we have one other change in the init() method. Rather than using the nominal viewing transform we will create our own transform for the ViewPlatform, putting us behind and slightly above the purple dude. This isn’t strictly necessary, it just looks better.
ourView = u.getViewingPlatform();This works by creating a Transform3D to locate the viewPlatform, setting it’s translation to be above and behind the origin and then using the lookAt method of Transform3D to point it in the right direction. Literally lookAt makes the object being translated face towards the ViewPlatform, which actually makes the ViewPlatform face exactly away from our scene, so we invert it at that point.
We should now have our Animated Dude moving around while the walking animation plays, turning and walking around a landscape, in a way that looks a bit like this:

Previous: The Mobile Manikin | Back to the Index | Next: Up Hill and Down Dale