001: /*
002: * $RCSfile: Billboard.java,v $
003: *
004: * Copyright 1996-2008 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
006: *
007: * This code is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU General Public License version 2 only, as
009: * published by the Free Software Foundation. Sun designates this
010: * particular file as subject to the "Classpath" exception as provided
011: * by Sun in the LICENSE file that accompanied this code.
012: *
013: * This code is distributed in the hope that it will be useful, but WITHOUT
014: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
015: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
016: * version 2 for more details (a copy is included in the LICENSE file that
017: * accompanied this code).
018: *
019: * You should have received a copy of the GNU General Public License version
020: * 2 along with this work; if not, write to the Free Software Foundation,
021: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
022: *
023: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
024: * CA 95054 USA or visit www.sun.com if you need additional information or
025: * have any questions.
026: *
027: * $Revision: 1.6 $
028: * $Date: 2008/02/28 20:17:19 $
029: * $State: Exp $
030: */
031:
032: package javax.media.j3d;
033:
034: import java.util.Enumeration;
035: import javax.vecmath.Point3f;
036: import javax.vecmath.Point3d;
037: import javax.vecmath.Vector3d;
038: import javax.vecmath.Vector3f;
039: import javax.vecmath.AxisAngle4d;
040:
041: /**
042: * The Billboard behavior node operates on the TransformGroup node
043: * to cause the local +z axis of the TransformGroup to point at
044: * the viewer's eye position. This is done regardless of the transforms
045: * above the specified TransformGroup node in the scene graph.
046: *
047: * <p>
048: * If the alignment mode is ROTATE_ABOUT_AXIS, the rotation will be
049: * around the specified axis. If the alignment mode is
050: * ROTATE_ABOUT_POINT, the rotation will be about the specified
051: * point, with an additional rotation to align the +y axis of the
052: * TransformGroup with the +y axis in the View.
053: *
054: * <p>
055: * Note that in a multiple View system, the alignment is done to
056: * the primary View only.
057: *
058: * <p>
059: * Billboard nodes are ideal for drawing screen aligned-text or
060: * for drawing roughly-symmetrical objects. A typical use might
061: * consist of a quadrilateral that contains a texture of a tree.
062: *
063: * @see OrientedShape3D
064: */
065: public class Billboard extends Behavior {
066: /**
067: * Specifies that rotation should be about the specified axis.
068: */
069: public static final int ROTATE_ABOUT_AXIS = 0;
070:
071: /**
072: * Specifies that rotation should be about the specified point and
073: * that the children's Y-axis should match the view object's Y-axis.
074: */
075: public static final int ROTATE_ABOUT_POINT = 1;
076:
077: // Wakeup condition for Billboard node
078: WakeupOnElapsedFrames wakeupFrame = new WakeupOnElapsedFrames(0,
079: true);
080:
081: // Specifies the billboard's mode of operation. One of ROTATE_AXIAL,
082: // ROTATE_POINT_VIEW, or ROTATE_POINT_WORLD.
083: int mode = ROTATE_ABOUT_AXIS;
084:
085: // Axis about which to rotate.
086: Vector3f axis = new Vector3f(0.0f, 1.0f, 0.0f);
087: Point3f rotationPoint = new Point3f(0.0f, 0.0f, 1.0f);
088: private Vector3d nAxis = new Vector3d(0.0, 1.0, 0.0); // normalized axis
089:
090: // TransformGroup to operate on.
091: TransformGroup tg = null;
092:
093: // reused temporaries
094: private Point3d viewPosition = new Point3d();
095: private Point3d yUpPoint = new Point3d();
096:
097: private Vector3d eyeVec = new Vector3d();
098: private Vector3d yUp = new Vector3d();
099: private Vector3d zAxis = new Vector3d();
100: private Vector3d yAxis = new Vector3d();
101: private Vector3d vector = new Vector3d();
102:
103: private AxisAngle4d aa = new AxisAngle4d();
104:
105: static final double EPSILON = 1.0e-6;
106:
107: /**
108: * Constructs a Billboard node with default parameters.
109: * The default values are as follows:
110: * <ul>
111: * alignment mode : ROTATE_ABOUT_AXIS<br>
112: * alignment axis : Y-axis (0,1,0)<br>
113: * rotation point : (0,0,1)<br>
114: * target transform group: null<br>
115: *</ul>
116: */
117: public Billboard() {
118: nAxis.x = 0.0;
119: nAxis.y = 1.0;
120: nAxis.z = 0.0;
121: }
122:
123: /**
124: * Constructs a Billboard node with default parameters that operates
125: * on the specified TransformGroup node.
126: * The default alignment mode is ROTATE_ABOUT_AXIS rotation with the axis
127: * pointing along the Y axis.
128: * @param tg the TransformGroup node that this Billboard
129: * node operates upon
130: */
131: public Billboard(TransformGroup tg) {
132: this .tg = tg;
133: nAxis.x = 0.0;
134: nAxis.y = 1.0;
135: nAxis.z = 0.0;
136:
137: }
138:
139: /**
140: * Constructs a Billboard node with the specified axis and mode
141: * that operates on the specified TransformGroup node.
142: * The specified axis must not be parallel to the <i>Z</i>
143: * axis--(0,0,<i>z</i>) for any value of <i>z</i>. It is not
144: * possible for the +<i>Z</i> axis to point at the viewer's eye
145: * position by rotating about itself. The target transform will
146: * be set to the identity if the axis is (0,0,<i>z</i>).
147: *
148: * @param tg the TransformGroup node that this Billboard
149: * node operates upon
150: * @param mode alignment mode, one of ROTATE_ABOUT_AXIS or
151: * ROTATE_ABOUT_POINT
152: * @param axis the ray about which the billboard rotates
153: */
154: public Billboard(TransformGroup tg, int mode, Vector3f axis) {
155: this .tg = tg;
156: this .mode = mode;
157: this .axis.set(axis);
158: double invMag;
159: invMag = 1.0 / Math.sqrt(axis.x * axis.x + axis.y * axis.y
160: + axis.z * axis.z);
161: nAxis.x = (double) axis.x * invMag;
162: nAxis.y = (double) axis.y * invMag;
163: nAxis.z = (double) axis.z * invMag;
164:
165: }
166:
167: /**
168: * Constructs a Billboard node with the specified rotation point and mode
169: * that operates on the specified TransformGroup node.
170: * @param tg the TransformGroup node that this Billboard
171: * node operates upon
172: * @param mode alignment mode, one of ROTATE_ABOUT_AXIS or
173: * ROTATE_ABOUT_POINT
174: * @param point the position about which the billboard rotates
175: */
176: public Billboard(TransformGroup tg, int mode, Point3f point) {
177: this .tg = tg;
178: this .mode = mode;
179: this .rotationPoint.set(point);
180: }
181:
182: /**
183: * Sets the alignment mode.
184: * @param mode one of: ROTATE_ABOUT_AXIS or ROTATE_ABOUT_POINT
185: */
186: public void setAlignmentMode(int mode) {
187: this .mode = mode;
188: }
189:
190: /**
191: * Gets the alignment mode.
192: * @return one of: ROTATE_ABOUT_AXIS or ROTATE_ABOUT_POINT
193: */
194: public int getAlignmentMode() {
195: return this .mode;
196: }
197:
198: /**
199: * Sets the alignment axis.
200: * The specified axis must not be parallel to the <i>Z</i>
201: * axis--(0,0,<i>z</i>) for any value of <i>z</i>. It is not
202: * possible for the +<i>Z</i> axis to point at the viewer's eye
203: * position by rotating about itself. The target transform will
204: * be set to the identity if the axis is (0,0,<i>z</i>).
205: *
206: * @param axis the ray about which the billboard rotates
207: */
208: public void setAlignmentAxis(Vector3f axis) {
209: this .axis.set(axis);
210: double invMag;
211: invMag = 1.0 / Math.sqrt(axis.x * axis.x + axis.y * axis.y
212: + axis.z * axis.z);
213: nAxis.x = (double) axis.x * invMag;
214: nAxis.y = (double) axis.y * invMag;
215: nAxis.z = (double) axis.z * invMag;
216:
217: }
218:
219: /**
220: * Sets the alignment axis.
221: * The specified axis must not be parallel to the <i>Z</i>
222: * axis--(0,0,<i>z</i>) for any value of <i>z</i>. It is not
223: * possible for the +<i>Z</i> axis to point at the viewer's eye
224: * position by rotating about itself. The target transform will
225: * be set to the identity if the axis is (0,0,<i>z</i>).
226: *
227: * @param x the x component of the ray about which the billboard rotates
228: * @param y the y component of the ray about which the billboard rotates
229: * @param z the z component of the ray about which the billboard rotates
230: */
231: public void setAlignmentAxis(float x, float y, float z) {
232: this .axis.set(x, y, z);
233: this .axis.set(axis);
234: double invMag;
235: invMag = 1.0 / Math.sqrt(axis.x * axis.x + axis.y * axis.y
236: + axis.z * axis.z);
237: nAxis.x = (double) axis.x * invMag;
238: nAxis.y = (double) axis.y * invMag;
239: nAxis.z = (double) axis.z * invMag;
240:
241: }
242:
243: /**
244: * Gets the alignment axis and sets the parameter to this value.
245: * @param axis the vector that will contain the ray about which
246: * the billboard rotates
247: */
248: public void getAlignmentAxis(Vector3f axis) {
249: axis.set(this .axis);
250: }
251:
252: /**
253: * Sets the rotation point.
254: * @param point the point about which the billboard rotates
255: */
256: public void setRotationPoint(Point3f point) {
257: this .rotationPoint.set(point);
258: }
259:
260: /**
261: * Sets the rotation point.
262: * @param x the x component of the point about which the billboard rotates
263: * @param y the y component of the point about which the billboard rotates
264: * @param z the z component of the point about which the billboard rotates
265: */
266: public void setRotationPoint(float x, float y, float z) {
267: this .rotationPoint.set(x, y, z);
268: }
269:
270: /**
271: * Gets the rotation point and sets the parameter to this value.
272: * @param point the position the Billboard rotates about
273: */
274: public void getRotationPoint(Point3f point) {
275: point.set(this .rotationPoint);
276: }
277:
278: /**
279: * Sets the tranformGroup for this Billboard object.
280: * @param tg the transformGroup node which replaces the current
281: * transformGroup node for this Billboard
282: */
283: public void setTarget(TransformGroup tg) {
284: this .tg = tg;
285: }
286:
287: /**
288: * Returns a copy of the transformGroup associated with this Billboard.
289: * @return the TranformGroup for this Billboard
290: */
291: public TransformGroup getTarget() {
292: return (tg);
293: }
294:
295: /**
296: * Initialize method that sets up initial wakeup criteria.
297: */
298: public void initialize() {
299: // Insert wakeup condition into queue
300: wakeupOn(wakeupFrame);
301: }
302:
303: /**
304: * Process stimulus method that computes appropriate transform.
305: * @param criteria an enumeration of the criteria that caused the
306: * stimulus
307: */
308: public void processStimulus(Enumeration criteria) {
309: double angle = 0.0;
310: double mag, sign;
311: double tx, ty, tz;
312:
313: if (tg == null) {
314: wakeupOn(wakeupFrame);
315: return;
316: }
317:
318: // get viewplatforms's location in virutal world
319: View v = this .getView();
320: if (v == null) {
321: wakeupOn(wakeupFrame);
322: return;
323: }
324: Canvas3D canvas = (Canvas3D) v.getCanvas3D(0);
325: boolean status;
326:
327: Transform3D xform = new Transform3D();
328: Transform3D bbXform = new Transform3D();
329: Transform3D prevTransform = new Transform3D();
330:
331: ((TransformGroupRetained) tg.retained)
332: .getTransform(prevTransform);
333:
334: if (mode == ROTATE_ABOUT_AXIS) { // rotate about axis
335: canvas.getCenterEyeInImagePlate(viewPosition);
336: canvas.getImagePlateToVworld(xform); // xform is imagePlateToLocal
337: xform.transform(viewPosition);
338:
339: // get billboard's transform
340:
341: // since we are using getTransform() to get the transform
342: // of the transformGroup, we need to use getLocalToVworld()
343: // to get the localToVworld which includes the static transform
344:
345: ((NodeRetained) tg.retained).getLocalToVworld(xform);
346:
347: xform.invert(); // xform is now vWorldToLocal
348:
349: // transform the eye position into the billboard's coordinate system
350: xform.transform(viewPosition);
351:
352: // eyeVec is a vector from the local origin to the eye pt in local
353: eyeVec.set(viewPosition);
354: eyeVec.normalize();
355:
356: // project the eye into the rotation plane
357: status = projectToPlane(eyeVec, nAxis);
358:
359: // If the first project was successful ..
360: if (status) {
361: // project the z axis into the rotation plane
362: zAxis.x = 0.0;
363: zAxis.y = 0.0;
364: zAxis.z = 1.0;
365: status = projectToPlane(zAxis, nAxis);
366: }
367:
368: ((TransformGroupRetained) tg.retained).getTransform(xform);
369: if (status) {
370: // compute the sign of the angle by checking if the cross product
371: // of the two vectors is in the same direction as the normal axis
372: vector.cross(eyeVec, zAxis);
373: if (vector.dot(nAxis) > 0.0) {
374: sign = 1.0;
375: } else {
376: sign = -1.0;
377: }
378:
379: // compute the angle between the projected eye vector and the
380: // projected z
381: double dot = eyeVec.dot(zAxis);
382:
383: if (dot > 1.0f) {
384: dot = 1.0f;
385: } else if (dot < -1.0f) {
386: dot = -1.0f;
387: }
388:
389: angle = sign * Math.acos(dot);
390:
391: // use -angle because xform is to *undo* rotation by angle
392: aa.x = nAxis.x;
393: aa.y = nAxis.y;
394: aa.z = nAxis.z;
395: aa.angle = -angle;
396: bbXform.set(aa);
397: if (!prevTransform.epsilonEquals(bbXform, EPSILON)) {
398: // Optimization for Billboard since it use passive
399: // behavior
400: // set the transform on the Billboard TG
401: tg.setTransform(bbXform);
402: }
403: } else {
404: bbXform.setIdentity();
405: if (!prevTransform.epsilonEquals(bbXform, EPSILON)) {
406: tg.setTransform(bbXform);
407: }
408: }
409:
410: } else { // rotate about point
411: // Need to rotate Z axis to point to eye, and Y axis to be
412: // parallel to view platform Y axis, rotating around rotation pt
413:
414: Transform3D zRotate = new Transform3D();
415:
416: // get the eye point
417: canvas.getCenterEyeInImagePlate(viewPosition);
418:
419: // derive the yUp point
420: yUpPoint.set(viewPosition);
421: yUpPoint.y += 0.01; // one cm in Physical space
422:
423: // transform the points to the Billboard's space
424: canvas.getImagePlateToVworld(xform); // xform is ImagePlateToVworld
425:
426: xform.transform(viewPosition);
427: xform.transform(yUpPoint);
428:
429: // get billboard's transform
430:
431: // since we are using getTransform() to get the transform
432: // of the transformGroup, we need to use getLocalToVworld()
433: // to get the localToVworld which includes the static transform
434:
435: ((NodeRetained) tg.retained).getLocalToVworld(xform);
436:
437: xform.invert(); // xform is vWorldToLocal
438:
439: // transfom points to local coord sys
440: xform.transform(viewPosition);
441: xform.transform(yUpPoint);
442:
443: // Make a vector from viewPostion to 0,0,0 in the BB coord sys
444: eyeVec.set(viewPosition);
445: eyeVec.normalize();
446:
447: // create a yUp vector
448: yUp.set(yUpPoint);
449: yUp.sub(viewPosition);
450: yUp.normalize();
451:
452: // find the plane to rotate z
453: zAxis.x = 0.0;
454: zAxis.y = 0.0;
455: zAxis.z = 1.0;
456:
457: // rotation axis is cross product of eyeVec and zAxis
458: vector.cross(eyeVec, zAxis); // vector is cross product
459:
460: // if cross product is non-zero, vector is rotation axis and
461: // rotation angle is acos(eyeVec.dot(zAxis)));
462: double length = vector.length();
463:
464: if (length > 0.0001) {
465: double dot = eyeVec.dot(zAxis);
466:
467: if (dot > 1.0f) {
468: dot = 1.0f;
469: } else if (dot < -1.0f) {
470: dot = -1.0f;
471: }
472:
473: angle = Math.acos(dot);
474: aa.x = vector.x;
475: aa.y = vector.y;
476: aa.z = vector.z;
477: aa.angle = -angle;
478: zRotate.set(aa);
479: } else {
480: // no rotation needed, set to identity (scale = 1.0)
481: zRotate.set(1.0);
482: }
483:
484: // Transform the yAxis by zRotate
485: yAxis.x = 0.0;
486: yAxis.y = 1.0;
487: yAxis.z = 0.0;
488: zRotate.transform(yAxis);
489:
490: // project the yAxis onto the plane perp to the eyeVec
491: status = projectToPlane(yAxis, eyeVec);
492:
493: if (status) {
494: // project the yUp onto the plane perp to the eyeVec
495: status = projectToPlane(yUp, eyeVec);
496: }
497:
498: ((TransformGroupRetained) tg.retained).getTransform(xform);
499: if (status) {
500: // rotation angle is acos(yUp.dot(yAxis));
501: double dot = yUp.dot(yAxis);
502:
503: // Fix numerical error, otherwise acos return NULL
504: if (dot > 1.0f) {
505: dot = 1.0f;
506: } else if (dot < -1.0f) {
507: dot = -1.0f;
508: }
509:
510: angle = Math.acos(dot);
511:
512: // check the sign by looking a the cross product vs the eyeVec
513: vector.cross(yUp, yAxis); // vector is cross product
514: if (eyeVec.dot(vector) < 0) {
515: angle *= -1;
516: }
517: aa.x = eyeVec.x;
518: aa.y = eyeVec.y;
519: aa.z = eyeVec.z;
520: aa.angle = -angle;
521:
522: xform.set(aa); // xform is now yRotate
523:
524: // rotate around the rotation point
525: vector.x = rotationPoint.x;
526: vector.y = rotationPoint.y;
527: vector.z = rotationPoint.z; // vector to translate to RP
528: bbXform.set(vector); // translate to RP
529: bbXform.mul(xform); // yRotate
530: bbXform.mul(zRotate); // zRotate
531: vector.scale(-1.0); // vector to translate back
532: xform.set(vector); // xform to translate back
533: bbXform.mul(xform); // translate back
534:
535: if (!prevTransform.epsilonEquals(bbXform, EPSILON)) {
536: // set the transform on the Billboard TG
537: tg.setTransform(bbXform);
538: }
539: } else {
540: bbXform.setIdentity();
541: if (!prevTransform.epsilonEquals(bbXform, EPSILON)) {
542: tg.setTransform(bbXform);
543: }
544: }
545: }
546:
547: // Insert wakeup condition into queue
548: wakeupOn(wakeupFrame);
549: }
550:
551: private boolean projectToPlane(Vector3d projVec, Vector3d planeVec) {
552: double dis = planeVec.dot(projVec);
553: projVec.x = projVec.x - planeVec.x * dis;
554: projVec.y = projVec.y - planeVec.y * dis;
555: projVec.z = projVec.z - planeVec.z * dis;
556:
557: double length = projVec.length();
558:
559: if (length < EPSILON) {
560: return false;
561: }
562: projVec.scale(1 / length);
563: return true;
564: }
565:
566: /**
567: * Creates a new instance of the node. This routine is called
568: * by <code>cloneTree</code> to duplicate the current node.
569: * @param forceDuplicate when set to <code>true</code>, causes the
570: * <code>duplicateOnCloneTree</code> flag to be ignored. When
571: * <code>false</code>, the value of each node's
572: * <code>duplicateOnCloneTree</code> variable determines whether
573: * NodeComponent data is duplicated or copied.
574: *
575: * @see Node#cloneTree
576: * @see Node#cloneNode
577: * @see Node#duplicateNode
578: * @see NodeComponent#setDuplicateOnCloneTree
579: */
580: public Node cloneNode(boolean forceDuplicate) {
581: Billboard b = new Billboard();
582: b.duplicateNode(this , forceDuplicate);
583: return b;
584: }
585:
586: /**
587: * Copies all Billboard information from
588: * <code>originalNode</code> into
589: * the current node. This method is called from the
590: * <code>cloneNode</code> method which is, in turn, called by the
591: * <code>cloneTree</code> method.<P>
592: *
593: * @param originalNode the original node to duplicate.
594: * @param forceDuplicate when set to <code>true</code>, causes the
595: * <code>duplicateOnCloneTree</code> flag to be ignored. When
596: * <code>false</code>, the value of each node's
597: * <code>duplicateOnCloneTree</code> variable determines whether
598: * NodeComponent data is duplicated or copied.
599: *
600: * @exception RestrictedAccessException if this object is part of a live
601: * or compiled scenegraph.
602: *
603: * @see Node#duplicateNode
604: * @see Node#cloneTree
605: * @see NodeComponent#setDuplicateOnCloneTree
606: */
607: void duplicateAttributes(Node originalNode, boolean forceDuplicate) {
608: super .duplicateAttributes(originalNode, forceDuplicate);
609:
610: Billboard bb = (Billboard) originalNode;
611:
612: setAlignmentMode(bb.getAlignmentMode());
613:
614: Vector3f v = new Vector3f();
615: bb.getAlignmentAxis(v);
616: setAlignmentAxis(v);
617:
618: Point3f p = new Point3f();
619: bb.getRotationPoint(p);
620: setRotationPoint(p);
621:
622: // this will be updated by updateNodeReferences() later
623: setTarget(bb.getTarget());
624: }
625:
626: /**
627: * Callback used to allow a node to check if any scene graph objects
628: * referenced
629: * by that node have been duplicated via a call to <code>cloneTree</code>.
630: * This method is called by <code>cloneTree</code> after all nodes in
631: * the sub-graph have been duplicated. The cloned Leaf node's method
632: * will be called and the Leaf node can then look up any object references
633: * by using the <code>getNewObjectReference</code> method found in the
634: * <code>NodeReferenceTable</code> object. If a match is found, a
635: * reference to the corresponding object in the newly cloned sub-graph
636: * is returned. If no corresponding reference is found, either a
637: * DanglingReferenceException is thrown or a reference to the original
638: * object is returned depending on the value of the
639: * <code>allowDanglingReferences</code> parameter passed in the
640: * <code>cloneTree</code> call.
641: * <p>
642: * NOTE: Applications should <i>not</i> call this method directly.
643: * It should only be called by the cloneTree method.
644: *
645: * @param referenceTable a NodeReferenceTableObject that contains the
646: * <code>getNewObjectReference</code> method needed to search for
647: * new object instances.
648: * @see NodeReferenceTable
649: * @see Node#cloneTree
650: * @see DanglingReferenceException
651: */
652: public void updateNodeReferences(NodeReferenceTable referenceTable) {
653: super .updateNodeReferences(referenceTable);
654:
655: // check for new TransformGroup
656: TransformGroup g = getTarget();
657: if (g != null) {
658: setTarget((TransformGroup) referenceTable
659: .getNewObjectReference(g));
660: }
661: }
662: }
|