Moving over the SurfaceThe KeyBehavior class is similar to the one in FractalLand3D in that it permits the user to move over the surface of the terrain and to float above it. However, there's one small change and one large one. The small change is the addition of the w key, which prints the user's current location on the landscape to System.out. The major change is that a move doesn't immediately affect the user's vertical position on the terrain. The height calculation is delegated to a HeightFinder object, which may take one or two seconds to obtain the result through picking. In the meantime, KeyBehavior continues to use the old value. As a consequence, the user can move inside mountains, but the vertical position will be corrected. The reason for the slow picking is the large terrain size (e.g., over 66,000 vertices in test2.obj, specifying 131,000 faces), all packaged inside a single GeometryArray. This approach has the advantage that key processing is decoupled from the height calculation. This means that the KeyBehavior object doesn't have to wait those few seconds after each move-related key press, which would quickly drive the user to distraction. At the end of the chapter, I discuss various alternatives to this coding technique. Where Am I?When the user presses the w key, printLandLocation( ) is called: private void printLandLocation( ) { targetTG.getTransform(t3d); t3d.get(trans); trans.y -= MOVE_STEP*zOffset; // ignore user floating Vector3d whereVec = land.worldToLand(trans); System.out.println("Land location: (" + df.format(whereVec.x) + ", " + df.format(whereVec.y) + ", " + df.format(whereVec.z) + ")"); } A slight problem is that the KeyBehavior object is attached to sceneBG, so it utilizes world coordinates. However, printing the landscape coordinates is more helpful, and so the (x, y, z) data maintained in KeyBehavior must be transformed. This is achieved by calling worldToLand( ) in Landscape: public Vector3d worldToLand(Vector3d worldVec) { double xCoord = (worldVec.x + LAND_LEN/2) / scaleLen; double yCoord = (-worldVec.z + LAND_LEN/2) / scaleLen; // z-axis --> y-axis double zCoord = worldVec.y / scaleLen; // y-axis --> z-axis return new Vector3d(xCoord, yCoord, zCoord); } The transformations apply the operations implicit in the subgraph in Figure 27-12: the world coordinates are translated, scaled and rotated to make them into terrain coordinates. The rotation (a -90-degree rotation around the x-axis) can be conveniently expressed as a switching of the y- and z-coordinates.
The w key is quite useful when the programmer is deciding where to place scenery on the terrain. The user can move over the landscape inside Terra3D, and press w when a promising location is encountered. The outputted coordinates can be used in the scenery file to position 3D models. Strolling Around the TerrainThe principal method for moving is moveBy( ), which is called with a predefined step for moving forward, back, left, or right. The viewpoint is adjusted in four stages:
Here's that sequence of events in code: private void moveBy(Vector3d theMove) { Vector3d nextLoc = tryMove(theMove); // next (x,?,z) position if (!land.inLandscape(nextLoc.x, nextLoc.z)) // not on landscape return; hf.requestMoveHeight(nextLoc); // height request to HeightFinder Vector3d actualMove = new Vector3d(theMove.x, 0, theMove.z); // no y-axis change... yet doMove(actualMove); } public void adjustHeight(double newHeight) { double heightChg = newHeight - currLandHeight - (MOVE_STEP*zOffset); Vector3d upVec = new Vector3d(0, heightChg, 0); currLandHeight = newHeight; // update current height zOffset = 0; // back on floor, so no offset doMove(upVec); } moveBy( ) and adjustHeight( ) call doMove( ), which updates the viewpoint position. This method is unchanged from the one in FractalLand3D, except that it is now prefixed with the synchronized keyword. This prevents KeyBehavior and HeightFinder from calling it at the same time. |