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: * A mouse behavior that allows user to pick and minipulate scene graph objects.
048: * Manipulations include translation, rotation and zoom.
049: * The behavior maps mouse drags to different transforms depending
050: * upon the mosue button held down:
051: *
052: * Button 1 (left)
053: * Translation of dragged object
054: * Button 2 (middle)
055: * Rotation of dragged object around itself
056: * Button 3 (right)
057: * Zoom of dragged object towards user
058: *
059: * To support systems with 2 or 1 mouse buttons, the following
060: * alternate mappings are supported while dragging with any mouse
061: * button held down and zero or more keyboard modifiers held down:
062: *
063: * No modifiers = Button 1
064: * ALT = Button 2
065: * Meta = Button 3
066: * Control = Button 3
067: *
068: * Common usage:
069: * <p>
070: * 1. Create your scene graph.
071: * <p>
072: * 2. Create this behavior with root and canvas.
073: * <p>
074: * <blockquote><pre>
075: * StandardPickViewerBehavior behavior = new StandardPickViewerBehavior(canvas, root, bounds);
076: * root.addChild(behavior);
077: * </pre></blockquote>
078: * <p>
079: * The above behavior will monitor for any picking events on
080: * the scene graph (below root node) and handle mouse drags on pick hits.
081: * Note the root node can also be a subgraph node of the scene graph (rather
082: * than the topmost).
083: */
084:
085: // This class is inspired by the PickMouseBehavior, PickMouseRotate,
086: // PickMouseTranslate, and PickMouseZoom utility behaviors provided with
087: // Java 3D. This class differs from those utilities in that it:
088: //
089: // (a) encapsulates all three behaviors into one in order to
090: // enforce a specific symantic
091: //
092: // (b) supports set/get of the rotation and translation factors
093: // that control the speed of movement.
094: //
095: // (c) supports the "Control" modifier as an alternative to the
096: // "Meta" modifier not present on PC, Mac, and most non-Sun
097: // keyboards. This makes button3 behavior usable on PCs,
098: // Macs, and other systems with fewer than 3 mouse buttons.
099:
100: public class StandardPickViewer extends PickViewerBehavior {
101:
102: // Previous cursor location
103: protected int previousX = 0;
104: protected int previousY = 0;
105:
106: // Saved standard cursor
107: protected Cursor savedCursor = null;
108:
109: private StandardPickViewerCallback callback = null;
110:
111: protected Shape3D pickedShape;
112: protected Material savedMaterial;
113:
114: /**
115: * Construct a behavior that listens to mouse movement
116: * and button presses to generate rotation and translation
117: * transforms written into a transform group given later
118: * with the setTransformGroup( ) method.
119: */
120: public StandardPickViewer() {
121:
122: super ();
123:
124: }
125:
126: /**
127: * Construct a behavior that listens to mouse movement
128: * and button presses to generate rotation and translation
129: * transforms written into a transform group given later
130: * with the setTransformGroup( ) method.
131: *
132: * @param parent The AWT Component that contains the area
133: * generating mouse events.
134: */
135: public StandardPickViewer(Canvas3D parent) {
136:
137: super (parent);
138:
139: }
140:
141: /**
142: * Construct a behavior that listens to mouse movement
143: * and button presses to generate rotation and translation
144: * transforms written into the given transform group.
145: *
146: * @param transformGroup The transform group to be modified
147: * by the behavior.
148: */
149: public StandardPickViewer(BranchGroup branchGroup) {
150:
151: super (branchGroup);
152:
153: }
154:
155: /**
156: * Construct a behavior that listens to mouse movement
157: * and button presses to generate rotation and translation
158: * transforms written into the given transform group.
159: *
160: * @param transformGroup The transform group to be modified
161: * by the behavior.
162: * @param parent The AWT Component that contains the area
163: * generating mouse events.
164: */
165: public StandardPickViewer(BranchGroup branchGroup, Canvas3D parent,
166: Bounds bounds) {
167:
168: super (branchGroup, parent);
169: this .setSchedulingBounds(bounds);
170:
171: }
172:
173: /**
174: * Respond to a button1 event (press, release, or drag).
175: *
176: * @param mouseEvent A MouseEvent to respond to.
177: */
178: public void onButton1(MouseEvent mev) {
179:
180: if (subjectBranchGroup == null)
181: return;
182:
183: int x = mev.getX();
184: int y = mev.getY();
185:
186: if (mev.getID() == MouseEvent.MOUSE_PRESSED) {
187:
188: if (parentCanvas3D != null) {
189: previousX = x;
190: previousY = y;
191: pickHighlightAndSetCursor(x, y);
192: return;
193: }
194: }
195: if (mev.getID() == MouseEvent.MOUSE_RELEASED) {
196: revertMaterialAndCursor();
197: return;
198: }
199:
200: //
201: // Mouse moved while button down: create a rotation
202: //
203: // Compute the delta in X and Y from the previous
204: // position. Use the delta to compute rotation
205: // angles with the mapping:
206: //
207: // positive X mouse delta --> positive Y-axis rotation
208: // positive Y mouse delta --> positive X-axis rotation
209: //
210: // where positive X mouse movement is to the right, and
211: // positive Y mouse movement is **down** the screen.
212: //
213: if ((subjectTransformGroup != null)
214: && (subjectTransformGroup
215: .getCapability(TransformGroup.ALLOW_TRANSFORM_READ))
216: && (subjectTransformGroup
217: .getCapability(TransformGroup.ALLOW_TRANSFORM_WRITE))) {
218:
219: int deltaX = x - previousX;
220: int deltaY = y - previousY;
221:
222: if (deltaX > UNUSUAL_XDELTA || deltaX < -UNUSUAL_XDELTA
223: || deltaY > UNUSUAL_YDELTA
224: || deltaY < -UNUSUAL_YDELTA) {
225: // Deltas are too huge to be believable. Probably a glitch.
226: // Don't record the new XY location, or do anything.
227: return;
228: }
229:
230: double xRotationAngle = deltaY * XRotationFactor;
231: double yRotationAngle = deltaX * YRotationFactor;
232:
233: //
234: // Build transforms
235: //
236: transform1.rotX(xRotationAngle);
237: transform2.rotY(yRotationAngle);
238:
239: // Get and save the current transform matrix
240: subjectTransformGroup.getTransform(currentTransform);
241: currentTransform.get(matrix);
242: translate.set(matrix.m03, matrix.m13, matrix.m23);
243:
244: // Translate to the origin, rotate, then translate back
245: currentTransform.setTranslation(origin);
246: currentTransform.mul(transform1, currentTransform);
247: currentTransform.mul(transform2, currentTransform);
248: currentTransform.setTranslation(translate);
249:
250: // Update the transform group
251: subjectTransformGroup.setTransform(currentTransform);
252: if (callback != null)
253: callback.transformChanged(
254: StandardPickViewerCallback.ROTATE,
255: currentTransform);
256:
257: previousX = x;
258: previousY = y;
259: } else if (callback != null)
260: callback.transformChanged(
261: StandardPickViewerCallback.NO_PICK, null);
262:
263: }
264:
265: /**
266: * Respond to a button2 event (press, release, or drag).
267: *
268: * @param mouseEvent A MouseEvent to respond to.
269: */
270: public void onButton2(MouseEvent mev) {
271:
272: if (subjectTransformGroup == null)
273: return;
274:
275: int x = mev.getX();
276: int y = mev.getY();
277:
278: if (mev.getID() == MouseEvent.MOUSE_PRESSED) {
279:
280: if (parentCanvas3D != null) {
281: previousX = x;
282: previousY = y;
283: pickHighlightAndSetCursor(x, y);
284: return;
285: }
286: }
287: if (mev.getID() == MouseEvent.MOUSE_RELEASED) {
288: revertMaterialAndCursor();
289: return;
290: }
291:
292: //
293: // Mouse moved while button down: create a translation
294: //
295: // Compute the delta in Y from the previous
296: // position. Use the delta to compute translation
297: // distances with the mapping:
298: //
299: // positive Y mouse delta --> positive Y-axis translation
300: //
301: // where positive X mouse movement is to the right, and
302: // positive Y mouse movement is **down** the screen.
303: //
304: if ((subjectTransformGroup != null)
305: && (subjectTransformGroup
306: .getCapability(TransformGroup.ALLOW_TRANSFORM_READ))
307: && (subjectTransformGroup
308: .getCapability(TransformGroup.ALLOW_TRANSFORM_WRITE))) {
309:
310: int deltaY = y - previousY;
311:
312: if (deltaY > UNUSUAL_YDELTA || deltaY < -UNUSUAL_YDELTA) {
313: // Deltas are too huge to be believable. Probably a glitch.
314: // Don't record the new XY location, or do anything.
315: return;
316: }
317:
318: double zTranslationDistance = deltaY * ZTranslationFactor;
319:
320: //
321: // Build transforms
322: //
323: translate.set(0.0, 0.0, zTranslationDistance);
324: transform1.set(translate);
325:
326: // Get and save the current transform
327: subjectTransformGroup.getTransform(currentTransform);
328:
329: // Translate as needed
330: currentTransform.mul(transform1, currentTransform);
331:
332: // Update the transform group
333: subjectTransformGroup.setTransform(currentTransform);
334:
335: if (callback != null)
336: callback.transformChanged(
337: StandardPickViewerCallback.ROTATE,
338: currentTransform);
339:
340: previousX = x;
341: previousY = y;
342: } else if (callback != null)
343: callback.transformChanged(StandardPickViewerCallback.ZOOM,
344: null);
345:
346: }
347:
348: /**
349: * Respond to a button3 event (press, release, or drag).
350: *
351: * @param mouseEvent A MouseEvent to respond to.
352: */
353: public void onButton3(MouseEvent mev) {
354:
355: if (subjectTransformGroup == null)
356: return;
357:
358: int x = mev.getX();
359: int y = mev.getY();
360:
361: if (mev.getID() == MouseEvent.MOUSE_PRESSED) {
362:
363: if (parentCanvas3D != null) {
364: previousX = x;
365: previousY = y;
366: pickHighlightAndSetCursor(x, y);
367: return;
368: }
369: }
370: if (mev.getID() == MouseEvent.MOUSE_RELEASED) {
371: revertMaterialAndCursor();
372: return;
373: }
374:
375: //
376: // Mouse moved while button down: create a translation
377: //
378: // Compute the delta in X and Y from the previous
379: // position. Use the delta to compute translation
380: // distances with the mapping:
381: //
382: // positive X mouse delta --> positive X-axis translation
383: // positive Y mouse delta --> negative Y-axis translation
384: //
385: // where positive X mouse movement is to the right, and
386: // positive Y mouse movement is **down** the screen.
387: //
388: if ((subjectTransformGroup != null)
389: && (subjectTransformGroup
390: .getCapability(TransformGroup.ALLOW_TRANSFORM_READ))
391: && (subjectTransformGroup
392: .getCapability(TransformGroup.ALLOW_TRANSFORM_WRITE))) {
393: int deltaX = x - previousX;
394: int deltaY = y - previousY;
395:
396: if (deltaX > UNUSUAL_XDELTA || deltaX < -UNUSUAL_XDELTA
397: || deltaY > UNUSUAL_YDELTA
398: || deltaY < -UNUSUAL_YDELTA) {
399: // Deltas are too huge to be believable. Probably a glitch.
400: // Don't record the new XY location, or do anything.
401: return;
402: }
403:
404: double xTranslationDistance = deltaX * XTranslationFactor;
405: double yTranslationDistance = -deltaY * YTranslationFactor;
406:
407: //
408: // Build transforms
409: //
410: translate.set(xTranslationDistance, yTranslationDistance,
411: 0.0);
412: transform1.set(translate);
413:
414: // Get and save the current transform
415: subjectTransformGroup.getTransform(currentTransform);
416:
417: // Translate as needed
418: currentTransform.mul(transform1, currentTransform);
419:
420: // Update the transform group
421: subjectTransformGroup.setTransform(currentTransform);
422:
423: if (callback != null)
424: callback.transformChanged(
425: StandardPickViewerCallback.TRANSLATE,
426: currentTransform);
427:
428: previousX = x;
429: previousY = y;
430: } else if (callback != null)
431: callback.transformChanged(
432: StandardPickViewerCallback.NO_PICK, null);
433:
434: }
435:
436: /**
437: * Respond to an elapsed frames event (assuming subclass has set up a
438: * wakeup criterion for it).
439: *
440: * @param time A WakeupOnElapsedFrames criterion to respond to.
441: */
442: public void onElapsedFrames(WakeupOnElapsedFrames timeEvent) {
443: // Can't happen
444: }
445:
446: /**
447: * The transformChanged method in the callback class will
448: * be called every time the transform is updated
449: */
450: public void setupCallback(StandardPickViewerCallback callback) {
451:
452: this .callback = callback;
453:
454: }
455:
456: protected void pickHighlightAndSetCursor(int x, int y) {
457:
458: SceneGraphPath sceneGraphPath = pickScene.pickClosest(x, y,
459: pickMode);
460: subjectTransformGroup = (TransformGroup) pickScene.pickNode(
461: sceneGraphPath, PickObject.TRANSFORM_GROUP);
462: if (subjectTransformGroup != null) {
463: savedCursor = parentCanvas3D.getCursor();
464: parentCanvas3D.setCursor(activeCursor);
465: pickedShape = (Shape3D) pickScene.pickNode(sceneGraphPath,
466: PickObject.SHAPE3D);
467: if ((pickedShape != null)
468: && pickedShape
469: .getCapability(Shape3D.ALLOW_APPEARANCE_READ)
470: && pickedShape
471: .getCapability(Shape3D.ALLOW_APPEARANCE_WRITE)) {
472: Appearance pickedAppearance = pickedShape
473: .getAppearance();
474: if ((pickedAppearance != null)
475: && pickedAppearance
476: .getCapability(Appearance.ALLOW_MATERIAL_READ)
477: && pickedAppearance
478: .getCapability(Appearance.ALLOW_MATERIAL_WRITE)) {
479: savedMaterial = pickedShape.getAppearance()
480: .getMaterial();
481: pickedShape.getAppearance().setMaterial(
482: highlightMaterial(savedMaterial));
483: }
484: }
485: }
486:
487: }
488:
489: protected void revertMaterialAndCursor() {
490:
491: if ((subjectTransformGroup != null) && (parentCanvas3D != null)) {
492: parentCanvas3D.setCursor(savedCursor);
493: if ((pickedShape != null)
494: && pickedShape
495: .getCapability(Shape3D.ALLOW_APPEARANCE_READ)
496: && pickedShape
497: .getCapability(Shape3D.ALLOW_APPEARANCE_WRITE)) {
498: Appearance pickedAppearance = pickedShape
499: .getAppearance();
500: if ((pickedAppearance != null)
501: && pickedAppearance
502: .getCapability(Appearance.ALLOW_MATERIAL_READ)
503: && pickedAppearance
504: .getCapability(Appearance.ALLOW_MATERIAL_WRITE)) {
505: pickedShape.getAppearance().setMaterial(
506: savedMaterial);
507: }
508: }
509: }
510:
511: }
512:
513: }
|