Saturday, July 11, 2009

Animations in Java 3D

Wow, I haven't done much on this blog in a while... I haven't done a whole lot with the game lately but I got it up on github if anyone is interested: http://github.com/caspian311/boardgame.

So I have been messing around a bit with how the pieces move. The way it use to get done was by using keyframes. The problem with that there was no way to tell when you had reached the end of the animation to signal something else to happen. So I finally just switched it over to using Behaviors instead.

From my understanding all that you need to do is extend the javax.media.j3d.Behavior class, set the scheduling bounds (setSchedulingBounds()), then add it to the scene graph. Then you to override two methods initialize() and processStimulus(Enumeration stimuli).

In the iniatilize method you specify what event, or wake up criterion, will trigger the behavior. For animations you can make it based on a certain number of frames has gone by or a specified amount of time has gone by or it an AWT even occurred.

In the processStimulus method you loop through all the stimuli looking for the wake up criterion that you specified. Then you do whatever you want done (move/rotate/whatever something a bit) then if you want to wake up when the next time a criterion happens you call the wakeupOn() method.

Ok I'm not great at describing it, let's just see some code...


public class AnimationBehviour extends Behavior {
private static final float MOVEMENT_SPEED = 0.1f;
private static final int ANIMATION_WAITING = 10;
private final ListenerManager finishedListenerManager
= new ListenerManager();
private final TransformGroup transformGroup;
private final Vector3f currentLocation;
private final Vector3f moveToLocation;
private boolean atLocation;

public AnimationBehviour(Bounds bounds, TransformGroup
transformGroup, Vector3f currentLocation, Vector3f
moveToLocation) {
this.transformGroup = transformGroup;
this.currentLocation = currentLocation;
this.moveToLocation = moveToLocation;

setSchedulingBounds(bounds);
}

@Override
public void initialize() {
wakeupOn(new WakeupOnElapsedTime(10));
}

@SuppressWarnings("unchecked")
@Override
public void processStimulus(Enumeration stimuli) {
while (stimuli.hasMoreElements()) {
WakeupCriterion criterion = (WakeupCriterion)
stimuli.nextElement();
if (criterion instanceof WakeupOnElapsedTime) {
moveCloser();

if (!atLocation) {
wakeupOn(new WakeupOnElapsedTime(ANIMATION_WAITING));
} else {
setEnable(false);
finishedListenerManager.notifyListeners();
}
}
}
}

private void moveCloser() {
Transform3D transformation = new Transform3D();
transformGroup.getTransform(transformation);

if (isFinished(currentLocation)) {
atLocation = true;
} else {
updatePosition(currentLocation, moveToLocation);
transformation.set(currentLocation);
}

transformGroup.setTransform(transformation);
}

private void updatePosition(Vector3f currentPositionVector,
Vector3f endingPointVector) {
float[] currentPosition = new float[3];
currentPositionVector.get(currentPosition);
float[] endingPoint = new float[3];
endingPointVector.get(endingPoint);

for (int i = 0; i < currentPosition.length; i++) {
if (currentPosition[i] < endingPoint[i]) {
currentPosition[i] += MOVEMENT_SPEED;
} else if (currentPosition[i] > endingPoint[i]) {
currentPosition[i] -= 0.1f;
}
}

currentPositionVector.set(currentPosition);
}

private boolean isFinished(Vector3f currentPositionVector) {
boolean finished = false;

if (currentPositionVector.x <= moveToLocation.x + .1
&& currentPositionVector.x >= moveToLocation.x - .1) {
if (currentPositionVector.y <= moveToLocation.y + .1
&& currentPositionVector.y >= moveToLocation.y - .1) {
if (currentPositionVector.z <= moveToLocation.z + .1
&& currentPositionVector.z >= moveToLocation.z - .1) {
finished = true;
}
}
}

return finished;
}

public void addAnimationFinishedListener(
IListener animationFinishedListener) {
finishedListenerManager.addListener(
animationFinishedListener);
}
}


Also, since you are specifying whether or not to continue on each iteration you can add listeners to when the animation is finished and notify them. Here's how I call this class.

public void animateAlongPath(List path) {
this.path = path;
animateNextStep(getNextStep());
}

private void animateNextStep(Vector3f currentLocation) {
final Vector3f moveToLocation = getNextStep();
if (moveToLocation != null) {
AnimationBehviour animationBehaviour =
new AnimationBehviour(bounds, transformGroup,
currentLocation, moveToLocation);
animationBehaviour.addAnimationFinishedListener(
new IListener() {
public void fireEvent() {
parentGroup.removeChild(animationGroup);
animateNextStep(moveToLocation);
}
});

animationGroup.addChild(animationBehaviour);
parentGroup.addChild(animationGroup);
}
}

private Vector3f getNextStep() {
Vector3f nextStep = null;
if (pathIndex < path.size()) {
nextStep = path.get(pathIndex);
pathIndex++;
}
return nextStep;
}

So the piece will walk through the path of locations specified and when the animation is done for a given step it will notify the next step animation.

No comments:

Post a Comment