001: /*
002: * Copyright (c) 2000 Silvere Martin-Michiellot All Rights Reserved.
003: *
004: * Silvere Martin-Michiellot grants you ("Licensee") a non-exclusive,
005: * royalty free, license to use, modify and redistribute this
006: * software in source and binary code form,
007: * provided that i) this copyright notice and license appear on all copies of
008: * the software; and ii) Licensee does not utilize the software in a manner
009: * which is disparaging to Silvere Martin-Michiellot.
010: *
011: * This software is provided "AS IS," without a warranty of any kind. ALL
012: * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
013: * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
014: * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. Silvere Martin-Michiellot
015: * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES
016: * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
017: * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL
018: * Silvere Martin-Michiellot OR ITS LICENSORS BE LIABLE
019: * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
020: * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
021: * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
022: * OR INABILITY TO USE SOFTWARE, EVEN IF Silvere Martin-Michiellot HAS BEEN
023: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
024: *
025: * This software is not designed or intended for use in on-line control of
026: * aircraft, air traffic, aircraft navigation or aircraft communications; or in
027: * the design, construction, operation or maintenance of any nuclear
028: * facility. Licensee represents and warrants that it will not use or
029: * redistribute the Software for such purposes.
030: *
031: */
032:
033: package com.db.behaviors;
034:
035: import java.awt.*;
036: import java.awt.event.*;
037: import java.util.*;
038: import javax.media.j3d.*;
039: import javax.vecmath.*;
040:
041: // This code is repackaged after WalkViewerBehavior from David R. Nadeau
042: // Site http://www.sdsc.edu/~nadeau/
043: // Email nadeau@sdsc.edu
044:
045: /**
046: * WalkViewerBehavior is a utility class that creates a "walking style"
047: * navigation symantic.
048: *
049: * The behavior wakes up on mouse button presses, releases, and
050: * mouse movements and generates transforms in a "walk style" that
051: * enables the user to walk through a scene, translating and turning
052: * about as if they are within the scene. Such a walk style is
053: * similar to the "Walk" navigation type used by VRML browsers.
054: * <P>
055: * The behavior maps mouse drags to different transforms depending
056: * upon the mouse button held down:
057: * <DL>
058: * <DT> Button 1 (left)
059: * <DD> Horizontal movement --> Y-axis rotation
060: * <DD> Vertical movement --> Z-axis translation
061: *
062: * <DT> Button 2 (middle)
063: * <DD> Horizontal movement --> Y-axis rotation
064: * <DD> Vertical movement --> X-axis rotation
065: *
066: * <DT> Button 3 (right)
067: * <DD> Horizontal movement --> X-axis translation
068: * <DD> Vertical movement --> Y-axis translation
069: * </DL>
070: *
071: * To support systems with 2 or 1 mouse buttons, the following
072: * alternate mappings are supported while dragging with any mouse
073: * button held down and zero or more keyboard modifiers held down:
074: * <UL>
075: * <LI>No modifiers = Button 1
076: * <LI>ALT = Button 2
077: * <LI>Meta = Button 3
078: * <LI>Control = Button 3
079: * </UL>
080: * The behavior automatically modifies a TransformGroup provided
081: * to the constructor. The TransformGroup's transform can be set
082: * at any time by the application or other behaviors to cause the
083: * walk rotation and translation to be reset.
084: * <P>
085: * While a mouse button is down, the behavior automatically changes
086: * the cursor in a given parent AWT Component. If no parent
087: * Component is given, no cursor changes are attempted.
088: *
089: * @version 1.0, 98/04/16
090: * @author David R. Nadeau, San Diego Supercomputer Center
091: */
092: public class WalkViewerBehavior extends ViewerBehavior {
093: // This class is inspired by the MouseBehavior, MouseRotate,
094: // MouseTranslate, and MouseZoom utility behaviors provided with
095: // Java 3D. This class differs from those utilities in that it:
096: //
097: // (a) encapsulates all three behaviors into one in order to
098: // enforce a specific "Walk" symantic
099: //
100: // (b) supports set/get of the rotation and translation factors
101: // that control the speed of movement.
102: //
103: // (c) supports the "Control" modifier as an alternative to the
104: // "Meta" modifier not present on PC, Mac, and most non-Sun
105: // keyboards. This makes button3 behavior usable on PCs,
106: // Macs, and other systems with fewer than 3 mouse buttons.
107:
108: // Previous and initial cursor locations
109: protected int previousX = 0;
110: protected int previousY = 0;
111: protected int initialX = 0;
112: protected int initialY = 0;
113:
114: // Deadzone size (delta from initial XY for which no
115: // translate or rotate action is taken
116: protected static final int DELTAX_DEADZONE = 10;
117: protected static final int DELTAY_DEADZONE = 10;
118:
119: // Saved standard cursor
120: protected Cursor savedCursor = null;
121:
122: /**
123: * Default Rotation and translation scaling factors for
124: * animated movements (Button 1 press).
125: */
126: public static final double DEFAULT_YROTATION_ANIMATION_FACTOR = 0.0002;
127: public static final double DEFAULT_ZTRANSLATION_ANIMATION_FACTOR = 0.01;
128:
129: protected double YRotationAnimationFactor = DEFAULT_YROTATION_ANIMATION_FACTOR;
130: protected double ZTranslationAnimationFactor = DEFAULT_ZTRANSLATION_ANIMATION_FACTOR;
131:
132: /**
133: * Constructs a new walk behavior that converts mouse actions
134: * into rotations and translations. Rotations and translations
135: * are written into a TransformGroup that must be set using the
136: * setTransformGroup method. The cursor will be changed during
137: * mouse actions if the parent frame is set using the
138: * setParentComponent method.
139: *
140: * @return a new WalkViewerBehavior that needs its
141: * TransformGroup and parent Component set
142: */
143: public WalkViewerBehavior() {
144:
145: super ();
146:
147: }
148:
149: /**
150: * Constructs a new walk behavior that converts mouse actions
151: * into rotations and translations. Rotations and translations
152: * are written into a TransformGroup that must be set using the
153: * setTransformGroup method. The cursor will be changed within
154: * the given AWT parent Component during mouse drags.
155: *
156: * @param parent a parent AWT Component within which the cursor
157: * will change during mouse drags
158: *
159: * @return a new WalkViewerBehavior that needs its
160: * TransformGroup and parent Component set
161: */
162: public WalkViewerBehavior(Component parent) {
163:
164: super (parent);
165:
166: }
167:
168: /**
169: * Constructs a new walk behavior that converts mouse actions
170: * into rotations and translations. Rotations and translations
171: * are written into the given TransformGroup. The cursor will
172: * be changed during mouse actions if the parent frame is set
173: * using the setParentComponent method.
174: *
175: * @param transformGroup a TransformGroup whos transform is
176: * read and written by the behavior
177: *
178: * @return a new WalkViewerBehavior that needs its
179: * TransformGroup and parent Component set
180: */
181: public WalkViewerBehavior(TransformGroup transformGroup) {
182:
183: super ();
184: subjectTransformGroup = transformGroup;
185:
186: }
187:
188: /**
189: * Constructs a new walk behavior that converts mouse actions
190: * into rotations and translations. Rotations and translations
191: * are written into the given TransformGroup. The cursor will be
192: * changed within the given AWT parent Component during mouse drags.
193: *
194: * @param transformGroup a TransformGroup whos transform is
195: * read and written by the behavior
196: *
197: * @param parent a parent AWT Component within which the cursor
198: * will change during mouse drags
199: *
200: * @return a new WalkViewerBehavior that needs its
201: * TransformGroup and parent Component set
202: */
203: public WalkViewerBehavior(TransformGroup transformGroup,
204: Component parent) {
205:
206: super (parent);
207: subjectTransformGroup = transformGroup;
208:
209: }
210:
211: /**
212: * Sets the Y rotation animation scaling factor for Y-axis rotations.
213: * This scaling factor is used to control the speed of Y rotation
214: * when button 1 is pressed and dragged.
215: *
216: * @param factor the double Y rotation scaling factor
217: */
218: public void setYRotationAnimationFactor(double factor) {
219:
220: YRotationAnimationFactor = factor;
221:
222: }
223:
224: /**
225: * Gets the current Y animation rotation scaling factor for Y-axis
226: * rotations.
227: *
228: * @return the double Y rotation scaling factor
229: */
230: public double getYRotationAnimationFactor() {
231:
232: return YRotationAnimationFactor;
233:
234: }
235:
236: /**
237: * Sets the Z animation translation scaling factor for Z-axis
238: * translations. This scaling factor is used to control the speed
239: * of Z translation when button 1 is pressed and dragged.
240: *
241: * @param factor the double Z translation scaling factor
242: */
243: public void setZTranslationAnimationFactor(double factor) {
244:
245: ZTranslationAnimationFactor = factor;
246:
247: }
248:
249: /**
250: * Gets the current Z animation translation scaling factor for Z-axis
251: * translations.
252: *
253: * @return the double Z translation scaling factor
254: */
255: public double getZTranslationAnimationFactor() {
256:
257: return ZTranslationAnimationFactor;
258:
259: }
260:
261: /**
262: * Responds to a button1 event (press, release, or drag). On a
263: * press, the method adds a wakeup criterion to the behavior's
264: * set, callling for the behavior to be awoken on each frame.
265: * On a button prelease, this criterion is removed from the set.
266: *
267: * @param mouseEvent the MouseEvent to respond to
268: */
269: public void onButton1(MouseEvent mev) {
270:
271: if (subjectTransformGroup == null)
272: return;
273:
274: int x = mev.getX();
275: int y = mev.getY();
276:
277: if (mev.getID() == MouseEvent.MOUSE_PRESSED) {
278: // Mouse button pressed: record position and change
279: // the wakeup criterion to include elapsed time wakeups
280: // so we can animate.
281: previousX = x;
282: previousY = y;
283: initialX = x;
284: initialY = y;
285:
286: // Change to a "move" cursor
287: if (parentComponent != null) {
288: savedCursor = parentComponent.getCursor();
289: parentComponent.setCursor(Cursor
290: .getPredefinedCursor(Cursor.HAND_CURSOR));
291: }
292: return;
293: }
294: if (mev.getID() == MouseEvent.MOUSE_RELEASED) {
295:
296: // Switch the cursor back
297: if (parentComponent != null)
298: parentComponent.setCursor(savedCursor);
299: return;
300: }
301:
302: int deltaX = previousX - initialX;
303: int deltaY = previousY - initialY;
304:
305: double yRotationAngle = -deltaX * YRotationAnimationFactor;
306: double zTranslationDistance = deltaY
307: * ZTranslationAnimationFactor;
308:
309: //
310: // Build transforms
311: //
312: transform1.rotY(yRotationAngle);
313: translate.set(0.0, 0.0, zTranslationDistance);
314:
315: // Get and save the current transform matrix
316: subjectTransformGroup.getTransform(currentTransform);
317: currentTransform.get(matrix);
318:
319: // Translate to the origin, rotate, then translate back
320: currentTransform.setTranslation(origin);
321: currentTransform.mul(transform1, currentTransform);
322:
323: // Translate back from the origin by the original translation
324: // distance, plus the new walk translation... but force walk
325: // to travel on a plane by ignoring the Y component of a
326: // transformed translation vector.
327: currentTransform.transform(translate);
328: translate.x += matrix.m03; // add in existing X translation
329: translate.y = matrix.m13; // use Y translation
330: translate.z += matrix.m23; // add in existing Z translation
331: currentTransform.setTranslation(translate);
332:
333: // Update the transform group
334: subjectTransformGroup.setTransform(currentTransform);
335:
336: previousX = x;
337: previousY = y;
338:
339: }
340:
341: /**
342: * Responds to a button2 event (press, release, or drag). On a
343: * press, the method records the initial cursor location.
344: * On a drag, the difference between the current and previous
345: * cursor location provides a delta that controls the amount by
346: * which to rotate in X and Y.
347: *
348: * @param mouseEvent the MouseEvent to respond to
349: */
350: public void onButton2(MouseEvent mev) {
351: if (subjectTransformGroup == null)
352: return;
353:
354: int x = mev.getX();
355: int y = mev.getY();
356:
357: if (mev.getID() == MouseEvent.MOUSE_PRESSED) {
358: // Mouse button pressed: record position
359: previousX = x;
360: previousY = y;
361: initialX = x;
362: initialY = y;
363:
364: // Change to a "rotate" cursor
365: if (parentComponent != null) {
366: savedCursor = parentComponent.getCursor();
367: parentComponent.setCursor(Cursor
368: .getPredefinedCursor(Cursor.MOVE_CURSOR));
369: }
370: return;
371: }
372: if (mev.getID() == MouseEvent.MOUSE_RELEASED) {
373: // Mouse button released: do nothing
374:
375: // Switch the cursor back
376: if (parentComponent != null)
377: parentComponent.setCursor(savedCursor);
378: return;
379: }
380:
381: //
382: // Mouse moved while button down: create a rotation
383: //
384: // Compute the delta in X and Y from the previous
385: // position. Use the delta to compute rotation
386: // angles with the mapping:
387: //
388: // positive X mouse delta --> negative Y-axis rotation
389: // positive Y mouse delta --> negative X-axis rotation
390: //
391: // where positive X mouse movement is to the right, and
392: // positive Y mouse movement is **down** the screen.
393: //
394: int deltaX = x - previousX;
395: int deltaY = 0;
396:
397: if (Math.abs(y - initialY) > DELTAY_DEADZONE) {
398: // Cursor has moved far enough vertically to consider
399: // it intentional, so get it's delta.
400: deltaY = y - previousY;
401: }
402:
403: if (deltaX > UNUSUAL_XDELTA || deltaX < -UNUSUAL_XDELTA
404: || deltaY > UNUSUAL_YDELTA || deltaY < -UNUSUAL_YDELTA) {
405: // Deltas are too huge to be believable. Probably a glitch.
406: // Don't record the new XY location, or do anything.
407: return;
408: }
409:
410: double xRotationAngle = -deltaY * XRotationFactor;
411: double yRotationAngle = -deltaX * YRotationFactor;
412:
413: //
414: // Build transforms
415: //
416: transform1.rotX(xRotationAngle);
417: transform2.rotY(yRotationAngle);
418:
419: // Get and save the current transform matrix
420: subjectTransformGroup.getTransform(currentTransform);
421: currentTransform.get(matrix);
422: translate.set(matrix.m03, matrix.m13, matrix.m23);
423:
424: // Translate to the origin, rotate, then translate back
425: currentTransform.setTranslation(origin);
426: currentTransform.mul(transform2, currentTransform);
427: currentTransform.mul(transform1);
428: currentTransform.setTranslation(translate);
429:
430: // Update the transform group
431: subjectTransformGroup.setTransform(currentTransform);
432:
433: previousX = x;
434: previousY = y;
435:
436: }
437:
438: /**
439: * Responds to a button3 event (press, release, or drag).
440: * On a drag, the difference between the current and previous
441: * cursor location provides a delta that controls the amount by
442: * which to translate in X and Y.
443: *
444: * @param mouseEvent the MouseEvent to respond to
445: */
446: public void onButton3(MouseEvent mev) {
447:
448: if (subjectTransformGroup == null)
449: return;
450:
451: int x = mev.getX();
452: int y = mev.getY();
453:
454: if (mev.getID() == MouseEvent.MOUSE_PRESSED) {
455: // Mouse button pressed: record position
456: previousX = x;
457: previousY = y;
458:
459: // Change to a "move" cursor
460: if (parentComponent != null) {
461: savedCursor = parentComponent.getCursor();
462: parentComponent.setCursor(Cursor
463: .getPredefinedCursor(Cursor.MOVE_CURSOR));
464: }
465: return;
466: }
467: if (mev.getID() == MouseEvent.MOUSE_RELEASED) {
468: // Mouse button released: do nothing
469:
470: // Switch the cursor back
471: if (parentComponent != null)
472: parentComponent.setCursor(savedCursor);
473: return;
474: }
475:
476: //
477: // Mouse moved while button down: create a translation
478: //
479: // Compute the delta in X and Y from the previous
480: // position. Use the delta to compute translation
481: // distances with the mapping:
482: //
483: // positive X mouse delta --> positive X-axis translation
484: // positive Y mouse delta --> negative Y-axis translation
485: //
486: // where positive X mouse movement is to the right, and
487: // positive Y mouse movement is **down** the screen.
488: //
489: int deltaX = x - previousX;
490: int deltaY = y - previousY;
491:
492: if (deltaX > UNUSUAL_XDELTA || deltaX < -UNUSUAL_XDELTA
493: || deltaY > UNUSUAL_YDELTA || deltaY < -UNUSUAL_YDELTA) {
494: // Deltas are too huge to be believable. Probably a glitch.
495: // Don't record the new XY location, or do anything.
496: return;
497: }
498:
499: double xTranslationDistance = deltaX * XTranslationFactor;
500: double yTranslationDistance = -deltaY * YTranslationFactor;
501:
502: //
503: // Build transforms
504: //
505: translate.set(xTranslationDistance, yTranslationDistance, 0.0);
506: transform1.set(translate);
507:
508: // Get and save the current transform
509: subjectTransformGroup.getTransform(currentTransform);
510:
511: // Translate as needed
512: currentTransform.mul(transform1);
513:
514: // Update the transform group
515: subjectTransformGroup.setTransform(currentTransform);
516:
517: previousX = x;
518: previousY = y;
519:
520: }
521:
522: /**
523: * Respond to an elapsed frames event (assuming subclass has set up a
524: * wakeup criterion for it).
525: *
526: * @param time A WakeupOnElapsedFrames criterion to respond to.
527: */
528: public void onElapsedFrames(WakeupOnElapsedFrames timeEvent) {
529: // Can't happen
530: }
531:
532: }
|