//
// Rocket Science 101 Version 1.1 - Copyright (C) 1996, 2001 by Lorenz Wiest
//
//
//
//
// "Contrary to popular opinion, filename is not from the English terms
// name and file. It is pronounced 'feh-LEE-nah-mee' and comes from the
// Tibetan, meaning 'What it is ?'"
// -- David Swift on CompuServe
//
//
// "Three things are certain:
// Death, taxes, and lost data.
// Guess which has occurred."
//
// "Chaos reigns within.
// Reflect, repent, and reboot.
// Order shall return."
//
// "Yesterday it worked,
// Today it is not working.
// The web is like that."
//
// "Serious error.
// All shortcuts have disappeared.
// Screen. Mind. Both are blank."
//
// "To have no errors
// Would be life without meaning.
// No struggle, no joy."
//
// -- Error Messages of the BeOS's web-browser (actually Japanese haiku!)
//
//
// GNU License
// =============================================================================
// This program is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software
// Foundation; either version 2 of the License, or (at your option) any later
// version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
// details.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the Free Software Foundation, Inc., 675 Mass
// Ave, Cambridge, MA 02139, USA.
//
//
// Notes
// =============================================================================
// - fix in Gadget class
// - Fixed superfluous image reload in run()
// - Code is Java 1.0.2 compliant and should run on any Java-enabled web browser.
//
import java.applet.*;
import java.awt.*;
import java.awt.image.*;
////////////////////////////////////////////////////////////////////////////////
// //
// RocketScience //
// //
////////////////////////////////////////////////////////////////////////////////
public final class RocketScience extends Applet implements Runnable, Sleeper {
final static int WIDTH = 641;
final static int HEIGHT = 481;
final static int MX = WIDTH / 2;
final static int MY = HEIGHT / 2;
final static int DELTAT = 20; // timespan [s] per timestep
final static int TSTEPS = 40; // timesteps between frames
final static float ZOOM = (float)(150.0 / Spacecraft.GEOSTATIONARY_RADIUS);
final static int NOTE_TIMERID = 1;
final static int DEMO_TIMERID = 2;
final static int SOYUZ_TIMERID = 3;
final static int DEMO_TIME = 120; // in [s]
final static int SOYUZ_LAUNCH_TIME = 5;
final static int NOTE_TIME = 2;
Image offScreen;
Graphics offScreenGC;
Rectangle offScreenClipRect;
Spacecraft Atlantis;
Spacecraft Soyuz;
MirSpacecraft Mir;
Bob AtlantisBob;
MirBob MirBob;
Bob SoyuzBob;
Bob AtlantisJetBob;
Bob SoyuzJetBob;
Bob DotsBob;
Bob EarthBob;
Bob NoteBob;
Bob AtlantisLabelBob;
Bob MirLabelBob;
Bob SoyuzLabelBob;
Bob TachoBob;
Image stripMirImage;
Image stripAtlantisImage;
Image stripSoyuzImage;
Image stripJetImage;
Image stripDotsImage;
Image stripEarthImage;
Image stripNoteImage;
Image stripLabelsImage;
Image stripTachoImage;
Gadget StartGadget;
Gadget BrakeGadget;
Gadget SpeedGadget;
Gadget ClearGadget;
Image stripStartGadget;
Image stripBrakeGadget;
Image stripSpeedGadget;
Image stripClearGadget;
Timer NoteTimer;
Timer DemoTimer;
Timer SoyuzTimer;
boolean InDemoMode = true;
boolean DoResetGame = true;
boolean DoRejectMouseEvents = true;
Autopilot Autopilot = new Autopilot();
DirtyRect dirtyRect;
Thread gameThread = null;
boolean AllImagesLoaded = false;
//*** Applet ******************************************************************
public String getAppletInfo() {
return("RocketScience 101 Version 1.1 - Build 030201 - Copyright 1996, 2001 by Lorenz Wiest");
}
//*** Applet ******************************************************************
public void start() {
// Initialize backbuffer
if(offScreenGC==null) {
offScreen = this.createImage(WIDTH, HEIGHT);
offScreenGC = offScreen.getGraphics();
offScreenClipRect = new Rectangle(0, 0, WIDTH, HEIGHT);
}
// Initialize spacecrafts
Atlantis = new Spacecraft(0, 0, 0, 0, true);
Soyuz = new Spacecraft(0, 0, 0, 0, false);
Mir = new MirSpacecraft(0, 0, 0, 0, true);
// Init Timer
NoteTimer = new Timer(this, NOTE_TIMERID );
DemoTimer = new Timer(this, DEMO_TIMERID );
SoyuzTimer = new Timer(this, SOYUZ_TIMERID);
// Init dirtyRect
dirtyRect = new DirtyRect();
InDemoMode = true;
DoResetGame = true;
DoRejectMouseEvents = true;
if(gameThread==null) {
gameThread = new Thread(this, "RocketScience 101 Game Thread");
gameThread.start();
}
}
//*** Applet ******************************************************************
public void stop() {
if(gameThread!=null && gameThread.isAlive())
gameThread.stop();
gameThread = null;
SoyuzTimer.stop();
DemoTimer.stop();
NoteTimer.stop();
}
//*** Applet ******************************************************************
public void run() {
final int FRAME_SLEEP = 50;
// Initialize backbuffer
if(offScreenGC==null) {
offScreen = this.createImage(WIDTH, HEIGHT);
offScreenGC = offScreen.getGraphics();
offScreenClipRect = new Rectangle(0, 0, WIDTH, HEIGHT);
}
// Load image strips via MediaTracker
if(AllImagesLoaded==false) {
offScreenGC.setColor(Color.white);
offScreenGC.fillRect(0, 0, WIDTH, HEIGHT);
stripMirImage = this.getImage(this.getCodeBase(), "mir.gif");
stripAtlantisImage = this.getImage(this.getCodeBase(), "atlantis.gif");
stripSoyuzImage = this.getImage(this.getCodeBase(), "soyuz.gif");
stripJetImage = this.getImage(this.getCodeBase(), "jet.gif");
stripDotsImage = this.getImage(this.getCodeBase(), "dots.gif");
stripEarthImage = this.getImage(this.getCodeBase(), "earth.gif");
stripNoteImage = this.getImage(this.getCodeBase(), "note.gif");
stripLabelsImage = this.getImage(this.getCodeBase(), "labels.gif");
stripTachoImage = this.getImage(this.getCodeBase(), "tacho.gif");
stripStartGadget = this.getImage(this.getCodeBase(), "reset.gif");
stripBrakeGadget = this.getImage(this.getCodeBase(), "brake.gif");
stripSpeedGadget = this.getImage(this.getCodeBase(), "speed.gif");
stripClearGadget = this.getImage(this.getCodeBase(), "clear.gif");
MediaTracker tracker = new MediaTracker(this);
tracker.addImage(stripMirImage, 0);
tracker.addImage(stripAtlantisImage, 1);
tracker.addImage(stripSoyuzImage, 2);
tracker.addImage(stripJetImage, 3);
tracker.addImage(stripDotsImage, 4);
tracker.addImage(stripEarthImage, 5);
tracker.addImage(stripNoteImage, 6);
tracker.addImage(stripLabelsImage, 7);
tracker.addImage(stripStartGadget, 8);
tracker.addImage(stripBrakeGadget, 9);
tracker.addImage(stripSpeedGadget, 10);
tracker.addImage(stripClearGadget, 11);
tracker.addImage(stripTachoImage, 12);
for(int i=0; i<13; i++) {
try { tracker.waitForID(i); } catch (InterruptedException e) {}
paintTracker( 100 * i / (13-1));
}
AllImagesLoaded = true;
}
// Initialize bobs
initBobs();
MirBob.setFrameRow(MIRBOB_ROW_1SOYUZ);
Autopilot.setSoyuzLaunchCount(2);
// Initialize gadgets
StartGadget = new Gadget(155, 450, stripStartGadget, offScreen);
BrakeGadget = new Gadget(255, 450, stripBrakeGadget, offScreen);
SpeedGadget = new Gadget(328, 450, stripSpeedGadget, offScreen);
ClearGadget = new Gadget(428, 450, stripClearGadget, offScreen);
// Game loop starts here
InDemoMode = true;
DoRejectMouseEvents = false;
DoResetGame = true;
boolean saveDemoMode;
while(Thread.currentThread()==gameThread) {
if(DoResetGame)
resetGame();
DoResetGame = false;
if(InDemoMode)
enterDemoMode();
else
exitDemoMode();
saveDemoMode = InDemoMode;
while(!DoResetGame && saveDemoMode==InDemoMode) {
Autopilot.nextManeuver(MirBob, SoyuzBob, Mir, Soyuz, DELTAT);
if((!Mir.blockAtlantisDocking()) && (!InDemoMode) && (!Atlantis.hasDocked()))
handleAtlantisRendezvous();
for(int j=0; j SENSERADIUS_SQR) {
if(NoteBob.isVisible() && NoteBob.getFrameRow()!=NOTEBOB_ROW_OK4DOCKING)
killNote();
}
else {
float dvx = Mir.vx - Atlantis.vx; // dvx, dvy are in Atlantis-centered CS
float dvy = Mir.vy - Atlantis.vy;
float dot = dvx * dx + dvy * dy;
if(dot < 0) { // ATLANTIS moves *TOWARD* MIR
float approachVelocity = (float)Math.sqrt(dvx * dvx + dvy * dvy);
int percent = (int)(approachVelocity / CONVERT_TO_PERCENT);
if(percent>100) percent = 100;
if(percent<20) {
setNote(NOTEBOB_ROW_LOOKSGOOD);
paintTachoBob(TACHOBOB_ROW_GREEN, percent);
TachoBob.setVisible(true);
}
else if(percent<40) {
setNote(NOTEBOB_ROW_LOOKSGOOD);
paintTachoBob(TACHOBOB_ROW_ORANGE, percent);
TachoBob.setVisible(true);
}
else {
setNote(NOTEBOB_ROW_TOOFAST);
paintTachoBob(TACHOBOB_ROW_RED, percent);
TachoBob.setVisible(true);
}
if(percent<20 && distMA_sqr < DOCKINGRADIUS_SQR) {
setNote(NOTEBOB_ROW_DOCKED);
Atlantis.setDocked(true);
disableGadgets();
}
}
}
}
//******************************************************************************
private synchronized void paintTachoBob(int rowIndex, int percent) {
blit(TachoBob.stripImage, 0, 0, stripTachoImage,
0, -TACHOBOB_ROW_MASTER * TachoBob.bitmapHeight,
TachoBob.width, TachoBob.height);
int pixelPercentage = (int)(percent * 0.01 * TachoBob.width);
blit(TachoBob.stripImage, 0, 0, stripTachoImage,
0, -rowIndex * TachoBob.bitmapHeight,
pixelPercentage, TachoBob.height);
int markerPos = pixelPercentage - TACHOBOB_MARKERWIDTH / 2 ;
blit(TachoBob.stripImage, markerPos, 0, stripTachoImage,
markerPos, -TACHOBOB_ROW_MARKER * TachoBob.bitmapHeight,
TACHOBOB_MARKERWIDTH, TachoBob.height);
}
//******************************************************************************
public void updateBobPositions(Autopilot Autopilot) {
final int PIXEL_SENSERADIUS_SQR = 100 * 100;
int dx;
int dy;
int distAM_sqr;
MirBob.x = (int)(MX + Mir.x * ZOOM + MirBob.offsetX);
MirBob.y = (int)(MY - Mir.y * ZOOM + MirBob.offsetY);
// Calculate Atlantis Bob position
if(Atlantis.hasDocked()) {
AtlantisBob.x = MirBob.x + MirBob.transX[MirBob.getFrameNr()];
AtlantisBob.y = MirBob.y + MirBob.transY[MirBob.getFrameNr()];
AtlantisJetBob.setVisible(false);
if(MirBob.getFrameNr()!=AtlantisBob.getFrameNr())
AtlantisBob.setFrameNr(MirBob.getFrameNr());
}
else {
AtlantisBob.x = (int)(MX + Atlantis.x * ZOOM + AtlantisBob.offsetX);
AtlantisBob.y = (int)(MY - Atlantis.y * ZOOM + AtlantisBob.offsetY);
if(!Mir.blockAtlantisDocking()) {
dx = MirBob.x - MirBob.offsetX - (AtlantisBob.x - AtlantisBob.offsetX);
dy = -(MirBob.y - MirBob.offsetY - (AtlantisBob.y - AtlantisBob.offsetY));
if((dx * dx + dy * dy) < PIXEL_SENSERADIUS_SQR)
AtlantisBob.setFrameNr(-dx, -dy);
}
// Calc Atlantis Jet position
if(Atlantis.thrust!= Spacecraft.NO_THRUST) {
AtlantisJetBob.setVisible(true);
AtlantisJetBob.x = AtlantisBob.x - AtlantisBob.offsetX + AtlantisJetBob.offsetX;
AtlantisJetBob.y = AtlantisBob.y - AtlantisBob.offsetY + AtlantisJetBob.offsetY;
AtlantisJetBob.setFrameNr(-Atlantis.thrustX, -Atlantis.thrustY);
}
else
AtlantisJetBob.setVisible(false);
}
// Calc Soyuz position
if(SoyuzBob.isVisible()) {
SoyuzBob.x = (int)(MX + Soyuz.x * ZOOM + SoyuzBob.offsetX);
SoyuzBob.y = (int)(MY - Soyuz.y * ZOOM + SoyuzBob.offsetY);
dx = MirBob.x - MirBob.offsetX - (SoyuzBob.x - SoyuzBob.offsetX);
dy = -(MirBob.y - MirBob.offsetY - (SoyuzBob.y - SoyuzBob.offsetY));
if((dx * dx + dy * dy) < PIXEL_SENSERADIUS_SQR)
SoyuzBob.setFrameNr(dx, dy);
if(Soyuz.thrust>Spacecraft.NO_THRUST)
SoyuzBob.setFrameNr(-Autopilot.soyuzVelocityDirX, -Autopilot.soyuzVelocityDirY);
}
// Calc Soyuz Jets position
if(Soyuz.thrust!=Spacecraft.NO_THRUST) {
Soyuz.thrust--;
SoyuzJetBob.setVisible(true);
SoyuzJetBob.x = (int)(MX + Soyuz.x * ZOOM + SoyuzJetBob.offsetX);
SoyuzJetBob.y = (int)(MY - Soyuz.y * ZOOM + SoyuzJetBob.offsetY);
SoyuzJetBob.setFrameNr(Autopilot.soyuzVelocityDirX, Autopilot.soyuzVelocityDirY);
}
else
SoyuzJetBob.setVisible(false);
// Calc Mir position
dx = AtlantisBob.x - AtlantisBob.offsetX - (MirBob.x - MirBob.offsetX);
dy = -(AtlantisBob.y - AtlantisBob.offsetY - (MirBob.y - MirBob.offsetX));
distAM_sqr = dx * dx + dy * dy;
if((!Mir.blockAtlantisDocking()) && (distAM_sqr < PIXEL_SENSERADIUS_SQR) && (!Atlantis.hasDocked()))
MirBob.setFrameNr(dx, dy);
else {
if(SoyuzBob.isVisible()) {
dx = SoyuzBob.x - SoyuzBob.offsetX - (MirBob.x - MirBob.offsetX);
dy = -(SoyuzBob.y - SoyuzBob.offsetY - (MirBob.y - MirBob.offsetY));
if((dx * dx + dy * dy) < PIXEL_SENSERADIUS_SQR) {
if(Autopilot.getManeuverType()==Autopilot.OM_HIDESOYUZ) {
if(Autopilot.getManeuverClass()==Autopilot.OMC_LODOCK) {
MirBob.setFrameNr(dy, -dx);
if((distAM_sqr < PIXEL_SENSERADIUS_SQR) && (!InDemoMode))
setNote(NOTEBOB_ROW_SOYUZ28);
}
if(Autopilot.getManeuverClass()==Autopilot.OMC_HIDOCK) {
MirBob.setFrameNr(-dy, dx);
if((distAM_sqr < PIXEL_SENSERADIUS_SQR) && (!InDemoMode))
setNote(NOTEBOB_ROW_SOYUZ10);
}
}
if(Autopilot.getManeuverClass()==Autopilot.OMC_LOSEPR) MirBob.setFrameNr( dy, -dx);
if(Autopilot.getManeuverClass()==Autopilot.OMC_HISEPR) MirBob.setFrameNr(-dy, dx);
}
}
}
// Calc Note position
if(NoteBob.isVisible()) {
if(AtlantisBob.x < MirBob.x) {
NoteBob.x = MirBob.x + MirBob.width;
NoteBob.y = MirBob.y - NOTEBOB_HALFHEIGHT;
NoteBob.setFrameNr(NOTEBOB_FRM_RIGHT);
}
else {
NoteBob.x = MirBob.x - NoteBob.width;
NoteBob.y = MirBob.y - NOTEBOB_HALFHEIGHT;
NoteBob.setFrameNr(NOTEBOB_FRM_LEFT);
}
}
// Calc Tacho position
if(TachoBob.isVisible()) {
if(AtlantisBob.x < MirBob.x) {
TachoBob.x = NoteBob.x + TACHOBOB_NOTEOFFSET_RIGHT;
TachoBob.y = NoteBob.y + TACHOBOB_NOTEOFFSET_DOWN;
}
else {
TachoBob.x = NoteBob.x + TACHOBOB_NOTEOFFSET_LEFT;
TachoBob.y = NoteBob.y + TACHOBOB_NOTEOFFSET_DOWN;
}
}
// Calc DemoMode labels position
if(InDemoMode) {
AtlantisLabelBob.x = AtlantisBob.x + AtlantisBob.width;
AtlantisLabelBob.y = AtlantisBob.y - LABELBOB_HALFHEIGHT;
MirLabelBob.x = MirBob.x + MirBob.width;
MirLabelBob.y = MirBob.y - LABELBOB_HALFHEIGHT;
if(SoyuzBob.isVisible()) {
SoyuzLabelBob.x = SoyuzBob.x + SoyuzBob.width;
SoyuzLabelBob.y = SoyuzBob.y - LABELBOB_HALFHEIGHT;
if((Autopilot.getManeuverClass()==Autopilot.OMC_HIDOCK) ||
(Autopilot.getManeuverClass()==Autopilot.OMC_HISEPR) )
SoyuzLabelBob.setFrameRow(LABELBOB_ROW_SOYUZ28);
else
SoyuzLabelBob.setFrameRow(LABELBOB_ROW_SOYUZ10);
}
}
EarthBob.cycleFrameNr();
}
/*******************************************************************************
* *
* Core Render Engine *
* *
*******************************************************************************/
public final void blit(Image destImage, int destX, int destY, Image srcImage, int srcX, int srcY, int w, int h) {
blit(destImage.getGraphics(), destX, destY, srcImage, srcX, srcY, w, h);
}
public final void blit(Graphics destGC, int destX, int destY, Image srcImage, int srcX, int srcY, int w, int h) {
Graphics g = destGC.create();
g.clipRect(destX, destY, w, h);
g.drawImage(srcImage, srcX, srcY, null);
g.dispose();
}
/*******************************************************************************
* *
* Bob Render Engine *
* *
*******************************************************************************/
final static int ATLANTISBOB_WIDTH = 39;
final static int ATLANTISBOB_HEIGHT = 39;
final static int ATLANTISBOB_OFFSETX = -19;
final static int ATLANTISBOB_OFFSETY = -19;
final static int ATLANTISBOB_BITMAPWIDTH = 40;
final static int ATLANTISBOB_BITMAPHEIGHT = 0;
final static int ATLANTISBOB_COLSOFFRAMES = 16;
final static int SOYUZBOB_WIDTH = 21;
final static int SOYUZBOB_HEIGHT = 21;
final static int SOYUZBOB_OFFSETX = -10;
final static int SOYUZBOB_OFFSETY = -10;
final static int SOYUZBOB_BITMAPWIDTH = 22;
final static int SOYUZBOB_BITMAPHEIGHT = 0;
final static int SOYUZBOB_COLSOFFRAMES = 16;
final static int MIRBOB_WIDTH = 45;
final static int MIRBOB_HEIGHT = 45;
final static int MIRBOB_OFFSETX = -22;
final static int MIRBOB_OFFSETY = -22;
final static int MIRBOB_BITMAPWIDTH = 46;
final static int MIRBOB_BITMAPHEIGHT = 46;
final static int MIRBOB_COLSOFFRAMES = 16;
public final static int MIRBOB_ROW_0SOYUZ = 0; // stored in MirBob.frameRow
public final static int MIRBOB_ROW_1SOYUZ = 1;
public final static int MIRBOB_ROW_2SOYUZ = 2;
final static int ATLANTISJETBOB_WIDTH = 43;
final static int ATLANTISJETBOB_HEIGHT = 43;
final static int ATLANTISJETBOB_OFFSETX = -21;
final static int ATLANTISJETBOB_OFFSETY = -21;
final static int ATLANTISJETBOB_BITMAPWIDTH = 44;
final static int ATLANTISJETBOB_BITMAPHEIGHT = 0;
final static int ATLANTISJETBOB_COLSOFFRAMES = 16;
final static int SOYUZJETBOB_WIDTH = 43;
final static int SOYUZJETBOB_HEIGHT = 43;
final static int SOYUZJETBOB_OFFSETX = -21;
final static int SOYUZJETBOB_OFFSETY = -21;
final static int SOYUZJETBOB_BITMAPWIDTH = 44;
final static int SOYUZJETBOB_BITMAPHEIGHT = 0;
final static int SOYUZJETBOB_COLSOFFRAMES = 16;
final static int DOTSBOB_WIDTH = 6;
final static int DOTSBOB_HEIGHT = 6;
final static int DOTSBOB_OFFSETX = -2; // Might be a little off
final static int DOTSBOB_OFFSETY = -2; // Might be a little off
final static int DOTSBOB_BITMAPWIDTH = 7;
final static int DOTSBOB_BITMAPHEIGHT = 7;
final static int DOTSBOB_COLSOFFRAMES = 3;
public final static int DOT_FRM_ATLANTIS = 0; // stored in DotBob.frameNr
public final static int DOT_FRM_ATLANTIS_THRUSTS = 1;
public final static int DOT_FRM_MIR = 2;
final static int EARTHBOB_WIDTH = 47;
final static int EARTHBOB_HEIGHT = 47;
final static int EARTHBOB_OFFSETX = -24;
final static int EARTHBOB_OFFSETY = -24;
final static int EARTHBOB_BITMAPWIDTH = 47;
final static int EARTHBOB_BITMAPHEIGHT = 0;
final static int EARTHBOB_COLSOFFRAMES = 16;
final static float EARTHBOB_RADIUS = EARTHBOB_WIDTH / ( 2 * ZOOM ); // ???
final static int NOTEBOB_WIDTH = 134;
final static int NOTEBOB_HEIGHT = 17;
final static int NOTEBOB_OFFSETX = 0;
final static int NOTEBOB_OFFSETY = 0;
final static int NOTEBOB_BITMAPWIDTH = 134;
final static int NOTEBOB_BITMAPHEIGHT = 17;
final static int NOTEBOB_COLSOFFRAMES = 2;
final static int NOTEBOB_HALFHEIGHT = 9;
final static int NOTEBOB_FRM_RIGHT = 0; // stored in NoteBob.frameNr
final static int NOTEBOB_FRM_LEFT = 1;
final static int NOTEBOB_ROW_OK4DOCKING = 0; // stored in NoteBob.frameRow
final static int NOTEBOB_ROW_SOYUZ10 = 1;
final static int NOTEBOB_ROW_SOYUZ28 = 2;
final static int NOTEBOB_ROW_TOOCLOSE = 3;
final static int NOTEBOB_ROW_TOOFAST = 4;
final static int NOTEBOB_ROW_DRIFTING = 5;
final static int NOTEBOB_ROW_LOOKSGOOD = 6;
final static int NOTEBOB_ROW_DOCKED = 7;
final static int TACHOBOB_WIDTH = 124;
final static int TACHOBOB_HEIGHT = 10;
final static int TACHOBOB_OFFSETX = 0;
final static int TACHOBOB_OFFSETY = 0;
final static int TACHOBOB_BITMAPWIDTH = 0;
final static int TACHOBOB_BITMAPHEIGHT = 10;
final static int TACHOBOB_COLSOFFRAMES = 1;
final static int TACHOBOB_ROW_MASTER = 1; // stored in TachoBob.frameRow
final static int TACHOBOB_ROW_RED = 2;
final static int TACHOBOB_ROW_ORANGE = 3;
final static int TACHOBOB_ROW_GREEN = 4;
final static int TACHOBOB_ROW_MARKER = 5;
final static int TACHOBOB_MARKERWIDTH = 7;
final static int TACHOBOB_NOTEOFFSET_LEFT = 0; // offsets from NoteBob.x
final static int TACHOBOB_NOTEOFFSET_RIGHT = 10; // offsets from NoteBob.x
final static int TACHOBOB_NOTEOFFSET_DOWN = 21; // offsets from NoteBob.y
final static int LABELBOB_WIDTH = 121;
final static int LABELBOB_HEIGHT = 17;
final static int LABELBOB_OFFSETX = 0;
final static int LABELBOB_OFFSETY = 0;
final static int LABELBOB_BITMAPWIDTH = 121;
final static int LABELBOB_BITMAPHEIGHT = 17;
final static int LABELBOB_COLSOFFRAMES = 2;
final static int LABELBOB_HALFHEIGHT = 9;
final static int LABELBOB_ROW_ATLANTIS = 0; // stored in AtlantisLabelBob.frameRow
final static int LABELBOB_ROW_MIR = 1; // stored in MirLabelBob.frameRow
final static int LABELBOB_ROW_SOYUZ10 = 2; // stored in SoyuzLabelBob.frameRow
final static int LABELBOB_ROW_SOYUZ28 = 3; // stored in SoyuzLabelBob.frameRow
//******************************************************************************
public synchronized void drawBobs() {
dirtyRect.beginClipRect();
AtlantisLabelBob.lift(dirtyRect);
MirLabelBob.lift(dirtyRect);
SoyuzLabelBob.lift(dirtyRect);
if(!SoyuzBob.isVisible())
SoyuzLabelBob.setInUse(false);
NoteBob.lift(dirtyRect);
TachoBob.lift(dirtyRect);
MirBob.lift(dirtyRect);
MirBob.setInUse(true);
AtlantisBob.lift(dirtyRect);
AtlantisBob.setInUse(true);
AtlantisJetBob.lift(dirtyRect);
SoyuzBob.lift(dirtyRect);
SoyuzJetBob.lift(dirtyRect);
EarthBob.lift(dirtyRect);
if(!InDemoMode) dropDots();
EarthBob.save();
SoyuzJetBob.save();
SoyuzBob.save();
AtlantisJetBob.save();
AtlantisBob.save();
MirBob.save();
NoteBob.save();
TachoBob.save();
if(SoyuzBob.isVisible())
SoyuzLabelBob.save();
MirLabelBob.save();
AtlantisLabelBob.save();
EarthBob.drop(dirtyRect);
SoyuzJetBob.drop(dirtyRect);
SoyuzBob.drop(dirtyRect);
AtlantisJetBob.drop(dirtyRect);
AtlantisBob.drop(dirtyRect);
MirBob.drop(dirtyRect);
TachoBob.drop(dirtyRect);
NoteBob.drop(dirtyRect);
if(SoyuzBob.isVisible())
SoyuzLabelBob.drop(dirtyRect);
MirLabelBob.drop(dirtyRect);
AtlantisLabelBob.drop(dirtyRect);
StartGadget.draw();
BrakeGadget.draw();
SpeedGadget.draw();
ClearGadget.draw();
dirtyRect.addClipRect(StartGadget.getRectangle());
dirtyRect.addClipRect(BrakeGadget.getRectangle());
dirtyRect.addClipRect(SpeedGadget.getRectangle());
dirtyRect.addClipRect(ClearGadget.getRectangle());
offScreenClipRect = dirtyRect.endClipRect();
}
//******************************************************************************
public void dropDots() {
if(!Atlantis.hasDocked()) {
DotsBob.x = AtlantisBob.x - AtlantisBob.offsetX + DotsBob.offsetX;
DotsBob.y = AtlantisBob.y - AtlantisBob.offsetY + DotsBob.offsetY;
if(AtlantisJetBob.isVisible())
DotsBob.drop(dirtyRect, DOT_FRM_ATLANTIS_THRUSTS);
else
DotsBob.drop(dirtyRect, DOT_FRM_ATLANTIS);
}
if(Mir.doTrail()) {
DotsBob.x = MirBob.x - MirBob.offsetX + DotsBob.offsetX;
DotsBob.y = MirBob.y - MirBob.offsetY + DotsBob.offsetY;
DotsBob.drop(dirtyRect, DOT_FRM_MIR);
}
}
//******************************************************************************
public synchronized void initBobs() {
AtlantisBob = new Bob( ATLANTISBOB_WIDTH, // width
ATLANTISBOB_HEIGHT, // height
ATLANTISBOB_COLSOFFRAMES, // colsOfFrames
stripAtlantisImage, // stripImage
ATLANTISBOB_OFFSETX, // offsetX
ATLANTISBOB_OFFSETY, // offsetY
ATLANTISBOB_BITMAPWIDTH, // bitmapWidth
ATLANTISBOB_BITMAPHEIGHT, // bitmapheight
true,
offScreen, this);
SoyuzBob = new Bob( SOYUZBOB_WIDTH, // width
SOYUZBOB_HEIGHT, // height
SOYUZBOB_COLSOFFRAMES, // colsOfFrames
stripSoyuzImage, // stripImage
SOYUZBOB_OFFSETX, // offsetX
SOYUZBOB_OFFSETY, // offsetY
SOYUZBOB_BITMAPWIDTH, // bitmapWidth
SOYUZBOB_BITMAPHEIGHT, // bitmapheight
false,
offScreen, this);
MirBob = new MirBob( MIRBOB_WIDTH, // width
MIRBOB_HEIGHT, // height
MIRBOB_COLSOFFRAMES, // colsOfFrames
stripMirImage, // stripImage
MIRBOB_OFFSETX, // offsetX
MIRBOB_OFFSETY, // offsetY
MIRBOB_BITMAPWIDTH, // bitmapWidth
MIRBOB_BITMAPHEIGHT, // bitmapheight
true,
offScreen, this);
AtlantisJetBob = new Bob( ATLANTISJETBOB_WIDTH, // width
ATLANTISJETBOB_HEIGHT, // height
ATLANTISJETBOB_COLSOFFRAMES, // colsOfFrames
stripJetImage, // stripImage
ATLANTISJETBOB_OFFSETX, // offsetX
ATLANTISJETBOB_OFFSETY, // offsetY
ATLANTISJETBOB_BITMAPWIDTH, // bitmapWidth
ATLANTISJETBOB_BITMAPHEIGHT, // bitmapheight
false,
offScreen, this );
SoyuzJetBob = new Bob( SOYUZJETBOB_WIDTH, // width
SOYUZJETBOB_HEIGHT, // height
SOYUZJETBOB_COLSOFFRAMES, // colsOfFrames
stripJetImage, // stripImage
SOYUZJETBOB_OFFSETX, // offsetX
SOYUZJETBOB_OFFSETY, // offsetY
SOYUZJETBOB_BITMAPWIDTH, // bitmapWidth
SOYUZJETBOB_BITMAPHEIGHT, // bitmapheight
false,
offScreen, this );
DotsBob = new Bob( DOTSBOB_WIDTH, // width
DOTSBOB_HEIGHT, // height
DOTSBOB_COLSOFFRAMES, // colsOfFrames
stripDotsImage, // stripImage
DOTSBOB_OFFSETX, // offsetX
DOTSBOB_OFFSETY, // offsetY
DOTSBOB_BITMAPWIDTH, // bitmapWidth
DOTSBOB_BITMAPHEIGHT, // bitmapheight
true,
offScreen, this );
EarthBob = new Bob( EARTHBOB_WIDTH, // width
EARTHBOB_HEIGHT, // height
EARTHBOB_COLSOFFRAMES, // colsOfFrames
stripEarthImage, // stripImage
EARTHBOB_OFFSETX, // offsetX
EARTHBOB_OFFSETY, // offsetY
EARTHBOB_BITMAPWIDTH, // bitmapWidth
EARTHBOB_BITMAPHEIGHT, // bitmapheight
true,
offScreen, this );
EarthBob.x = MX + EARTHBOB_OFFSETX;
EarthBob.y = MY + EARTHBOB_OFFSETY;
EarthBob.setFrameNr(0);
NoteBob = new Bob( NOTEBOB_WIDTH, // width
NOTEBOB_HEIGHT, // height
NOTEBOB_COLSOFFRAMES, // colsOfFrames
stripNoteImage, // stripImage
NOTEBOB_OFFSETX, // offsetX
NOTEBOB_OFFSETY, // offsetY
NOTEBOB_BITMAPWIDTH, // bitmapWidth
NOTEBOB_BITMAPHEIGHT, // bitmapheight
false,
offScreen, this );
TachoBob = new Bob( TACHOBOB_WIDTH, // width
TACHOBOB_HEIGHT, // height
TACHOBOB_COLSOFFRAMES, // colsOfFrames
this.createImage(TACHOBOB_WIDTH, TACHOBOB_HEIGHT),
TACHOBOB_OFFSETX, // offsetX
TACHOBOB_OFFSETY, // offsetY
TACHOBOB_BITMAPWIDTH, // bitmapWidth
TACHOBOB_BITMAPHEIGHT, // bitmapheight
false,
offScreen, this );
AtlantisLabelBob = new Bob( LABELBOB_WIDTH, // width
LABELBOB_HEIGHT, // height
LABELBOB_COLSOFFRAMES, // colsOfFrames
stripLabelsImage, // stripImage
LABELBOB_OFFSETX, // offsetX
LABELBOB_OFFSETY, // offsetY
LABELBOB_WIDTH, // bitmapWidth
LABELBOB_HEIGHT, // bitmapheight
false,
offScreen, this );
SoyuzLabelBob = new Bob( LABELBOB_WIDTH, // width
LABELBOB_HEIGHT, // height
LABELBOB_COLSOFFRAMES, // colsOfFrames
stripLabelsImage, // stripImage
LABELBOB_OFFSETX, // offsetX
LABELBOB_OFFSETY, // offsetY
LABELBOB_WIDTH, // bitmapWidth
LABELBOB_HEIGHT, // bitmapheight
false,
offScreen, this );
MirLabelBob = new Bob( LABELBOB_WIDTH, // width
LABELBOB_HEIGHT, // height
LABELBOB_COLSOFFRAMES, // colsOfFrames
stripLabelsImage, // stripImage
LABELBOB_OFFSETX, // offsetX
LABELBOB_OFFSETY, // offsetY
LABELBOB_WIDTH, // bitmapWidth
LABELBOB_HEIGHT, // bitmapheight
false,
offScreen, this );
}
}
////////////////////////////////////////////////////////////////////////////////
// //
// AutopilotTable //
// //
////////////////////////////////////////////////////////////////////////////////
// A helper class for Autopilot
final class AutopilotTable {
float alfa;
float semiAxis;
int maneuverType;
public AutopilotTable() {
}
public AutopilotTable(float alfa, float semiAxis, int maneuverType) {
this.alfa = alfa;
this.semiAxis = semiAxis;
this.maneuverType = maneuverType;
}
}
////////////////////////////////////////////////////////////////////////////////
// //
// Autopilot //
// //
////////////////////////////////////////////////////////////////////////////////
// Autopilot as a table-driven Finite State Machine -- still pretty messy!
final class Autopilot {
final static float DEG2RAD = (float)(Math.PI / 180.0);
final static float RAD2DEG = (float)(180.0 / Math.PI);
final static int OM_CLASSES = 4; // number of orbital maneuver classes
final static int OMC_LODOCK = 0; // maneuver class IDs
final static int OMC_LOSEPR = 1;
final static int OMC_HIDOCK = 2;
final static int OMC_HISEPR = 3;
final static int OM_STEPS = 8; // number of steps of each orbital meneuver
final static int OM_INITLAUNCH = 0; // maneuver step IDs
final static int OM_MANEUVER = 1;
final static int OM_RENDEZVOUS = 2;
final static int OM_HIDESOYUZ = 3;
final static int OM_DOCKING = 4;
final static int OM_SEPARATION = 5;
final static int OM_SHOWSOYUZ = 6;
final static int OM_CLEANUPDESCENT = 7;
final static int OM_NOP = 8;
final static int NIL = -1;
final static int SOYUZTHRUST_NUM_FRAMES = 10; // soyuz jet is shown for this # of screen frames
private int OMIndex;
private boolean OMPending;
private int OMClass;
private float TD;
private float deltaTD;
private boolean readyJets;
private int soyuzLaunchCount;
public float soyuzVelocityDirX;
public float soyuzVelocityDirY;
private AutopilotTable OM; // the current orbital maneuver
private AutopilotTable OMTable[][];
////////////////////////////////////////////////////////////////////////////////
public Autopilot() {
OM = new AutopilotTable();
OMTable = new AutopilotTable[OM_CLASSES][OM_STEPS];
// Fill tables for automatic high/low orbit docking/separation procedures
float R = Spacecraft.GEOSTATIONARY_RADIUS;
float GROUND_ORBIT = (float)(1.0 / 2.0 * 2.0 / 7.0 * R);
float TRANSFER_PARK_TO_GROUND = (float)(1.0 / 2.0 * (1.0 / 2.0 + 1.0 / 7.0) * R);
float PARK_ORBIT = (float)(1.0 / 2.0 * R);
float TRANSFER_HIGH_TO_PARK = (float)(1.0 / 2.0 * (6.0 / 5.0 + 1.0 / 2.0) * R);
float HIGH_ORBIT = (float)(6.0 / 5.0 * R);
float TRANSFER_MIR_TO_HIGH = (float)(1.0 / 2.0 * (6.0 / 5.0 + 1.0) * R);
float TRANSFER_LOW_TO_PARK = (float)(1.0 / 2.0 * (4.0 / 5.0 + 1.0 / 2.0) * R);
float TRANSFER_MIR_TO_LOW = (float)(1.0 / 2.0 * (4.0 / 5.0 + 1.0) * R);
float LOW_ORBIT = (float)(4.0 / 5.0 * R);
float MIR_ORBIT = (float)(1.0 * R);
float SPECIAL = (float)(1.0 / 7.0 * R);
OMTable[OMC_HISEPR][0] = new AutopilotTable(NIL, NIL, OM_CLEANUPDESCENT);
OMTable[OMC_HISEPR][1] = new AutopilotTable(NIL, GROUND_ORBIT, OM_MANEUVER );
OMTable[OMC_HISEPR][2] = new AutopilotTable(NIL, TRANSFER_PARK_TO_GROUND, OM_MANEUVER );
OMTable[OMC_HISEPR][3] = new AutopilotTable(NIL, PARK_ORBIT, OM_MANEUVER );
OMTable[OMC_HISEPR][4] = new AutopilotTable(NIL, TRANSFER_HIGH_TO_PARK, OM_MANEUVER );
OMTable[OMC_HISEPR][5] = new AutopilotTable(NIL, HIGH_ORBIT, OM_MANEUVER );
OMTable[OMC_HISEPR][6] = new AutopilotTable(NIL, NIL, OM_SHOWSOYUZ );
OMTable[OMC_HISEPR][7] = new AutopilotTable(NIL, TRANSFER_MIR_TO_HIGH, OM_SEPARATION );
OMTable[OMC_LOSEPR][0] = new AutopilotTable(NIL, NIL, OM_CLEANUPDESCENT);
OMTable[OMC_LOSEPR][1] = new AutopilotTable(NIL, GROUND_ORBIT, OM_MANEUVER );
OMTable[OMC_LOSEPR][2] = new AutopilotTable(NIL, TRANSFER_PARK_TO_GROUND, OM_MANEUVER );
OMTable[OMC_LOSEPR][3] = new AutopilotTable(NIL, PARK_ORBIT, OM_MANEUVER );
OMTable[OMC_LOSEPR][4] = new AutopilotTable(NIL, TRANSFER_LOW_TO_PARK, OM_MANEUVER );
OMTable[OMC_LOSEPR][5] = new AutopilotTable(NIL, LOW_ORBIT, OM_MANEUVER );
OMTable[OMC_LOSEPR][6] = new AutopilotTable(NIL, NIL, OM_SHOWSOYUZ );
OMTable[OMC_LOSEPR][7] = new AutopilotTable(NIL, TRANSFER_MIR_TO_LOW, OM_SEPARATION );
OMTable[OMC_HIDOCK][0] = new AutopilotTable(NIL, NIL, OM_DOCKING );
OMTable[OMC_HIDOCK][1] = new AutopilotTable(NIL, MIR_ORBIT, OM_MANEUVER );
OMTable[OMC_HIDOCK][2] = new AutopilotTable(NIL, NIL, OM_HIDESOYUZ );
OMTable[OMC_HIDOCK][3] = new AutopilotTable(NIL, TRANSFER_MIR_TO_HIGH, OM_RENDEZVOUS);
OMTable[OMC_HIDOCK][4] = new AutopilotTable(NIL, HIGH_ORBIT, OM_MANEUVER );
OMTable[OMC_HIDOCK][5] = new AutopilotTable(NIL, TRANSFER_HIGH_TO_PARK, OM_MANEUVER );
OMTable[OMC_HIDOCK][6] = new AutopilotTable(NIL, PARK_ORBIT, OM_MANEUVER );
OMTable[OMC_HIDOCK][7] = new AutopilotTable(SPECIAL, TRANSFER_PARK_TO_GROUND, OM_INITLAUNCH);
OMTable[OMC_LODOCK][0] = new AutopilotTable(NIL, NIL, OM_DOCKING );
OMTable[OMC_LODOCK][1] = new AutopilotTable(NIL, MIR_ORBIT, OM_MANEUVER );
OMTable[OMC_LODOCK][2] = new AutopilotTable(NIL, NIL, OM_HIDESOYUZ );
OMTable[OMC_LODOCK][3] = new AutopilotTable(NIL, TRANSFER_MIR_TO_LOW, OM_RENDEZVOUS);
OMTable[OMC_LODOCK][4] = new AutopilotTable(NIL, LOW_ORBIT, OM_MANEUVER );
OMTable[OMC_LODOCK][5] = new AutopilotTable(NIL, TRANSFER_LOW_TO_PARK, OM_MANEUVER );
OMTable[OMC_LODOCK][6] = new AutopilotTable(NIL, PARK_ORBIT, OM_MANEUVER );
OMTable[OMC_LODOCK][7] = new AutopilotTable(SPECIAL, TRANSFER_PARK_TO_GROUND, OM_INITLAUNCH);
}
////////////////////////////////////////////////////////////////////////////////
public void reset(MirSpacecraft Mir, Spacecraft Soyuz) {
Soyuz.thrust = Spacecraft.NO_THRUST;
Mir.setBlockAtlantisDocking(false);
OMIndex = NIL;
OMPending = false;
for(int i=0; i 0) ? true : false; // readyJets if soyuz 180 deg from OM Point
break;
case OM_NOP :
break;
}
}
////////////////////////////////////////////////////////////////////////////////
public void navigate(MirBob MirBob, Bob SoyuzBob, MirSpacecraft Mir, Spacecraft Soyuz) {
final float CATCHRADIUS_SQR = (8 * 8) / (RocketScience.ZOOM * RocketScience.ZOOM);
float vh;
float normdeltav;
float alfa;
float dx;
float dy;
if(OMPending==true) {
switch(OM.maneuverType) {
case OM_SHOWSOYUZ:
dx = Mir.x - Soyuz.x;
dy = Mir.y - Soyuz.y;
if((dx * dx + dy * dy) > CATCHRADIUS_SQR) {
SoyuzBob.setVisible(true);
if(soyuzLaunchCount==2)
MirBob.setFrameRow(RocketScience.MIRBOB_ROW_1SOYUZ);
if(soyuzLaunchCount==3)
MirBob.setFrameRow(RocketScience.MIRBOB_ROW_0SOYUZ);
OMPending = false;
}
break;
case OM_HIDESOYUZ:
dx = Mir.x - Soyuz.x;
dy = Mir.y - Soyuz.y;
if((dx * dx + dy * dy) < CATCHRADIUS_SQR) {
SoyuzBob.setVisible(false);
if(soyuzLaunchCount==0)
MirBob.setFrameRow(RocketScience.MIRBOB_ROW_1SOYUZ);
if(soyuzLaunchCount==1)
MirBob.setFrameRow(RocketScience.MIRBOB_ROW_2SOYUZ);
OMPending = false;
}
break;
case OM_RENDEZVOUS:
TD = TD - deltaTD;
if(TD<=0) {
Mir.setBlockAtlantisDocking(true);
if(OMClass==OMC_LODOCK)
OMTable[OMC_LODOCK][1].alfa = trim360(atan360(Soyuz.x, Soyuz.y) + 180);
if(OMClass==OMC_HIDOCK)
OMTable[OMC_HIDOCK][1].alfa = trim360(atan360(Mir.x, Mir.y) + 180);
vh = DeltaVHohmann(Soyuz, OM.semiAxis);
Soyuz.v = (float)Math.sqrt(Soyuz.vx * Soyuz.vx + Soyuz.vy * Soyuz.vy);
normdeltav = (vh / Soyuz.v) - 1;
Soyuz.thrust = SOYUZTHRUST_NUM_FRAMES;
soyuzVelocityDirX = (normdeltav < 0) ? Soyuz.vx : -Soyuz.vx;
soyuzVelocityDirY = (normdeltav < 0) ? Soyuz.vy : -Soyuz.vy;
Soyuz.vx += normdeltav * Soyuz.vx;
Soyuz.vy += normdeltav * Soyuz.vy;
OMPending = false;
}
break;
case OM_MANEUVER:
alfa = OM.alfa - atan360(Soyuz.x, Soyuz.y);
if(alfa < -180) alfa += 360;
if( (alfa > 0) && (readyJets==false) )
readyJets = true;
if( (alfa < 0) && (readyJets==true) ) {
vh = DeltaVHohmann(Soyuz, OM.semiAxis);
Soyuz.v = (float)Math.sqrt(Soyuz.vx * Soyuz.vx + Soyuz.vy * Soyuz.vy);
normdeltav = (vh / Soyuz.v) - 1;
Soyuz.thrust = SOYUZTHRUST_NUM_FRAMES;
soyuzVelocityDirX = (normdeltav < 0) ? Soyuz.vx : -Soyuz.vx;
soyuzVelocityDirY = (normdeltav < 0) ? Soyuz.vy : -Soyuz.vy;
Soyuz.vx += normdeltav * Soyuz.vx;
Soyuz.vy += normdeltav * Soyuz.vy;
OMPending = false;
readyJets = false;
}
break;
case OM_NOP:
break;
}
}
}
/*** Propellerhead Info ********************************************************
* *
* How to calculate the Hohmann transfer velocity vh *
* *
* In order to transfer a spacecraft from a circular orbit with radius r1 to *
* reach an orbit of radius r2 you have to accelerate the spacecraft to the *
* Hohmann velocity vh. With v2 as the velocity at the target orbit, use the *
* conservation of moments *
* *
* vh * r1 = v2 * r2 -> v2 = vh * ( r1 / r2 ) (1) *
* *
* and the conservation of energy *
* *
* -g * m1 * m2 / r1 + 1/2 * m2 * vh^2 = -g * m1 * m2 / r2 + 1/2 * m2 * v2^2 *
* *
* where g is the gravitational constant, and m1 the mass of the Earth. *
* Plug in Equ.(1), set r2 = 2 * a - r1, where a is the major semi-axis of the *
* (Hohmann) transfer orbit ellipse. After a little algebra we receive *
* *
* vh^2 = 2 * g * m1 * ( 1 / r1 - 1 / a ) *
* *
* With the spacecraft already being on a circular orbit with velocity v1 the *
* necessary change deltaV to vh is *
* *
* deltaV = vh - v1; *
* *
*******************************************************************************/
////////////////////////////////////////////////////////////////////////////////
private float DeltaVHohmann(Spacecraft s, float a) {
float r1 = (float)Math.sqrt(s.x * s.x + s.y * s.y);
float vh = (float)Math.sqrt(2 * Spacecraft.GM / r1 - Spacecraft.GM / a);
return(vh);
}
////////////////////////////////////////////////////////////////////////////////
public void triggerSoyuz() {
if(OMIndex==NIL) {
soyuzLaunchCount++;
if(soyuzLaunchCount==4) soyuzLaunchCount = 0;
switch(soyuzLaunchCount) {
case 0 : OMClass = OMC_LODOCK; break;
case 1 : OMClass = OMC_HIDOCK; break;
case 2 : OMClass = OMC_HISEPR; break;
case 3 : OMClass = OMC_LOSEPR; break;
}
OMIndex = OM_STEPS - 1;
}
}
////////////////////////////////////////////////////////////////////////////////
private float atan360(float x, float y) {
double phi;
phi = Math.atan2((double)y, (double)x) * RAD2DEG;
if(phi<0) phi += 360;
return((float)phi);
}
////////////////////////////////////////////////////////////////////////////////
private float trim360(float phi) {
if(phi>360) phi -= 360;
return(phi);
}
}
////////////////////////////////////////////////////////////////////////////////
// //
// Sleeper //
// //
////////////////////////////////////////////////////////////////////////////////
interface Sleeper {
public void wakeUp(int id);
}
////////////////////////////////////////////////////////////////////////////////
// //
// Timer //
// //
////////////////////////////////////////////////////////////////////////////////
final class Timer implements Runnable {
final static int OFF = -1;
private int id;
private long snooze;
private Thread timerThread;
// This is an ugly clutch. Something like a callback function/method should be
// more elegant. To be implemented in release 2.0 :-)
private RocketScience client;
////////////////////////////////////////////////////////////////////////////////
public Timer(RocketScience client, int id) {
this.client = client;
this.id = id;
this.snooze = OFF;
if(this.timerThread==null) {
this.timerThread = new Thread(this, "RocketScience 101 Timer Thread " + id);
this.timerThread.start();
}
}
////////////////////////////////////////////////////////////////////////////////
public void clearSnooze() {
if(this.timerThread!=null)
this.snooze = OFF;
}
////////////////////////////////////////////////////////////////////////////////
public void setSnooze(long snooze) {
if(this.timerThread!=null)
this.snooze = snooze;
}
////////////////////////////////////////////////////////////////////////////////
public void run() {
while(Thread.currentThread()==this.timerThread) {
try {
this.timerThread.sleep(1000); // sleep 1 sec
} catch(InterruptedException e) {}
if(this.snooze!=OFF) {
this.snooze--;
if(this.snooze<1) {
this.snooze = OFF;
this.client.wakeUp(this.id);
}
}
}
}
////////////////////////////////////////////////////////////////////////////////
public synchronized void stop() {
if(this.timerThread!=null && this.timerThread.isAlive())
this.timerThread.stop();
this.timerThread = null;
}
}
////////////////////////////////////////////////////////////////////////////////
// //
// DirtyRect //
// //
////////////////////////////////////////////////////////////////////////////////
// Implements the classic dirty rectangle algorithm. In DirtyRect the elegant
// Java allocation method ("new") for adding rectangles is traded with one that
// uses a static Rectangle. Since these methods here are called quite often,
// this avoids the necessity for frequent garbage collection.
final class DirtyRect {
private Rectangle clipRect; // global clipRectangle for all bobs
private boolean firstClip; // flags start of clipping
private Rectangle tmpRect; // static rect to avoid dynamic allocation
////////////////////////////////////////////////////////////////////////////////
public DirtyRect() {
this.firstClip = true;
this.clipRect = new Rectangle();
this.tmpRect = new Rectangle();
}
////////////////////////////////////////////////////////////////////////////////
public void beginClipRect() {
this.firstClip = true;
}
////////////////////////////////////////////////////////////////////////////////
public Rectangle endClipRect() {
return this.clipRect;
}
////////////////////////////////////////////////////////////////////////////////
public void addClipRect(Rectangle rect) {
addClipRect(rect.x, rect.y, rect.width, rect.height);
}
////////////////////////////////////////////////////////////////////////////////
public void addClipRect(int x, int y, int width, int height) {
if(this.firstClip) {
this.clipRect.x = x;
this.clipRect.y = y;
this.clipRect.width = width;
this.clipRect.height = height;
this.firstClip = false;
}
else {
this.tmpRect.x = x;
this.tmpRect.y = y;
this.tmpRect.width = width;
this.tmpRect.height = height;
this.clipRect = this.clipRect.union(tmpRect);
}
}
}
////////////////////////////////////////////////////////////////////////////////
// //
// Bob //
// //
////////////////////////////////////////////////////////////////////////////////
// A "Bob" is AMIGA-lingo for a blitter object (=sprite).
class Bob {
int x; // left coordinate
int y; // top coordinate
int width; // width
int height; // height
private int saveX; // top coordinate of bob area to save
private int saveY; // left coordinate of bob area to save
Image saveImage; // place for the bob area to save
private int frameNr; // frame number (column)
private int frameRow; // frame row
int colsOfFrames; // # of frame columns
Image stripImage; // actual image strip
int offsetX; // x offset of the image to the bob's x coordinate
int offsetY; // y offset of the image to the bob's x coordinate
int bitmapWidth; // x offset to the next frame on the image strip
int bitmapHeight; // y offset to the next frame on the image strip
private boolean visible; // is bob currently visible
private boolean inUse; // is save image used -> backbuffer must be restored
private Image destImage; // destination image -> where the bob is rendered to
////////////////////////////////////////////////////////////////////////////////
public Bob( int width, int height, int colsOfFrames, Image stripImage,
int offsetX, int offsetY, int bitmapWidth, int bitmapHeight,
boolean visible, Image destImage, Component component) {
this.x = 0;
this.y = 0;
this.width = width;
this.height = height;
this.saveX = 0;
this.saveY = 0;
this.saveImage = component.createImage(this.width, this.height);
this.frameNr = 0;
this.frameRow = 0;
this.colsOfFrames = colsOfFrames;
this.stripImage = stripImage;
this.offsetX = offsetX;
this.offsetY = offsetY;
this.bitmapWidth = bitmapWidth;
this.bitmapHeight = bitmapHeight;
this.visible = visible;
this.inUse = false;
this.destImage = destImage;
}
////////////////////////////////////////////////////////////////////////////////
public void blit(Image destImage, int destX, int destY, Image srcImage, int srcX, int srcY, int w, int h) {
Graphics g = destImage.getGraphics().create();
g.clipRect(destX, destY, w, h);
g.drawImage(srcImage, srcX, srcY, null);
g.dispose();
}
////////////////////////////////////////////////////////////////////////////////
public void lift(DirtyRect dirtyRect) { // Copies saveImage to screen
if(this.isInUse()) {
blit(this.destImage, this.saveX, this.saveY, this.saveImage, this.saveX, this.saveY, this.width, this.height);
if(!this.visible) this.inUse = false;
dirtyRect.addClipRect(this.saveX, this.saveY, this.width, this.height);
}
}
////////////////////////////////////////////////////////////////////////////////
public void save() { // Copies screen to saveImage
if(this.visible) {
blit(this.saveImage, 0, 0, this.destImage, -this.x, -this.y, this.width, this.height);
this.saveX = this.x;
this.saveY = this.y;
}
}
////////////////////////////////////////////////////////////////////////////////
public void drop(DirtyRect dirtyRect) { // Copies bob to screen
if(this.visible) {
blit(this.destImage, this.x, this.y, this.stripImage,
-this.frameNr * this.bitmapWidth + this.x,
-this.frameRow * this.bitmapHeight + this.y,
this.width, this.height);
this.inUse = true;
dirtyRect.addClipRect(this.x, this.y, this.width, this.height);
}
}
public void drop(DirtyRect dirtyRect, int frameNr) {
this.frameNr = frameNr;
this.drop(dirtyRect);
}
//////////////////////////////////////////////////////////////////////////////////////////////
public void setFrameNr(int frameNr) {
this.frameNr = frameNr;
}
public void setFrameNr(float x, float y) {
final float RAD2DEG = (float)(180.0 / Math.PI);
double phi = Math.atan2((double)y, (double)x) * RAD2DEG;
int imageAngle = (int)((360.0 / this.colsOfFrames ) + 1);
int frameNr;
phi += 0.5 * imageAngle;
if(phi<0) phi += 360;
if(phi>=360) phi -= 360;
frameNr = (int)phi / imageAngle;
setFrameNr(frameNr);
}
//////////////////////////////////////////////////////////////////////////////////////////////
public int getFrameNr() {
return this.frameNr;
}
//////////////////////////////////////////////////////////////////////////////////////////////
public void setFrameRow(int frameRow) {
this.frameRow = frameRow;
}
//////////////////////////////////////////////////////////////////////////////////////////////
public void cycleFrameNr() {
this.frameNr++;
if(this.frameNr>=this.colsOfFrames)
this.frameNr = 0;
}
//////////////////////////////////////////////////////////////////////////////////////////////
public int getFrameRow() {
return this.frameRow;
}
//////////////////////////////////////////////////////////////////////////////////////////////
public boolean isVisible() {
return(this.visible);
}
//////////////////////////////////////////////////////////////////////////////////////////////
public void setVisible(boolean visible) {
this.visible = visible;
}
//////////////////////////////////////////////////////////////////////////////////////////////
public boolean isInUse() {
return(this.inUse);
}
//////////////////////////////////////////////////////////////////////////////////////////////
public void setInUse(boolean inUse) {
this.inUse = inUse;
}
}
////////////////////////////////////////////////////////////////////////////////
// //
// MirBob //
// //
////////////////////////////////////////////////////////////////////////////////
final class MirBob extends Bob {
// Translation offsets AtlantisBob to MirBob for all frames
int transX[] = {27, 26, 21, 13, 3, -8, -15, -19, -21, -19, -14, -7, 3, 13, 21, 26};
int transY[] = { 3, -7, -15, -19, -21, -19, -14, -6, 3, 12, 20, 26, 27, 24, 20, 11};
MirBob( int width, int height, int colsOfFrames, Image stripImage,
int offsetX, int offsetY, int bitmapWidth, int bitmapHeight,
boolean isVisible, Image destImage, Component component) {
super( width, height, colsOfFrames, stripImage, offsetX, offsetY,
bitmapWidth, bitmapHeight, isVisible, destImage, component);
}
}
////////////////////////////////////////////////////////////////////////////////
// //
// Spacecraft //
// //
////////////////////////////////////////////////////////////////////////////////
class Spacecraft {
public final static float GRAVITY = (float)6.67E-11; // [N kg2/m2]
public final static float EARTH_MASS = (float)6.00E+24; // [kg]
public final static float EARTH_RADIUS = (float)6.40E+06; // [m]
public final static float ONE_DAY = (float)(24.0 * 60.0 * 60.0); // [s]
public final static float GM = GRAVITY * EARTH_MASS;
public final static float GEOSTATIONARY_RADIUS = (float)Math.pow(GRAVITY * EARTH_MASS * Math.pow(ONE_DAY / (2 * Math.PI), 2), 1.0 / 3.0); // [m]
public final static float NO_THRUST = 0;
public final static float ATLANTIS_SPEED_THRUST = (float)(70 * 0.001);
public final static float ATLANTIS_BRAKE_THRUST = -(float)(70 * 0.001);
float x;
float y;
float vx;
float vy;
float r;
float v;
float thrust;
float thrustX;
float thrustY;
boolean onScreen;
private boolean hasDocked;
////////////////////////////////////////////////////////////////////////////////
public Spacecraft(float x, float y, float vx, float vy, boolean onScreen) {
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.r = (float)Math.sqrt( x * x + y * y );
this.v = (float)Math.sqrt( vx * vx + vy * vy );
this.onScreen = onScreen;
this.thrust = NO_THRUST;
this.thrustX = 0;
this.thrustY = 0;
this.hasDocked = false;
}
////////////////////////////////////////////////////////////////////////////////
public boolean hasDocked() {
return this.hasDocked;
}
////////////////////////////////////////////////////////////////////////////////
public void setDocked(boolean docked) {
this.hasDocked = docked;
}
////////////////////////////////////////////////////////////////////////////////
public void newPosition(float deltaT) {
// Uses a 2nd order Runge-Kutte algorithm to solve the equation of motion
float d;
float ax;
float ay;
float tmpX;
float tmpY;
d = (float)Math.sqrt(x * x + y * y);
d = -GM / (d * d * d);
ax = d * x + thrustX;
ay = d * y + thrustY;
tmpX = x + ( vx + (float)0.5 * ax * deltaT ) * deltaT;
tmpY = y + ( vy + (float)0.5 * ay * deltaT ) * deltaT;
d = (float)Math.sqrt(tmpX * tmpX + tmpY * tmpY);
d = -GM / (d * d * d);
ax = (float)0.5 * (ax + d * tmpX);
ay = (float)0.5 * (ay + d * tmpY);
x += ( vx + (float)0.5 * ax * deltaT ) * deltaT;
y += ( vy + (float)0.5 * ay * deltaT ) * deltaT;
vx += ax * deltaT;
vy += ay * deltaT;
r = (float)Math.sqrt(x * x + y * y);
v = (float)Math.sqrt(vx * vx + vy * vy);
}
}
////////////////////////////////////////////////////////////////////////////////
// //
// MirSpacecraft //
// //
////////////////////////////////////////////////////////////////////////////////
final class MirSpacecraft extends Spacecraft {
private int trailCnt;
private int trailMax;
private boolean trailDoTrail;
private boolean blockAtlantisDocking = false;
MirSpacecraft(float x, float y, float vx, float vy, boolean onScreen) {
super(x, y, vx, vy, onScreen);
}
////////////////////////////////////////////////////////////////////////////////
public boolean blockAtlantisDocking() {
return this.blockAtlantisDocking;
}
////////////////////////////////////////////////////////////////////////////////
public void setBlockAtlantisDocking(boolean isBlocked) {
this.blockAtlantisDocking = isBlocked;
}
////////////////////////////////////////////////////////////////////////////////
public void resetTrail(int numTrailDots) {
this.trailCnt = 0;
this.trailMax = numTrailDots;
this.trailDoTrail = true;
}
////////////////////////////////////////////////////////////////////////////////
public boolean doTrail() {
this.trailCnt++;
if(this.trailCnt>this.trailMax)
this.trailDoTrail = false;
return(this.trailDoTrail);
}
}
////////////////////////////////////////////////////////////////////////////////
// //
// Gadget //
// //
////////////////////////////////////////////////////////////////////////////////
// A "Gadget" is AMIGA-lingo for a push button
final class Gadget {
final static int GADGET_ROW_UP = 0;
final static int GADGET_ROW_DOWN = 1;
final static int GADGET_ROW_DISABLED = 2;
private Rectangle rect;
private boolean pressed;
private boolean enabled;
private Image image;
private Image destImage;
////////////////////////////////////////////////////////////////////////////////
public Gadget(int x, int y, Image image, Image destImage) {
this.rect = new Rectangle(x, y, image.getWidth(null), image.getHeight(null) / 3);
this.image = image;
this.pressed = false;
this.enabled = true;
this.destImage = destImage;
}
////////////////////////////////////////////////////////////////////////////////
public boolean inside(int x, int y) {
if(this.enabled)
return this.rect.inside(x, y);
else
return false;
}
////////////////////////////////////////////////////////////////////////////////
public Rectangle getRectangle() {
return this.rect;
}
////////////////////////////////////////////////////////////////////////////////
public boolean isPressed() {
return this.pressed;
}
////////////////////////////////////////////////////////////////////////////////
public boolean isEnabled() {
return this.enabled;
}
////////////////////////////////////////////////////////////////////////////////
public void enable() {
this.pressed = false;
this.enabled = true;
this.draw();
}
////////////////////////////////////////////////////////////////////////////////
public void disable() {
this.pressed = false;
this.enabled = false;
this.draw();
}
////////////////////////////////////////////////////////////////////////////////
public void down() {
if(this.enabled) {
this.drawDown();
this.pressed = true;
}
}
////////////////////////////////////////////////////////////////////////////////
public void up() {
if(this.enabled) {
this.drawUp();
this.pressed = false;
}
}
////////////////////////////////////////////////////////////////////////////////
public void draw() {
if(this.enabled) {
if(this.pressed)
this.drawDown();
else
this.drawUp();
}
else
this.drawDisabled();
}
private synchronized void drawUp() {
Graphics g = destImage.getGraphics().create();
g.clipRect(this.rect.x, this.rect.y, this.rect.width, this.rect.height);
g.drawImage(this.image, this.rect.x, -this.rect.height * GADGET_ROW_UP + this.rect.y, null);
g.dispose();
}
private synchronized void drawDown() {
Graphics g = this.destImage.getGraphics().create();
g.clipRect(this.rect.x, this.rect.y, this.rect.width, this.rect.height);
g.drawImage(this.image, this.rect.x, -this.rect.height * GADGET_ROW_DOWN + this.rect.y, null);
g.dispose();
}
private synchronized void drawDisabled() {
Graphics g = this.destImage.getGraphics().create();
g.clipRect(this.rect.x, this.rect.y, this.rect.width, this.rect.height);
g.drawImage(this.image, this.rect.x, -this.rect.height * GADGET_ROW_DISABLED + this.rect.y, null);
g.dispose();
}
}