//********************************************************************// //* F. PERMADI MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE *// //* SUITABILITY OF *// //* THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT *// //* LIMITED *// //* TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *// //* PARTICULAR PURPOSE *// //********************************************************************// import java.awt.*; import java.applet.*; //*******************************************************************// //* main class //*******************************************************************// public class Rayc extends Applet implements Runnable { // this is Java's stuff Thread fThread; // size of tile (wall height) static final int TILE_SIZE = 64; static final int WALL_HEIGHT = 64; static final int PROJECTIONPLANEWIDTH = 320; static final int PROJECTIONPLANEHEIGHT = 200; static final int ANGLE60 = PROJECTIONPLANEWIDTH; static final int ANGLE30 = (ANGLE60/2); static final int ANGLE15 = (ANGLE30/2); static final int ANGLE90 = (ANGLE30*3); static final int ANGLE180 = (ANGLE90*2); static final int ANGLE270 = (ANGLE90*3); static final int ANGLE360 = (ANGLE60*6); static final int ANGLE0 = 0; static final int ANGLE5 = (ANGLE30/6); static final int ANGLE10 = (ANGLE5*2); // trigonometric tables float fSinTable[]; float fISinTable[]; float fCosTable[]; float fICosTable[]; float fTanTable[]; float fITanTable[]; float fFishTable[]; float fXStepTable[]; float fYStepTable[]; // offscreen buffer Image fOffscreenImage; Graphics fOffscreenGraphics; // player's attributes int fPlayerX = 100; int fPlayerY = 160; int fPlayerArc = ANGLE0; int fPlayerDistanceToTheProjectionPlane = 277; int fPlayerHeight =32; int fPlayerSpeed = 16; int fProjectionPlaneYCenter = PROJECTIONPLANEHEIGHT/2; // the following variables are used to keep the player coordinate in the overhead map int fPlayerMapX, fPlayerMapY, fMinimapWidth; // movement flag boolean fKeyUp=false; boolean fKeyDown=false; boolean fKeyLeft=false; boolean fKeyRight=false; // 2 dimensional map byte fMap[]; static final byte W=1; // wall static final byte O=0; // opening static final int MAP_WIDTH=12; static final int MAP_HEIGHT=12; //*******************************************************************// //* Convert arc to radian //*******************************************************************// float arcToRad(float arcAngle) { return ((float)(arcAngle*Math.PI)/(float)ANGLE180); } //*******************************************************************// //* Create tigonometric values to make the program runs faster. //*******************************************************************// public void createTables() { int i; float radian; fSinTable = new float[ANGLE360+1]; fISinTable = new float[ANGLE360+1]; fCosTable = new float[ANGLE360+1]; fICosTable = new float[ANGLE360+1]; fTanTable = new float[ANGLE360+1]; fITanTable = new float[ANGLE360+1]; fFishTable = new float[ANGLE60+1]; fXStepTable = new float[ANGLE360+1]; fYStepTable = new float[ANGLE360+1]; for (i=0; i<=ANGLE360;i++) { // get the radian value (the last addition is to avoid division by 0, try removing // that and you'll see a hole in the wall when a ray is at 0, 90, 180, or 270 degree) radian = arcToRad(i) + (float)(0.0001); fSinTable[i]=(float)Math.sin(radian); fISinTable[i]=(1.0F/(fSinTable[i])); fCosTable[i]=(float)Math.cos(radian); fICosTable[i]=(1.0F/(fCosTable[i])); fTanTable[i]=(float)Math.tan(radian); fITanTable[i]=(1.0F/fTanTable[i]); // you can see that the distance between xi is the same // if we know the angle // _____|_/next xi______________ // | // ____/|next xi_________ slope = tan = height / dist between xi's // / | // __/__|_________ dist between xi = height/tan where height=tile size // old xi| // distance between xi = x_step[view_angle]; // // // facine left // facing left if (i>=ANGLE90 && i0) fXStepTable[i]=-fXStepTable[i]; } // facing right else { fXStepTable[i] = (float)(TILE_SIZE/fTanTable[i]); if (fXStepTable[i]<0) fXStepTable[i]=-fXStepTable[i]; } // FACING DOWN if (i>=ANGLE0 && i0) fYStepTable[i]=-fYStepTable[i]; } } for (i=-ANGLE30; i<=ANGLE30; i++) { radian = arcToRad(i); // we don't have negative angle, so make it start at 0 // this will give range 0 to 320 fFishTable[i+ANGLE30] = (float)(1.0F/Math.cos(radian)); } // CERATE A SIMPLE MAP byte[] map= { W,W,W,W,W,W,W,W,W,W,W,W, W,O,O,O,O,O,O,O,O,O,O,W, W,O,O,O,O,O,O,O,O,O,O,W, W,O,O,O,O,O,O,O,W,O,O,W, W,O,O,W,O,W,O,O,W,O,O,W, W,O,O,W,O,W,W,O,W,O,O,W, W,O,O,W,O,O,W,O,W,O,O,W, W,O,O,O,W,O,W,O,W,O,O,W, W,O,O,O,W,O,W,O,W,O,O,W, W,O,O,O,W,W,W,O,W,O,O,W, W,O,O,O,O,O,O,O,O,O,O,W, W,W,W,W,W,W,W,W,W,W,W,W }; fMap=map; } //*******************************************************************// //* Called when program starts //*******************************************************************// public void start() { createTables(); fThread=new Thread(this); fThread.start(); } //*******************************************************************// //* Running thread //*******************************************************************// public void run() { requestFocus(); // create offscreen buffer fOffscreenImage=createImage(size().width, size().height); fOffscreenGraphics=fOffscreenImage.getGraphics(); while(true) { // rotate left if (fKeyLeft) { if ((fPlayerArc-=ANGLE10)=ANGLE360) fPlayerArc-=ANGLE360; } // _____ _ // |\ arc | // | \ y // | \ | // - // |--x--| // // sin(arc)=y/diagonal // cos(arc)=x/diagonal where diagonal=speed float playerXDir=fCosTable[fPlayerArc]; float playerYDir=fSinTable[fPlayerArc]; // move forward if (fKeyUp) { fPlayerX+=(int)(playerXDir*fPlayerSpeed); fPlayerY+=(int)(playerYDir*fPlayerSpeed); } // move backward else if (fKeyDown) { fPlayerX-=(int)(playerXDir*fPlayerSpeed); fPlayerY-=(int)(playerYDir*fPlayerSpeed); } render(); try { Thread.sleep(50); } catch (Exception sleepProblem) { showStatus("Sleep problem"); } } } //*******************************************************************// //* Draw background image //*******************************************************************// public void drawBackground() { // sky int c=25; int r; for (r=0; r ANGLE0 && castArc < ANGLE180) { // truncuate then add to get the coordinate of the FIRST grid (horizontal // wall) that is in front of the player (this is in pixel unit) // ROUND DOWN horizontalGrid = (fPlayerY/TILE_SIZE)*TILE_SIZE + TILE_SIZE; // compute distance to the next horizontal wall distToNextHorizontalGrid = TILE_SIZE; float xtemp = fITanTable[castArc]*(horizontalGrid-fPlayerY); // we can get the vertical distance to that wall by // (horizontalGrid-GLplayerY) // we can get the horizontal distance to that wall by // 1/tan(arc)*verticalDistance // find the x interception to that wall xIntersection = xtemp + fPlayerX; } // else, the ray is facing up else { horizontalGrid = (fPlayerY/TILE_SIZE)*TILE_SIZE; distToNextHorizontalGrid = -TILE_SIZE; float xtemp = fITanTable[castArc]*(horizontalGrid - fPlayerY); xIntersection = xtemp + fPlayerX; horizontalGrid--; } // LOOK FOR HORIZONTAL WALL if (castArc==ANGLE0 || castArc==ANGLE180) { distToHorizontalGridBeingHit=9999999F;//Float.MAX_VALUE; } // else, move the ray until it hits a horizontal wall else { distToNextXIntersection = fXStepTable[castArc]; while (true) { xGridIndex = (int)(xIntersection/TILE_SIZE); // in the picture, yGridIndex will be 1 yGridIndex = (horizontalGrid/TILE_SIZE); if ((xGridIndex>=MAP_WIDTH) || (yGridIndex>=MAP_HEIGHT) || xGridIndex<0 || yGridIndex<0) { distToHorizontalGridBeingHit = Float.MAX_VALUE; break; } else if ((fMap[yGridIndex*MAP_WIDTH+xGridIndex])!=O) { distToHorizontalGridBeingHit = (xIntersection-fPlayerX)*fICosTable[castArc]; break; } // else, the ray is not blocked, extend to the next block else { xIntersection += distToNextXIntersection; horizontalGrid += distToNextHorizontalGrid; } } } // FOLLOW X RAY if (castArc < ANGLE90 || castArc > ANGLE270) { verticalGrid = TILE_SIZE + (fPlayerX/TILE_SIZE)*TILE_SIZE; distToNextVerticalGrid = TILE_SIZE; float ytemp = fTanTable[castArc]*(verticalGrid - fPlayerX); yIntersection = ytemp + fPlayerY; } // RAY FACING LEFT else { verticalGrid = (fPlayerX/TILE_SIZE)*TILE_SIZE; distToNextVerticalGrid = -TILE_SIZE; float ytemp = fTanTable[castArc]*(verticalGrid - fPlayerX); yIntersection = ytemp + fPlayerY; verticalGrid--; } // LOOK FOR VERTICAL WALL if (castArc==ANGLE90||castArc==ANGLE270) { distToVerticalGridBeingHit = 9999999;//Float.MAX_VALUE; } else { distToNextYIntersection = fYStepTable[castArc]; while (true) { // compute current map position to inspect xGridIndex = (verticalGrid/TILE_SIZE); yGridIndex = (int)(yIntersection/TILE_SIZE); if ((xGridIndex>=MAP_WIDTH) || (yGridIndex>=MAP_HEIGHT) || xGridIndex<0 || yGridIndex<0) { distToVerticalGridBeingHit = Float.MAX_VALUE; break; } else if ((fMap[yGridIndex*MAP_WIDTH+xGridIndex])!=O) { distToVerticalGridBeingHit =(yIntersection-fPlayerY)*fISinTable[castArc]; break; } else { yIntersection += distToNextYIntersection; verticalGrid += distToNextVerticalGrid; } } } // DRAW THE WALL SLICE float scaleFactor; float dist; int topOfWall; // used to compute the top and bottom of the sliver that int bottomOfWall; // will be the staring point of floor and ceiling // determine which ray strikes a closer wall. // if yray distance to the wall is closer, the yDistance will be shorter than // the xDistance if (distToHorizontalGridBeingHit < distToVerticalGridBeingHit) { // the next function call (drawRayOnMap()) is not a part of raycating rendering part, // it just draws the ray on the overhead map to illustrate the raycasting process drawRayOnOverheadMap(xIntersection, horizontalGrid); dist=distToHorizontalGridBeingHit; fOffscreenGraphics.setColor(Color.gray); } // else, we use xray instead (meaning the vertical wall is closer than // the horizontal wall) else { // the next function call (drawRayOnMap()) is not a part of raycating rendering part, // it just draws the ray on the overhead map to illustrate the raycasting process drawRayOnOverheadMap(verticalGrid, yIntersection); dist=distToVerticalGridBeingHit; fOffscreenGraphics.setColor(Color.darkGray); } // correct distance (compensate for the fishbown effect) dist /= fFishTable[castColumn]; // projected_wall_height/wall_height = fPlayerDistToProjectionPlane/dist; int projectedWallHeight=(int)(WALL_HEIGHT*(float)fPlayerDistanceToTheProjectionPlane/dist); bottomOfWall = fProjectionPlaneYCenter+(int)(projectedWallHeight*0.5F); topOfWall = PROJECTIONPLANEHEIGHT-bottomOfWall; if (bottomOfWall>=PROJECTIONPLANEHEIGHT) bottomOfWall=PROJECTIONPLANEHEIGHT-1; //fOffscreenGraphics.drawLine(castColumn, topOfWall, castColumn, bottomOfWall); fOffscreenGraphics.fillRect(castColumn, topOfWall, 5, projectedWallHeight); // TRACE THE NEXT RAY castArc+=5; if (castArc>=ANGLE360) castArc-=ANGLE360; } // blit to screen paint(getGraphics()); } //*******************************************************************// //* Called when leaving the page. //*******************************************************************// public void stop() { if((fThread != null) && fThread.isAlive()) { fThread.stop(); fThread = null; } } //*******************************************************************// //* Called everytime applet need painting or whenever repaint is //* called. //*******************************************************************// public void paint(Graphics g) { if (fOffscreenImage!=null) g.drawImage(fOffscreenImage, 0, 0, this); } //*******************************************************************// //* Detect keypress //*******************************************************************// public boolean keyDown(Event evt, int key) { switch (key) { case Event.UP: case 'i': case 'I': fKeyUp=true; break; case Event.DOWN: case 'k': case 'K': fKeyDown=true; break; case Event.LEFT: case 'j': case 'J': fKeyLeft=true; break; case Event.RIGHT: case 'l': case 'L': fKeyRight=true; break; default: } return true; } //*******************************************************************// //* Detect key release //*******************************************************************// public boolean keyUp(Event evt, int key) { switch (key) { case Event.UP: case 'i': case 'I': fKeyUp=false; break; case Event.DOWN: case 'k': case 'K': fKeyDown=false; break; case Event.LEFT: case 'j': case 'J': fKeyLeft=false; break; case Event.RIGHT: case 'l': case 'L': fKeyRight=false; break; default: } return true; } }