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: // This code is repackaged after the code from David R. Nadeau
036: // Site http://www.sdsc.edu/~nadeau/
037: // Email nadeau@sdsc.edu
038:
039: import java.awt.*;
040: import java.awt.event.*;
041: import java.util.*;
042: import javax.media.j3d.*;
043: import javax.vecmath.*;
044: import com.sun.j3d.utils.behaviors.picking.*;
045:
046: /**
047: * PickViewerBehavior
048: *
049: * @version 1.0, 2000/04/16
050: **/
051:
052: /**
053: * A mouse behavior that allows user to pick and minipulate scene graph objects.
054: * Manipulations include translation, rotation and zoom.
055: * The behavior maps mouse drags to different transforms depending
056: * upon the mosue button held down:
057: *
058: * Button 1 (left)
059: * Translation of dragged object
060: * Button 2 (middle)
061: * Rotation of dragged object around itself
062: * Button 3 (right)
063: * Zoom of dragged object towards user
064: *
065: * To support systems with 2 or 1 mouse buttons, the following
066: * alternate mappings are supported while dragging with any mouse
067: * button held down and zero or more keyboard modifiers held down:
068: *
069: * No modifiers = Button 1
070: * ALT = Button 2
071: * Meta = Button 3
072: * Control = Button 3
073: *
074: * Common usage:
075: * <p>
076: * 1. Create your scene graph.
077: * <p>
078: * 2. Create this behavior with root and canvas.
079: * <p>
080: * <blockquote><pre>
081: * StandardPickViewerBehavior behavior = new StandardPickViewerBehavior(canvas, root, bounds);
082: * root.addChild(behavior);
083: * </pre></blockquote>
084: * <p>
085: * The above behavior will monitor for any picking events on
086: * the scene graph (below root node) and handle mouse drags on pick hits.
087: * Note the root node can also be a subgraph node of the scene graph (rather
088: * than the topmost).
089: */
090:
091: // This class is inspired by the PickMouseBehavior, PickMouseRotate,
092: // PickMouseTranslate, and PickMouseZoom utility behaviors provided with
093: // Java 3D. This class differs from those utilities in that it:
094: //
095: // (a) encapsulates all three behaviors into one in order to
096: // enforce a specific symantic
097: //
098: // (b) supports set/get of the rotation and translation factors
099: // that control the speed of movement.
100: //
101: // (c) supports the "Control" modifier as an alternative to the
102: // "Meta" modifier not present on PC, Mac, and most non-Sun
103: // keyboards. This makes button3 behavior usable on PCs,
104: // Macs, and other systems with fewer than 3 mouse buttons.
105:
106: public abstract class PickViewerBehavior extends Behavior {
107:
108: // Keep track of the transform group who's transform we modify
109: // during mouse motion.
110: protected BranchGroup subjectBranchGroup = null;
111:
112: protected PickObject pickScene;
113:
114: protected int pickMode = PickObject.USE_BOUNDS;
115:
116: protected TransformGroup subjectTransformGroup = null;
117:
118: // Keep a set of wakeup criterion for different mouse-generated
119: // event types.
120: protected WakeupCriterion[] mouseEvents = null;
121: protected WakeupOr mouseCriterion = null;
122:
123: // Track which button was last pressed
124: protected static final int BUTTONNONE = -1;
125: protected static final int BUTTON1 = 0;
126: protected static final int BUTTON2 = 1;
127: protected static final int BUTTON3 = 2;
128: protected int buttonPressed = BUTTONNONE;
129:
130: // Keep a few Transform3Ds for use during event processing. This
131: // avoids having to allocate new ones on each event.
132: protected Transform3D currentTransform = new Transform3D();
133: protected Transform3D transform1 = new Transform3D();
134: protected Transform3D transform2 = new Transform3D();
135: protected Matrix4d matrix = new Matrix4d();
136: protected Vector3d origin = new Vector3d(0.0, 0.0, 0.0);
137: protected Vector3d translate = new Vector3d(0.0, 0.0, 0.0);
138:
139: // Unusual X and Y delta limits.
140: protected static final int UNUSUAL_XDELTA = 400;
141: protected static final int UNUSUAL_YDELTA = 400;
142:
143: protected Canvas3D parentCanvas3D = null;
144:
145: public static final float DEFAULT_HIGHLIGHT_LEVEL = 0.4f;
146: protected float highlightLevel = DEFAULT_HIGHLIGHT_LEVEL;
147:
148: public static final Cursor DEFAULT_PICK_ACTIVE_CURSOR = Cursor
149: .getPredefinedCursor(Cursor.HAND_CURSOR);
150: protected Cursor activeCursor = DEFAULT_PICK_ACTIVE_CURSOR;
151:
152: /**
153: * Construct a viewer behavior that listens to mouse movement
154: * and button presses to generate rotation and translation
155: * transforms written into a transform group given later
156: * with the setTransformGroup( ) method.
157: */
158: public PickViewerBehavior() {
159:
160: this (null, null);
161:
162: }
163:
164: /**
165: * Construct a viewer behavior that listens to mouse movement
166: * and button presses to generate rotation and translation
167: * transforms written into a transform group given later
168: * with the setTransformGroup( ) method.
169: *
170: * @param parent The AWT Component that contains the area
171: * generating mouse events.
172: */
173: public PickViewerBehavior(Canvas3D parent) {
174:
175: this (null, parent);
176:
177: }
178:
179: /**
180: * Construct a viewer behavior that listens to mouse movement
181: * and button presses to generate rotation and translation
182: * transforms written into the given transform group.
183: *
184: * @param transformGroup The transform group to be modified
185: * by the behavior.
186: */
187: public PickViewerBehavior(BranchGroup branchGroup) {
188:
189: this (branchGroup, null);
190:
191: }
192:
193: /**
194: * Construct a viewer behavior that listens to mouse movement
195: * and button presses to generate rotation and translation
196: * transforms written into the given transform group.
197: *
198: * @param transformGroup The transform group to be modified
199: * by the behavior.
200: * @param parent The AWT Component that contains the area
201: * generating mouse events.
202: */
203: public PickViewerBehavior(BranchGroup branchGroup, Canvas3D parent) {
204:
205: super ();
206: subjectBranchGroup = branchGroup;
207: parentCanvas3D = parent;
208: pickScene = new PickObject(parentCanvas3D, subjectBranchGroup);
209:
210: }
211:
212: /**
213: * Set the transform group modified by the viewer behavior.
214: * Setting the transform group to null disables the behavior
215: * until the transform group is again set to an existing group.
216: *
217: * @param transformGroup The new transform group to be modified
218: * by the behavior.
219: */
220: public void setBranchGroup(BranchGroup branchGroup) {
221:
222: subjectBranchGroup = branchGroup;
223: //Useful ??
224: pickScene = new PickObject(parentCanvas3D, subjectBranchGroup);
225:
226: }
227:
228: /**
229: * Get the transform group modified by the viewer behavior.
230: */
231: public BranchGroup getBranchGroup() {
232:
233: return subjectBranchGroup;
234:
235: }
236:
237: /**
238: * Sets the parent component who's cursor will be changed during
239: * mouse drags. If no component is given is given to the
240: * constructor, or set via this method, no cursor changes
241: * will be done.
242: *
243: * @param parent the AWT Component, such as a Frame, within which
244: * cursor changes should take place during
245: * mouse drags
246: */
247: public void setParentComponent(Canvas3D parent) {
248:
249: parentCanvas3D = parent;
250: //Useful ??
251: pickScene = new PickObject(parentCanvas3D, subjectBranchGroup);
252:
253: }
254:
255: /*
256: * Gets the parent frame within which the cursor changes
257: * during mouse drags.
258: *
259: * @return the AWT Component, such as a Frame, within which
260: * cursor changes should take place during
261: * mouse drags. Returns null if no parent is set.
262: */
263: public Canvas3D getParentComponent() {
264:
265: return parentCanvas3D;
266:
267: }
268:
269: /**
270: * Initialize the behavior.
271: */
272: public void initialize() {
273:
274: // Wakeup when the mouse is dragged or when a mouse button
275: // is pressed or released.
276: mouseEvents = new WakeupCriterion[3];
277: mouseEvents[0] = new WakeupOnAWTEvent(MouseEvent.MOUSE_DRAGGED);
278: mouseEvents[1] = new WakeupOnAWTEvent(MouseEvent.MOUSE_PRESSED);
279: mouseEvents[2] = new WakeupOnAWTEvent(MouseEvent.MOUSE_RELEASED);
280: mouseCriterion = new WakeupOr(mouseEvents);
281: wakeupOn(mouseCriterion);
282:
283: }
284:
285: /**
286: * Process a new wakeup. Interpret mouse button presses, releases,
287: * and mouse drags.
288: *
289: * @param criteria The wakeup criteria causing the behavior wakeup.
290: */
291: public void processStimulus(Enumeration criteria) {
292:
293: WakeupCriterion wakeup = null;
294: AWTEvent[] event = null;
295: int whichButton = BUTTONNONE;
296:
297: // Process all pending wakeups
298: while (criteria.hasMoreElements()) {
299: wakeup = (WakeupCriterion) criteria.nextElement();
300: if (wakeup instanceof WakeupOnAWTEvent) {
301: event = ((WakeupOnAWTEvent) wakeup).getAWTEvent();
302:
303: // Process all pending events
304: for (int i = 0; i < event.length; i++) {
305: if (event[i].getID() != MouseEvent.MOUSE_PRESSED
306: && event[i].getID() != MouseEvent.MOUSE_RELEASED
307: && event[i].getID() != MouseEvent.MOUSE_DRAGGED)
308: // Ignore uninteresting mouse events
309: continue;
310:
311: //
312: // Regretably, Java event handling (or perhaps
313: // underlying OS event handling) doesn't always
314: // catch button bounces (redundant presses and
315: // releases), or order events so that the last
316: // drag event is delivered before a release.
317: // This means we can get stray events that we
318: // filter out here.
319: //
320: if (event[i].getID() == MouseEvent.MOUSE_PRESSED
321: && buttonPressed != BUTTONNONE)
322: // Ignore additional button presses until a release
323: continue;
324:
325: if (event[i].getID() == MouseEvent.MOUSE_RELEASED
326: && buttonPressed == BUTTONNONE)
327: // Ignore additional button releases until a press
328: continue;
329:
330: if (event[i].getID() == MouseEvent.MOUSE_DRAGGED
331: && buttonPressed == BUTTONNONE)
332: // Ignore drags until a press
333: continue;
334:
335: MouseEvent mev = (MouseEvent) event[i];
336: int modifiers = mev.getModifiers();
337:
338: //
339: // Unfortunately, the underlying event handling
340: // doesn't do a "grab" operation when a mouse button
341: // is pressed. This means that once a button is
342: // pressed, if another mouse button or a keyboard
343: // modifier key is pressed, the delivered mouse event
344: // will show that a different button is being held
345: // down. For instance:
346: //
347: // Action Event
348: // Button 1 press Button 1 press
349: // Drag with button 1 down Button 1 drag
350: // ALT press -
351: // Drag with ALT & button 1 down Button 2 drag
352: // Button 1 release Button 2 release
353: //
354: // The upshot is that we can get a button press
355: // without a matching release, and the button
356: // associated with a drag can change mid-drag.
357: //
358: // To fix this, we watch for an initial button
359: // press, and thenceforth consider that button
360: // to be the one held down, even if additional
361: // buttons get pressed, and despite what is
362: // reported in the event. Only when a button is
363: // released, do we end such a grab.
364: //
365:
366: if (buttonPressed == BUTTONNONE) {
367: // No button is pressed yet, figure out which
368: // button is down now and how to direct events
369: if (((modifiers & InputEvent.BUTTON3_MASK) != 0)
370: || (((modifiers & InputEvent.BUTTON1_MASK) != 0) && ((modifiers & InputEvent.CTRL_MASK) == InputEvent.CTRL_MASK))) {
371: // Button 3 activity (META or CTRL down)
372: whichButton = BUTTON3;
373: } else if ((modifiers & InputEvent.BUTTON2_MASK) != 0) {
374: // Button 2 activity (ALT down)
375: whichButton = BUTTON2;
376: } else {
377: // Button 1 activity (no modifiers down)
378: whichButton = BUTTON1;
379: }
380:
381: // If the event is to press a button, then
382: // record that that button is now down
383: if (event[i].getID() == MouseEvent.MOUSE_PRESSED)
384: buttonPressed = whichButton;
385: } else {
386: // Otherwise a button was pressed earlier and
387: // hasn't been released yet. Assign all further
388: // events to it, even if ALT, META, CTRL, or
389: // another button has been pressed as well.
390: whichButton = buttonPressed;
391: }
392:
393: // Distribute the event
394: switch (whichButton) {
395: case BUTTON1:
396: onButton1(mev);
397: break;
398: case BUTTON2:
399: onButton2(mev);
400: break;
401: case BUTTON3:
402: onButton3(mev);
403: break;
404: default:
405: break;
406: }
407:
408: // If the event is to release a button, then
409: // record that that button is now up
410: if (event[i].getID() == MouseEvent.MOUSE_RELEASED)
411: buttonPressed = BUTTONNONE;
412: }
413: continue;
414: }
415:
416: if (wakeup instanceof WakeupOnElapsedFrames) {
417: onElapsedFrames((WakeupOnElapsedFrames) wakeup);
418: continue;
419: }
420:
421: }
422:
423: // Reschedule us for another wakeup
424: wakeupOn(mouseCriterion);
425:
426: }
427:
428: /**
429: * Default X and Y rotation factors, and XYZ translation factors.
430: */
431: public static final double DEFAULT_XROTATION_FACTOR = 0.02;
432: public static final double DEFAULT_YROTATION_FACTOR = 0.005;
433: public static final double DEFAULT_XTRANSLATION_FACTOR = 0.02;
434: public static final double DEFAULT_YTRANSLATION_FACTOR = 0.02;
435: public static final double DEFAULT_ZTRANSLATION_FACTOR = 0.04;
436:
437: protected double XRotationFactor = DEFAULT_XROTATION_FACTOR;
438: protected double YRotationFactor = DEFAULT_YROTATION_FACTOR;
439: protected double XTranslationFactor = DEFAULT_XTRANSLATION_FACTOR;
440: protected double YTranslationFactor = DEFAULT_YTRANSLATION_FACTOR;
441: protected double ZTranslationFactor = DEFAULT_ZTRANSLATION_FACTOR;
442:
443: /**
444: * Set the X rotation scaling factor for X-axis rotations.
445: *
446: * @param factor The new scaling factor.
447: */
448: public void setXRotationFactor(double factor) {
449:
450: XRotationFactor = factor;
451:
452: }
453:
454: /**
455: * Get the current X rotation scaling factor for X-axis rotations.
456: */
457: public double getXRotationFactor() {
458:
459: return XRotationFactor;
460:
461: }
462:
463: /**
464: * Set the Y rotation scaling factor for Y-axis rotations.
465: *
466: * @param factor The new scaling factor.
467: */
468: public void setYRotationFactor(double factor) {
469:
470: YRotationFactor = factor;
471:
472: }
473:
474: /**
475: * Get the current Y rotation scaling factor for Y-axis rotations.
476: */
477: public double getYRotationFactor() {
478:
479: return YRotationFactor;
480:
481: }
482:
483: /**
484: * Set the X translation scaling factor for X-axis translations.
485: *
486: * @param factor The new scaling factor.
487: */
488: public void setXTranslationFactor(double factor) {
489:
490: XTranslationFactor = factor;
491:
492: }
493:
494: /**
495: * Get the current X translation scaling factor for X-axis translations.
496: */
497: public double getXTranslationFactor() {
498:
499: return XTranslationFactor;
500:
501: }
502:
503: /**
504: * Set the Y translation scaling factor for Y-axis translations.
505: *
506: * @param factor The new scaling factor.
507: */
508: public void setYTranslationFactor(double factor) {
509:
510: YTranslationFactor = factor;
511:
512: }
513:
514: /**
515: * Get the current Y translation scaling factor for Y-axis translations.
516: */
517: public double getYTranslationFactor() {
518:
519: return YTranslationFactor;
520:
521: }
522:
523: /**
524: * Set the Z translation scaling factor for Z-axis translations.
525: *
526: * @param factor The new scaling factor.
527: */
528: public void setZTranslationFactor(double factor) {
529:
530: ZTranslationFactor = factor;
531:
532: }
533:
534: /**
535: * Get the current Z translation scaling factor for Z-axis translations.
536: */
537: public double getZTranslationFactor() {
538:
539: return ZTranslationFactor;
540:
541: }
542:
543: /**
544: * Sets the pickMode component of this PickRotateBehavior to the value of
545: * the passed pickMode.
546: * @param pickMode the pickMode to be copied.
547: **/
548:
549: public void setPickMode(int pickMode) {
550:
551: this .pickMode = pickMode;
552:
553: }
554:
555: /**
556: * Return the pickMode component of this PickRotateBehavior.
557: **/
558: public int getPickMode() {
559:
560: return pickMode;
561:
562: }
563:
564: /**
565: *
566: */
567: public void setHighlightLevel(float level) {
568:
569: highlightLevel = level;
570:
571: }
572:
573: /**
574: *
575: */
576: public float getHighlightLevel() {
577:
578: return highlightLevel;
579:
580: }
581:
582: /**
583: * Set the active cursor to work with when an event occurs.
584: *
585: * @param cursor The cursor active when the user picks something.
586: */
587: public void setActiveCursor(Cursor cursor) {
588:
589: activeCursor = cursor;
590:
591: }
592:
593: /**
594: * Get the active cursor we work with when an event occurs.
595: *
596: * @param cursor The cursor active when the user picks something.
597: */
598: public Cursor getActiveCursor() {
599:
600: return activeCursor;
601:
602: }
603:
604: /**
605: *
606: */
607: protected Material highlightMaterial(Material material) {
608:
609: if ((material != null)
610: && material
611: .getCapability(Material.ALLOW_COMPONENT_READ)) {
612:
613: Color3f ambientColor = new Color3f();
614: Color3f diffuseColor = new Color3f();
615: Color3f emissiveColor = new Color3f();
616: Color3f specularColor = new Color3f();
617:
618: material.getAmbientColor(ambientColor);
619: material.getEmissiveColor(emissiveColor);
620: material.getDiffuseColor(diffuseColor);
621: material.getSpecularColor(specularColor);
622:
623: return new Material(highlightColor(ambientColor),
624: emissiveColor, highlightColor(diffuseColor),
625: specularColor, material.getShininess());
626: } else
627: return material;
628:
629: }
630:
631: /**
632: *
633: */
634: protected Color3f highlightColor(Color3f color3f) {
635:
636: Color3f highlightedColor = new Color3f();
637:
638: highlightedColor.x = Math.min(color3f.x + highlightLevel, 1.0f);
639: highlightedColor.y = Math.min(color3f.y + highlightLevel, 1.0f);
640: highlightedColor.z = Math.min(color3f.z + highlightLevel, 1.0f);
641:
642: return highlightedColor;
643:
644: }
645:
646: /**
647: * Respond to a button1 event (press, release, or drag).
648: *
649: * @param mouseEvent A MouseEvent to respond to.
650: */
651: public abstract void onButton1(MouseEvent mouseEvent);
652:
653: /**
654: * Respond to a button2 event (press, release, or drag).
655: *
656: * @param mouseEvent A MouseEvent to respond to.
657: */
658: public abstract void onButton2(MouseEvent mouseEvent);
659:
660: /**
661: * Responed to a button3 event (press, release, or drag).
662: *
663: * @param mouseEvent A MouseEvent to respond to.
664: */
665: public abstract void onButton3(MouseEvent mouseEvent);
666:
667: /**
668: * Respond to an elapsed frames event (assuming subclass has set up a
669: * wakeup criterion for it).
670: *
671: * @param time A WakeupOnElapsedFrames criterion to respond to.
672: */
673: public abstract void onElapsedFrames(WakeupOnElapsedFrames timeEvent);
674:
675: }
|