// // KeyboardBehaviorJ3D.java // /* VisAD system for interactive analysis and visualization of numerical data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and Tommy Jasmin. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ package visad.java3d; import java.awt.AWTEvent; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.rmi.RemoteException; import java.util.Enumeration; import javax.media.j3d.Behavior; import javax.media.j3d.BoundingSphere; import javax.media.j3d.Bounds; import javax.media.j3d.WakeupCondition; import javax.media.j3d.WakeupCriterion; import javax.media.j3d.WakeupOnAWTEvent; import javax.media.j3d.WakeupOr; import javax.vecmath.Point3d; import visad.DisplayEvent; import visad.DisplayImpl; import visad.DisplayRenderer; import visad.KeyboardBehavior; import visad.MouseBehavior; import visad.ProjectionControl; import visad.VisADException; /** * KeyboardBehaviorJ3D is the VisAD class for keyboard control of * rotation, translation, and zoom of display in Java3D. * @author Troy Sandblom NCAR/RAP May, 2000 * @author Don Murray (adapted to VisAD with input from Curtis) */ public class KeyboardBehaviorJ3D extends Behavior implements KeyboardBehavior { private ProjectionControl proj; private DisplayRenderer displayRenderer; private MouseBehavior mouseBehavior; private double rotateAmount = 5.0; private double scaleAmount = .05; private double transAmount = .1; /** Identifier for function to rotate positively around the Z viewing axis*/ public static final int ROTATE_Z_POS = 7; /** Identifier for function to rotate negatively around the Z viewing axis*/ public static final int ROTATE_Z_NEG = 8; /** Identifier for function to rotate positively around the X viewing axis*/ public static final int ROTATE_X_POS = 9; /** Identifier for function to rotate negatively around the X viewing axis*/ public static final int ROTATE_X_NEG = 10; /** Identifier for function to rotate positively around the Y viewing axis*/ public static final int ROTATE_Y_POS = 11; /** Identifier for function to rotate negatively around the Y viewing axis*/ public static final int ROTATE_Y_NEG = 12; /* WLH 19 Feb 2001 // Maximum number of functions for this behavior private final int MAX_FUNCTIONS = 13; private int[] functionKeys = new int[MAX_FUNCTIONS]; private int[] functionMods = new int[MAX_FUNCTIONS]; */ private int MAX_FUNCTIONS; private int[] functionKeys = null; private int[] functionMods = null; /** * Condition that causes this Behavior to wake up. */ protected WakeupCondition wakeupCondition = null; /** * Construct a new keyboard behavior for the DisplayRenderer. You * need to add the behavior to the DisplayRenderer if you want to * use it. Default key assignments use the arrow keys to simulate * the default VisAD mouse behavior.<P> * Rotation (3D renderer only): * <UL> * <LI>Arrow keys * - rotate up (X_NEG), down(X_POS), left(Y_NEG), right(Y_POS) * </UL> * Zoom: * <UL> * <LI>Shift + Up arrow - zoom in (ZOOM_IN) * <LI>Shift + Down arrow - zoom out (ZOOM_OUT) * </UL> * Rotate: * <UL> * <LI>Shift + Left arrow - rotate left around axis perpendicular to screen * (Z_POS) * <LI>Shift + Right arrow - rotate right around axis perpendicular * to screen (Z_NEG) * </UL> * Translate: * <UL> * <LI>Ctrl + Arrow keys - translate up, down, left, right * </UL> * Reset: * <UL> * <LI>Ctrl + R key - reset to original projection (RESET) * </UL> * <P> * @see DisplayRendererJ3D#addKeyboardBehavior(KeyboardBehaviorJ3D behavior) * @see #mapKeyToFunction(int function, int keycode, int modifiers) to * change default key to function mappings * @param r DisplayRenderer to use. */ public KeyboardBehaviorJ3D(DisplayRendererJ3D r) { displayRenderer = r; boolean mode2D = displayRenderer.getMode2D(); proj = displayRenderer.getDisplay().getProjectionControl(); mouseBehavior = displayRenderer.getMouseBehavior(); WakeupCriterion[] wakeupCriteria = { new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED), new WakeupOnAWTEvent(KeyEvent.KEY_RELEASED), }; wakeupCondition = new WakeupOr(wakeupCriteria); Bounds bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 2000000.0); this.setSchedulingBounds(bounds); // WLH 19 Feb 2001 MAX_FUNCTIONS = 13; functionKeys = new int[MAX_FUNCTIONS]; functionMods = new int[MAX_FUNCTIONS]; // initialize array functions mapKeyToFunction( TRANSLATE_UP, KeyEvent.VK_UP, (mode2D == true) ? NO_MASK : InputEvent.CTRL_MASK); mapKeyToFunction( TRANSLATE_DOWN, KeyEvent.VK_DOWN, (mode2D == true) ? NO_MASK : InputEvent.CTRL_MASK); mapKeyToFunction( TRANSLATE_LEFT, KeyEvent.VK_LEFT, (mode2D == true) ? NO_MASK : InputEvent.CTRL_MASK); mapKeyToFunction( TRANSLATE_RIGHT, KeyEvent.VK_RIGHT, (mode2D == true) ? NO_MASK : InputEvent.CTRL_MASK); mapKeyToFunction(ZOOM_IN, KeyEvent.VK_UP, InputEvent.SHIFT_MASK); mapKeyToFunction(ZOOM_OUT, KeyEvent.VK_DOWN, InputEvent.SHIFT_MASK); mapKeyToFunction(RESET, KeyEvent.VK_R, InputEvent.CTRL_MASK); mapKeyToFunction(ROTATE_X_POS, KeyEvent.VK_DOWN, NO_MASK); mapKeyToFunction(ROTATE_X_NEG, KeyEvent.VK_UP, NO_MASK); mapKeyToFunction(ROTATE_Y_POS, KeyEvent.VK_LEFT, NO_MASK); mapKeyToFunction(ROTATE_Y_NEG, KeyEvent.VK_RIGHT, NO_MASK); mapKeyToFunction( ROTATE_Z_POS, KeyEvent.VK_LEFT, InputEvent.SHIFT_MASK); mapKeyToFunction( ROTATE_Z_NEG, KeyEvent.VK_RIGHT, InputEvent.SHIFT_MASK); } // WLH 19 Feb 2001 public KeyboardBehaviorJ3D(DisplayRendererJ3D r, int num_functions) { displayRenderer = r; boolean mode2D = displayRenderer.getMode2D(); proj = displayRenderer.getDisplay().getProjectionControl(); mouseBehavior = displayRenderer.getMouseBehavior(); WakeupCriterion[] wakeupCriteria = { new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED), new WakeupOnAWTEvent(KeyEvent.KEY_RELEASED), }; wakeupCondition = new WakeupOr(wakeupCriteria); Bounds bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 2000000.0); this.setSchedulingBounds(bounds); MAX_FUNCTIONS = num_functions; functionKeys = new int[MAX_FUNCTIONS]; functionMods = new int[MAX_FUNCTIONS]; } /** * Initialize this behavior. NOTE: Applications should not call * this method. It is called by the Java 3D behavior scheduler. */ public void initialize() { wakeupOn(wakeupCondition); } /** * Process a stimulus meant for this behavior. This method is * invoked when a key is pressed. NOTE: Applications should not * call this method. It is called by the Java 3D behavior scheduler. * @param criteria an enumeration of triggered wakeup criteria */ public void processStimulus(Enumeration criteria) { WakeupOnAWTEvent event; WakeupCriterion genericEvent; AWTEvent[] events; while (criteria.hasMoreElements()) { genericEvent = (WakeupCriterion) criteria.nextElement(); if (genericEvent instanceof WakeupOnAWTEvent) { event = (WakeupOnAWTEvent) genericEvent; events = event.getAWTEvent(); // Process each event for (int i = 0; i < events.length; i++) { if (events[i] instanceof KeyEvent) processKeyEvent((KeyEvent)events[i]); } } wakeupOn(wakeupCondition); } } /** * Maps key represented by keycode & modifiers to the given function. * Each function can only have one key/modifier combination assigned * to it at a time. * @see java.awt.event.KeyEvent * @see java.awt.event.InputEvent * @param function keyboard function (ROTATE_X_POS, ZOOM_IN, etc) * @param keycode <CODE>KeyEvent</CODE> virtual keycodes * @param modifiers <CODE>InputEvent</CODE> key mask */ public void mapKeyToFunction(int function, int keycode, int modifiers) { if (function < 0 || function >= MAX_FUNCTIONS) return; functionKeys[function] = keycode; functionMods[function] = modifiers; } /** * Process a key event. Determines whether a meaningful key was pressed. * @param event KeyEvent stimulus */ public void processKeyEvent(KeyEvent event) { int id = event.getID(); if (id == KeyEvent.KEY_PRESSED) { int modifiers = event.getModifiers(); int keyCode = event.getKeyCode(); // determine whether a meaningful key was pressed for (int i=0; i<MAX_FUNCTIONS; i++) { if (functionKeys[i] == keyCode && (modifiers == functionMods[i])) { execFunction(i); break; } } } // notify DisplayListeners of key event int d_id = -1; if (id == KeyEvent.KEY_PRESSED) d_id = DisplayEvent.KEY_PRESSED; else if (id == KeyEvent.KEY_RELEASED) d_id = DisplayEvent.KEY_RELEASED; if (d_id != -1) { try { DisplayImpl display = displayRenderer.getDisplay(); DisplayEvent e = new DisplayEvent(display, d_id, event); display.notifyListeners(e); } catch (VisADException exc) { } catch (RemoteException exc) { } } } /** * Executes the given function. * @param function function to perform (TRANSLATE_UP, ZOOM_IN, etc) */ public void execFunction(int function) { double transx = 0.0; double transy = 0.0; double scale = 1.0; double anglex = 0.0; double angley = 0.0; double anglez = 0.0; double [] t1 = null; double [] tstart = proj.getMatrix(); double[] transA = { 0.0, 0.0, 0.0 }; double[] rotA = { 0.0, 0.0, 0.0 }; double[] scaleA = { 0.0, 0.0, 0.0 }; MouseBehaviorJ3D.unmake_matrix(rotA, scaleA, transA,tstart); double rotateAngle = rotateAmount; if(displayRenderer.getScaleRotation()) { //Scale down the rotation angle when we are zoomed in rotateAngle = rotateAmount/scaleA[0]; } boolean wasRotate = false; switch (function) { case ROTATE_X_NEG: if (displayRenderer.getMode2D()) break; wasRotate = true; anglex += rotateAngle; t1 = mouseBehavior.make_matrix( anglex, angley, 0.0, 1.0, 0.0, 0.0, 0.0); break; case ROTATE_X_POS: if (displayRenderer.getMode2D()) break; wasRotate = true; anglex -= rotateAngle; t1 = mouseBehavior.make_matrix( anglex, angley, 0.0, 1.0, 0.0, 0.0, 0.0); break; case ROTATE_Y_POS: if (displayRenderer.getMode2D()) break; wasRotate = true; angley += rotateAngle; t1 = mouseBehavior.make_matrix( anglex, angley, 0.0, 1.0, 0.0, 0.0, 0.0); break; case ROTATE_Y_NEG: if (displayRenderer.getMode2D()) break; wasRotate = true; angley -= rotateAngle; t1 = mouseBehavior.make_matrix( anglex, angley, 0.0, 1.0, 0.0, 0.0, 0.0); break; case ROTATE_Z_POS: wasRotate = true; anglez -= rotateAngle; t1 = mouseBehavior.make_matrix( 0.0, 0.0, anglez, 1.0, 0.0, 0.0, 0.0); break; case ROTATE_Z_NEG: wasRotate = true; anglez += rotateAngle; t1 = mouseBehavior.make_matrix( 0.0, 0.0, anglez, 1.0, 0.0, 0.0, 0.0); break; case TRANSLATE_UP: transy += transAmount; t1 = mouseBehavior.make_matrix( 0.0, 0.0, 0.0, 1.0, transx, transy, 0.0); break; case TRANSLATE_DOWN: transy -= transAmount; t1 = mouseBehavior.make_matrix( 0.0, 0.0, 0.0, 1.0, transx, transy, 0.0); break; case TRANSLATE_LEFT: transx -= transAmount; t1 = mouseBehavior.make_matrix( 0.0, 0.0, 0.0, 1.0, transx, transy, 0.0); break; case TRANSLATE_RIGHT: transx += transAmount; t1 = mouseBehavior.make_matrix( 0.0, 0.0, 0.0, 1.0, transx, transy, 0.0); break; case ZOOM_IN: scale += scaleAmount; t1 = mouseBehavior.make_matrix( 0.0, 0.0, 0.0, scale, 0.0, 0.0, 0.0); break; case ZOOM_OUT: scale -= scaleAmount; t1 = mouseBehavior.make_matrix( 0.0, 0.0, 0.0, scale, 0.0, 0.0, 0.0); break; case RESET: tstart = proj.getSavedProjectionMatrix(); t1 = mouseBehavior.make_matrix( 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0); break; default: break; } // t1 = mouseBehavior.make_matrix( // 0.0, 0.0, 0.0, 1.0, transx, transy, 0.0); double[] t2 = null; if (t1 != null) { if(displayRenderer.getRotateAboutCenter() && wasRotate) { t2 = mouseBehavior.make_translate(-transA[0], -transA[1], -transA[2]); tstart = mouseBehavior.multiply_matrix(t2, tstart); t2 = mouseBehavior.make_translate(transA[0], transA[1], transA[2]); } t1 = mouseBehavior.multiply_matrix(t1, tstart); if(t2!=null) { t1 = mouseBehavior.multiply_matrix(t2,t1); } try { proj.setMatrix(t1); } catch (VisADException e) { } catch (RemoteException e) { } } } }