import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.SwingUtilities;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.Animator.RepeatBehavior;
import org.jdesktop.animation.timing.interpolation.Interpolator;
import org.jdesktop.animation.timing.interpolation.KeyFrames;
import org.jdesktop.animation.timing.interpolation.KeyTimes;
import org.jdesktop.animation.timing.interpolation.KeyValues;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
import org.jdesktop.animation.timing.interpolation.SplineInterpolator;
import org.jdesktop.animation.timing.triggers.ActionTrigger;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.Point;
import javax.swing.JComponent;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import java.applet.AudioClip;
import java.net.URL;
import org.jdesktop.animation.timing.TimingTarget;
import org.jdesktop.animation.timing.interpolation.KeyFrames;
/*
* MultiStepRace.java
*
* Created on May 3, 2007, 2:45 PM
*
* Copyright (c) 2007, Sun Microsystems, Inc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of the TimingFramework project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.UIManager;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* The full-blown demo with all of the bells and whistles. This one uses
* the facilities shown in all of the other variations, but adds
* both multi-step and non-linear interpolation. It does this by
* creating a KeyFrames object to hold the times/values/splines
* used for each segment of the race. It also adds an animation for
* the rotation of the car (since the car should turn as it goes around the
* curves) and sound effects (just to go completely overboard).
*
* @author Chet
*/
public class MultiStepRace {
protected Animator animator;
private SoundEffects soundEffects;
public static final int RACE_TIME = 10000;
/** Creates a new instance of BasicRace */
public MultiStepRace(String appName) {
RaceGUI basicGUI = new RaceGUI(appName);
// We're going to need a more involved PropertyRange object
// that has all curves of the track in it, as well as
// non-linear movement around the curves
Point values[] = {
TrackView.START_POS,
TrackView.FIRST_TURN_START, TrackView.FIRST_TURN_END,
TrackView.SECOND_TURN_START, TrackView.SECOND_TURN_END,
TrackView.THIRD_TURN_START, TrackView.THIRD_TURN_END,
TrackView.FOURTH_TURN_START,
TrackView.START_POS};
KeyValues keyValues = KeyValues.create(values);
// Calculate the keyTimes based on the distances that must be
// traveled on each leg of the journey
double totalDistance = 0;
double segmentDistance[] = new double[values.length];
for (int i = 0; i < (values.length - 1); ++i) {
segmentDistance[i] = values[i].distance(values[i + 1]);
totalDistance += segmentDistance[i];
}
segmentDistance[(values.length-1)] =
values[(values.length - 1)].distance(values[0]);
totalDistance += segmentDistance[(values.length-1)];
float times[] = new float[values.length];
float elapsedTime = 0.0f;
times[0] = 0.0f;
times[values.length - 1] = 1.0f;
for (int i = 0; i < (values.length - 2); ++i) {
times[i + 1] = elapsedTime + (float)(segmentDistance[i] / totalDistance);
elapsedTime = times[i + 1];
}
KeyTimes keyTimes = new KeyTimes(times);
// For realistic movement, we want a big acceleration
// on the straightaways
Interpolator initialSpline = new SplineInterpolator(1.00f, 0.00f, 0.2f, .2f);
Interpolator straightawaySpline = new SplineInterpolator(0.50f, 0.20f, .50f, .80f);
Interpolator curveSpline = new SplineInterpolator(0.50f, 0.20f, .50f, .80f);
Interpolator finalSpline = new SplineInterpolator(0.50f, 0.00f, .50f, 1.00f);
KeyFrames keyFrames = new KeyFrames(keyValues, keyTimes,
initialSpline, curveSpline, straightawaySpline, curveSpline,
straightawaySpline, curveSpline,
straightawaySpline, finalSpline);
// This PropertySetter enables the animation for the car movement all
// the way around the track
PropertySetter modifier = new PropertySetter(basicGUI.getTrack(),
"carPosition", keyFrames);
animator = new Animator(RACE_TIME, Animator.INFINITE,
RepeatBehavior.LOOP, modifier);
// Now create similar keyframes for rotation of car
keyValues = KeyValues.create(360, 315, 270, 225, 180, 135, 90, 45, 0);
Interpolator straightawayTurnSpline = new SplineInterpolator(1.0f, 0.0f, 1.0f, 0.0f);
Interpolator curveTurnSpline = new SplineInterpolator(0.0f, 0.5f, 0.5f, 1.0f);
keyFrames = new KeyFrames(keyValues, keyTimes,
straightawayTurnSpline, curveTurnSpline,
straightawayTurnSpline, curveTurnSpline,
straightawayTurnSpline, curveTurnSpline,
straightawayTurnSpline, curveTurnSpline);
modifier = new PropertySetter(basicGUI.getTrack(), "carRotation",
keyFrames);
animator.addTarget(modifier);
// Finally, add sound effects, triggered by the same animator
soundEffects = new SoundEffects(keyFrames);
animator.addTarget(soundEffects);
// Instead of manually tracking the events, have the framework do
// the work by setting up a trigger
JButton goButton = basicGUI.getControlPanel().getGoButton();
JButton stopButton = basicGUI.getControlPanel().getStopButton();
ActionTrigger trigger = ActionTrigger.addTrigger(goButton, animator);
stopButton.addActionListener(new Stopper(animator));
}
/**
* Handle clicks on the Stop button to stop the race
*/
private class Stopper implements ActionListener {
Animator timer;
Stopper(Animator timer) {
this.timer = timer;
}
public void actionPerformed(ActionEvent ae) {
timer.stop();
}
}
public static void main(String args[]) {
Runnable doCreateAndShowGUI = new Runnable() {
public void run() {
MultiStepRace race = new MultiStepRace("Multi Step Race");
}
};
SwingUtilities.invokeLater(doCreateAndShowGUI);
}
}
/**
* Copyright (c) 2007, Sun Microsystems, Inc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of the TimingFramework project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* Go/Stop buttons to control the animation
*
* @author Chet
*/
class RaceControlPanel extends JPanel {
/** Make these static so that outside classes can easily
* add themselves as listeners */
JButton goButton = new JButton("Go");
JButton stopButton = new JButton("Stop");
/**
* Creates a new instance of RaceControlPanel
*/
public RaceControlPanel() {
add(goButton);
add(stopButton);
}
public JButton getGoButton() {
return goButton;
}
public JButton getStopButton() {
return stopButton;
}
public void addListener(ActionListener listener) {
goButton.addActionListener(listener);
stopButton.addActionListener(listener);
}
}
/**
* Copyright (c) 2007, Sun Microsystems, Inc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of the TimingFramework project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* The GUI used by all of the different race demos.
* It contains a control panel (for the Go/Stop buttons) and a
* TrackView (where the race is rendered)
*
* @author Chet
*/
class RaceGUI {
private TrackView track;
private RaceControlPanel controlPanel;
/**
* Creates a new instance of RaceGUI
*/
public RaceGUI(String appName) {
UIManager.put("swing.boldMetal", Boolean.FALSE);
JFrame f = new JFrame(appName);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new BorderLayout());
// Add Track view
track = new TrackView();
f.add(track, BorderLayout.CENTER);
// Add control panel
controlPanel = new RaceControlPanel();
f.add(controlPanel, BorderLayout.SOUTH);
f.pack();
f.setVisible(true);
}
public TrackView getTrack() {
return track;
}
public RaceControlPanel getControlPanel() {
return controlPanel;
}
}
/**
* Copyright (c) 2007, Sun Microsystems, Inc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of the TimingFramework project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* Simple utility class used to load and play sound effects for
* MultiStepRace.
*
* @author Chet
*/
class SoundEffects implements TimingTarget {
AudioClip drivingClip;
AudioClip turningClip;
KeyFrames keyFrames;
/** Creates a new instance of SoundEffects */
public SoundEffects(KeyFrames keyFrames) {
this.keyFrames = keyFrames;
try {
URL url = SoundEffects.class.getResource("vroom.wav");
drivingClip = java.applet.Applet.newAudioClip(url);
url = SoundEffects.class.getResource("drift.wav");
turningClip = java.applet.Applet.newAudioClip(url);
} catch (Exception e) {
System.out.println("Problem loading track/car images: " + e);
}
}
/**
* Plays the driving clip
*/
public void drive() {
if (drivingClip != null) {
drivingClip.loop();
}
}
/**
* Stops current clips
*/
public void stop() {
if (drivingClip != null) {
drivingClip.stop();
}
if (turningClip != null) {
turningClip.stop();
}
}
/**
* Plays the turning clip
*/
public void turn() {
if (turningClip != null) {
turningClip.play();
}
}
// TimingTarget implementation
boolean pastFirstTurn = false;
boolean pastSecondTurn = false;
boolean pastThirdTurn = false;
boolean pastFourthTurn = false;
public void begin() {
drive();
pastFirstTurn = false;
pastSecondTurn = false;
pastThirdTurn = false;
pastFourthTurn = false;
}
public void end() {
stop();
}
/**
* This method figures out when the car hits one of the turns
* and plays the turn clip appropriately
*/
public void timingEvent(float fraction) {
if (!pastFirstTurn) {
if (keyFrames.getInterval(fraction) == 1) {
turn();
pastFirstTurn = true;
}
} else if (!pastSecondTurn) {
if (keyFrames.getInterval(fraction) == 3) {
turn();
pastSecondTurn = true;
}
} else if (!pastThirdTurn) {
if (keyFrames.getInterval(fraction) == 5) {
turn();
pastThirdTurn = true;
}
} else if (!pastFourthTurn) {
if (keyFrames.getInterval(fraction) == 7) {
turn();
pastFourthTurn = true;
}
}
}
public void repeat() {
pastFirstTurn = false;
pastSecondTurn = false;
pastThirdTurn = false;
pastFourthTurn = false;
}
}
/**
* Copyright (c) 2007, Sun Microsystems, Inc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of the TimingFramework project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* This class does the work of rendering the current view of the
* racetrack. It holds the car position and rotation and displays
* the car accordingly. The track itself is merely a background image
* that is copied the same on every repaint.
* Note that carPosition and carRotation are both JavaBean properties, which
* is exploited in the SetterRace and MultiStepRace variations.
*
* @author Chet
*/
class TrackView extends JComponent {
BufferedImage car;
BufferedImage track;
Point carPosition;
double carRotation = 0;
int trackW, trackH;
int carW, carH, carWHalf, carHHalf;
/** Hard-coded positions of interest on the track */
static final Point START_POS = new Point(450, 70);
static final Point FIRST_TURN_START = new Point(130, 70);
static final Point FIRST_TURN_END = new Point(76, 127);
static final Point SECOND_TURN_START = new Point(76, 404);
static final Point SECOND_TURN_END = new Point(130, 461);
static final Point THIRD_TURN_START = new Point(450, 461);
static final Point THIRD_TURN_END = new Point(504, 404);
static final Point FOURTH_TURN_START = new Point(504, 127);
/** Creates a new instance of TrackView */
public TrackView() {
try {
car = ImageIO.read(TrackView.class.getResource("beetle_red.gif"));
track = ImageIO.read(TrackView.class.getResource("track.jpg"));
} catch (Exception e) {
System.out.println("Problem loading track/car images: " + e);
}
carPosition = new Point(START_POS.x, START_POS.y);
carW = car.getWidth();
carH = car.getHeight();
carWHalf = carW / 2;
carHHalf = carH / 2;
trackW = track.getWidth();
trackH = track.getHeight();
}
public Dimension getPreferredSize() {
return new Dimension(trackW, trackH);
}
/**
* Render the track and car
*/
public void paintComponent(Graphics g) {
// First draw the race track
g.drawImage(track, 0, 0, null);
// Now draw the car. The translate/rotate/translate settings account
// for any nonzero carRotation values
Graphics2D g2d = (Graphics2D)g.create();
g2d.translate(carPosition.x, carPosition.y);
g2d.rotate(Math.toRadians(carRotation));
g2d.translate(-(carPosition.x), -(carPosition.y));
// Now the graphics has been set up appropriately; draw the
// car in position
g2d.drawImage(car, carPosition.x - carWHalf, carPosition.y - carHHalf, null);
}
/**
* Set the new position and schedule a repaint
*/
public void setCarPosition(Point newPosition) {
repaint(0, carPosition.x - carWHalf, carPosition.y - carHHalf,
carW, carH);
carPosition.x = newPosition.x;
carPosition.y = newPosition.y;
repaint(0, carPosition.x - carWHalf, carPosition.y - carHHalf,
carW, carH);
}
/**
* Set the new rotation and schedule a repaint
*/
public void setCarRotation(double newDegrees) {
carRotation = newDegrees;
// repaint area accounts for larger rectangular are because rotate
// car will exceed normal rectangular bounds
repaint(0, carPosition.x - carW, carPosition.y - carH,
2 * carW, 2 * carH);
}
}
|