/** Company: Shout Interactive Project: Shout3D 1.0 Class: ExaminePanel Date: September 15, 1999 Description: Class for ExaminePanel (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.*; /** * A Shout3DPanel which allows examination of an item. It is similar * to but not exactly like, VRML "EXAMINE" mode. * * @author Jim Stewartson * @author Paul Isaacs * @author Dave Westwood */ public class FunctionSliders extends Shout3DPanel implements RenderObserver, DeviceObserver { // cached reference to the initial viewpoint Viewpoint camera; Node[] pathToCameraParent = null; // cached reference to the scene root Transform root; // variables for the last x and y positions of the mouse float lastX = 0; float lastY = 0; // a cached MouseInput MouseInput mi; // variables for doing the examine calculations float[] distanceVector = new float[3]; float BACKUP_SLACK = 0.1f; Quaternion cameraQuat = new Quaternion(); Quaternion transQuat = new Quaternion(); float cameraHeadingSpeed = 0; float cameraPitchSpeed = 0; float cameraZoomSpeed = 0; float cameraHeading = 3.14f; float cameraPitch = 0.2f; float cameraRoll = 0; float cameraDist = 0; float[] bboxMin; float[] bboxMax; float[] bboxCenter; /** * Constructs me */ public FunctionSliders(Shout3DApplet applet){ super(applet, 300, 300); } /** * 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() { camera = (Viewpoint)(applet.getCurrentBindableNode("Viewpoint")); root = (Transform)getScene(); // register for device input applet.getDeviceListener().addDeviceObserver(this, "DeviceInput", null); // register for rendering notification applet.getRenderer().addRenderObserver(this, null); } /** * DeviceObserver method */ public boolean onDeviceInput(DeviceInput di, Object userData){ if (di instanceof MouseInput){ mi = (MouseInput)di; switch (mi.which){ case MouseInput.DOWN: // reset the x and y lastX = mi.x; lastY = mi.y; break; case MouseInput.DRAG: // adjust the camera rotation delta to the distance the // user dragged cameraHeadingSpeed -= (float)(mi.x - lastX)/30f; // if the control key is down if ((mi.modifiers & DeviceInput.CTRL_MASK) != 0){ // zoom based on y cameraZoomSpeed += (float)(mi.y - lastY)/20f; } else { // otherwise tumble cameraPitchSpeed += (float)(mi.y - lastY)/30f; } // reset the x and y lastX = mi.x; lastY = mi.y; break; case MouseInput.UP: // stop rotating the camera cameraHeadingSpeed = 0; cameraPitchSpeed = 0; cameraZoomSpeed = 0; break; } } // return false, let other entities handle the event too. return false; } /** * Utility function for getting the distance between two points */ 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); return dist; } /** * RenderObserver function. * Does the camera movement calculations after each render. */ public void onPostRender(Renderer r, Object userData){ if (bboxMin == null){ // This is the first render. Establish camera position based on // starting orientation and bbox size. // Get the center of the current scene's bbox, in world space Searcher s = getNewSearcher(); s.setNode(root); Node[] pathToRoot = s.searchFirst(root); s.setNode(camera); Node[] pathToCamera = s.searchFirst(root); bboxMin = root.getWorldBBoxMin(pathToRoot); bboxMax = root.getWorldBBoxMax(pathToRoot); bboxCenter = new float[3]; // Get the path to the camera's parent, if path has length > 1 if (pathToCamera != null && pathToCamera.length > 1){ pathToCameraParent = new Node[pathToCamera.length - 1]; System.arraycopy(pathToCamera, 0, pathToCameraParent, 0, pathToCameraParent.length); } bboxCenter[0] = ((bboxMax[0] - bboxMin[0])/2f)+bboxMin[0]; bboxCenter[1] = ((bboxMax[1] - bboxMin[1])/2f)+bboxMin[1]; bboxCenter[2] = ((bboxMax[2] - bboxMin[2])/2f)+bboxMin[2]; performInitialCameraPlacement(); } // change the camera's heading, pitch and distance cameraHeading += cameraHeadingSpeed/getFramesPerSecond(); cameraPitch += cameraPitchSpeed/getFramesPerSecond(); float distanceToSceneCenter = getDistance(camera.position.getValue(), bboxCenter); distanceToSceneCenter += cameraZoomSpeed/getFramesPerSecond(); // distance from target to camera distanceVector[0] = 0; distanceVector[1] = 0; distanceVector[2] = -distanceToSceneCenter; // set the eulers of the camera and set camera orientation cameraQuat.setEulers(cameraHeading-3.14f, -cameraPitch, -cameraRoll); // get the axis angle from the quat and set the camera orientation float[] camRot = new float[4]; cameraQuat.getAxisAngle(camRot); if (camRot[0] != camera.orientation.getValue()[0] || camRot[1] != camera.orientation.getValue()[1] || camRot[2] != camera.orientation.getValue()[2] || camRot[3] != camera.orientation.getValue()[3]){ camera.orientation.setValue(camRot); } // transform the distance vector by an oriented quaternion transQuat.setEulers(cameraHeading, cameraPitch, cameraRoll); transQuat.xform(distanceVector); // Add the transformed distance vector to the model position float[] camPos = new float[3]; camPos[0] = bboxCenter[0]+distanceVector[0]; camPos[1] = bboxCenter[1]+distanceVector[1]; camPos[2] = bboxCenter[2]+distanceVector[2]; if (camPos[0] != camera.position.getValue()[0] || camPos[1] != camera.position.getValue()[1] || camPos[2] != camera.position.getValue()[2]){ camera.position.setValue(camPos); } } public void onPreRender(Renderer r, Object userData){ } public void performInitialCameraPlacement() { // Perform initial camera placement. // Start at the bbox center: float[] cameraPos = { bboxCenter[0], bboxCenter[1], bboxCenter[2]}; // Move back far enough that the largest of width/height/depth of the bbox can be fully seen within the field of view // These are given by the equations: tan(fieldOfView/2) = (width/2)/xbasedZdist; // tan(fieldOfView/2) = (height/2)/ybasedZdist; // tan(fieldOfView/2) = (depth/2)/zbasedZdist; float xbasedZdist = (float)((bboxMax[0] - bboxMin[0])/(2f * Math.tan(camera.fieldOfView.getValue() / 2f))); float ybasedZdist = (float)((bboxMax[1] - bboxMin[1])/(2f * Math.tan(camera.fieldOfView.getValue() / 2f))); float zbasedZdist = (float)((bboxMax[2] - bboxMin[2])/(2f * Math.tan(camera.fieldOfView.getValue() / 2f))); // Use the bigger of the two distances, with some slack for good measure: if (xbasedZdist > ybasedZdist && xbasedZdist > zbasedZdist) cameraPos[2] += (1f + BACKUP_SLACK) * xbasedZdist; else if ( ybasedZdist > zbasedZdist) cameraPos[2] += (1f + BACKUP_SLACK) * ybasedZdist; else cameraPos[2] += (1f + BACKUP_SLACK) * zbasedZdist; // If needed, transform position into camera parent space: if (pathToCameraParent != null){ // Second argument means get matrix going from top to bottom of path. float[] xfMat = MatUtil.getMatrixAlongPath(pathToCameraParent, false); MatUtil.multVecMatrix(xfMat, cameraPos); } // Now set it the position in the camera camera.position.setValue(cameraPos); // Set the initial cameraHeading and cameraPitch from the current values in the camera. Quaternion initCamQuat = new Quaternion(); initCamQuat.setAxisAngle(camera.orientation.getValue()); float[] headingPitchRoll = new float[3]; initCamQuat.getEulers(headingPitchRoll); cameraHeading = headingPitchRoll[0] + 3.14f; cameraPitch = -headingPitchRoll[1]; } }