/** Company: Shout Interactive Project: Shout3D 1.0 Class: WalkPanel Date: September 15, 1999 Description: Class for Walking (C) Copyright Shout Interactive, Inc. - 1997/1998/1999 - All rights reserved */ package applets; import java.applet.*; import java.awt.*; import java.awt.image.*; import java.io.*; import java.util.Date; import java.net.URL; import shout3d.core.*; import shout3d.math.*; import shout3d.*; /** * Shout3D WalkPanel. This class is meant to provide the user with the ability to \ * navigate around a 3D world in a similar manner to the VRML "WALK" mode. * * @author Jim Stewartson * @author Paul Isaacs * @author Rory Lane Lutter * @author Dave Westwood */ public class WalkPanel extends Shout3DPanel implements RenderObserver, DeviceObserver{ // cache the initial camera and the root of the scene Viewpoint camera; Transform root; // quaternion for the camera Quaternion cameraQuat = new Quaternion(); // variables for camera calculations float cameraHeading = 0; float headingDelta = 0; float speed = 0; float startX = 0; float startY = 0; // the avatarRadius, avatarHeight, and collideHeight of the avatar // can all be set using applet parameters. // The camera is always placed at a height of "avatarHeight" // Collisions are done in the plane that lies at the level of "collideHeight" // The avatarRadius is the closest that the avatar may get to a surface in the scene. // The collision algorithm starts with the camera position (which is at a height of avatarHeight) and // finds the imaginary collider, which is the point below the camera at the height "collideHeight" // Then it attemps to move that collider forward through the scene. If the proposed motion // will move the collider closer than avatarRadius to a surface in the scene, then a collision is // declared and the camera position will not be changed. public float avatarRadius = 2f; public float avatarHeight = 2f; public float collideHeight = .25f; /** * Construct me */ public WalkPanel(Shout3DApplet applet){ super(applet); } /** * Remove observers when done with the panel */ public void finalize()throws Throwable { applet.getDeviceListener().removeDeviceObserver(this, "DeviceInput"); applet.getRenderer().removeRenderObserver(this); super.finalize(); } /** * Overrides Shout3DPanel.customInitialize() */ public void customInitialize() { // Read the 3 avatar parameters from applet parameters, if specified: String avatarHeightString = applet.getParameter("avatarHeight"); if (avatarHeightString != null){ avatarHeight = Float.valueOf(avatarHeightString).floatValue(); } String avatarRadiusString = applet.getParameter("avatarRadius"); if (avatarRadiusString != null){ avatarRadius = Float.valueOf(avatarRadiusString).floatValue(); } String collideHeightString = applet.getParameter("collideHeight"); if (collideHeightString != null){ collideHeight = Float.valueOf(collideHeightString).floatValue(); } // cache the camera and the scene root camera = (Viewpoint)(applet.getCurrentBindableNode("Viewpoint")); root = (Transform)getScene(); // set the position to your avatar's height camera.position.set1Value(1, avatarHeight); // Set the camera heading to align with that of the active viewpoint. Quaternion startCamQuat = new Quaternion(); startCamQuat.setAxisAngle(camera.orientation.getValue()); float[] startCamEulers = new float[3]; startCamQuat.getEulers(startCamEulers); cameraHeading = startCamEulers[0]; // register for device events getDeviceListener().addDeviceObserver(this, "DeviceInput", null); // register for render events getRenderer().addRenderObserver(this, null); } MouseInput mi; public boolean onDeviceInput(DeviceInput di, Object userData){ if (di instanceof MouseInput){ mi = (MouseInput)di; switch (mi.which){ case MouseInput.DOWN: startX = mi.x; startY = mi.y; break; case MouseInput.DRAG: // if the Shift key is down if ((mi.modifiers & DeviceInput.SHIFT_MASK) != 0){ // go fast headingDelta = -(mi.x-startX)/200f; speed = (mi.y-startY)/10f; } else { // otherwise go slower headingDelta = -(mi.x-startX)/400f; speed = (mi.y-startY)/20f; } break; case MouseInput.UP: // stop headingDelta = 0; speed = 0; break; } } // return false, let other entities handle the events too. return false; } /** * Do camera stuff before rendering */ public void onPreRender(Renderer r, Object userData){ // adjust the camera heading according to user input cameraHeading += headingDelta/getFramesPerSecond(); // set the camera orientation to be an axis/angle rotation equivalent to // the new heading. cameraQuat.setEulers(cameraHeading, 0, 0); float[] tempOr = new float[4]; cameraQuat.getAxisAngle(tempOr); camera.orientation.setValue(tempOr); // Determine the proposed camera motion according to user input float[] cameraMotion = new float[3]; cameraMotion[2] = (float)(speed/getFramesPerSecond()); cameraQuat.xform(cameraMotion); // Collisions are done at the level of an imaginary collider located below // the camera at a level of collideHeight. Calculate the old collider location // and the proposed new collider location. float[] oldColliderLoc = new float[3]; oldColliderLoc[0] = camera.position.getValue()[0]; oldColliderLoc[1] = camera.position.getValue()[1] - avatarHeight + collideHeight; oldColliderLoc[2] = camera.position.getValue()[2]; float[] newColliderLoc = new float[3]; newColliderLoc[0] = oldColliderLoc[0] + cameraMotion[0]; newColliderLoc[1] = oldColliderLoc[1] + cameraMotion[1]; newColliderLoc[2] = oldColliderLoc[2] + cameraMotion[2]; // check for collision in moving knee from old to new location if (!checkCollision(oldColliderLoc, newColliderLoc)){ // If no collision, move camera by the given amount. camera.position.set1Value(0, camera.position.getValue()[0] + cameraMotion[0]); camera.position.set1Value(1, camera.position.getValue()[1] + cameraMotion[1]); camera.position.set1Value(2, camera.position.getValue()[2] + cameraMotion[2]); } } // cache a picker Picker picker; /** * Check to see if the motion has resulted in a collision * * @return whether there is a collision */ boolean checkCollision(float[] oldPos, float[] newPos){ if (picker == null) { // Get a new picker picker = getNewPicker(); // set the picker to provide the hit point picker.setPickInfo(Picker.POINT, true); // set the picker's scene to be while scene picker.setScene(getScene()); } // This returns the path to the geometry that has the closest intersection // to oldPos that lies along the directed ray from oldPos to newPos. // If there is no geometry that intersects that ray, results will be null. Node[] results = picker.pickClosestFromTo(oldPos, newPos); if (results == null) { // Nothing in the way, no collision. return false; } else { // Retrieve the point of intersection from the picker. float[] intersection = picker.getPickInfo(Picker.POINT); // Find the distance between oldPos/newPos and oldPos/intersection float oldToNewDist = getDistance(oldPos, newPos); float oldToInterDist = getDistance(oldPos, intersection); // If newPos is further from oldPos than the intersection, you'd collide with // the geometry in travelling from oldPos to newPos, so return true if (oldToNewDist >= oldToInterDist) return true; // If the difference in the distances is less than avatarRadius, the newPos is too // close to the intersection for the avatar to fit. That counts as a collision, return true if ((oldToInterDist - oldToNewDist) < avatarRadius) return true; } // There's an intersection point but it is not located where it would cause a collision. return false; } // Distance function float getDistance(float[] from, float[] to){ float x = from[0] - to[0]; float y = from[1] - to[1]; float z = from[2] - to[2]; float dist = (float)Math.sqrt(x*x + y*y + z*z); //System.out.println(dist); return dist; } public void onPostRender(Renderer r, Object userData){ } }