1 /*
2  * $RCSfile: Text3DRetained.java,v $
3  *
4  * Copyright 1998-2008 Sun Microsystems, Inc.  All Rights Reserved.
5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
6  *
7  * This code is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License version 2 only, as
9  * published by the Free Software Foundation.  Sun designates this
10  * particular file as subject to the "Classpath" exception as provided
11  * by Sun in the LICENSE file that accompanied this code.
12  *
13  * This code is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16  * version 2 for more details (a copy is included in the LICENSE file that
17  * accompanied this code).
18  *
19  * You should have received a copy of the GNU General Public License version
20  * 2 along with this work; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
22  *
23  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
24  * CA 95054 USA or visit www.sun.com if you need additional information or
25  * have any questions.
26  *
27  * $Revision: 1.9 $
28  * $Date: 2008/02/28 20:17:31 $
29  * $State: Exp $
30  */
31 
32 package javax.media.j3d;
33 
34 import javax.vecmath.*;
35 import java.awt.font.*;
36 import java.awt.*;
37 import java.awt.geom.Rectangle2D;
38 import java.util.ArrayList;
39 
40 /**
41  * Implements Text3D class.
42  */
43 class Text3DRetained extends GeometryRetained {
44     /**
45      * Packaged scope variables needed for implementation
46      */
47     Font3D      font3D = null;
48     String      string = null;
49     Point3f     position = new Point3f(0.0f, 0.0f, 0.0f);
50     int         alignment = Text3D.ALIGN_FIRST, path = Text3D.PATH_RIGHT;
51     float       charSpacing = 0.0f;
52     int         numChars = 0;
53     static final int  targetThreads = (J3dThread.UPDATE_TRANSFORM |
54 				       J3dThread.UPDATE_GEOMETRY |
55 				       J3dThread.UPDATE_RENDER);
56     /**
57      * The temporary transforms for this Text3D
58      */
59     Transform3D[] charTransforms = new Transform3D[0];
60 
61     /**
62      * A cached list of geometry arrays for the current settings
63      */
64     GeometryArrayRetained[] geometryList = new GeometryArrayRetained[0];
65     GlyphVector[] glyphVecs = new GlyphVector[0];
66 
67     /**
68      * Bounding box data for this text string.
69      */
70     Point3d lower = new Point3d();
71     Point3d upper = new Point3d();
72 
73 
74     /**
75      * An Array list used for messages
76      */
77     ArrayList newGeometryAtomList = new ArrayList();
78     ArrayList oldGeometryAtomList = new ArrayList();
79 
80 
81     /**
82      * temporary model view matrix for immediate mode only
83      */
84     Transform3D vpcToEc;
85     Transform3D drawTransform;
86 
87 
Text3DRetained()88     Text3DRetained(){
89        this.geoType = GEO_TYPE_TEXT3D;
90     }
91 
92 
computeBoundingBox()93     synchronized void computeBoundingBox() {
94 	Point3d l = new Point3d();
95 	Point3d u = new Point3d();
96         Vector3f location = new Vector3f(this.position);
97         int i, k=0, numTotal=0;
98         double width = 0, height = 0;
99         Rectangle2D bounds;
100 
101         //Reset bounds data
102         l.set(location);
103         u.set(location);
104 
105 	if (numChars != 0) {
106 	    // Set loop counters based on path type
107 	    if (path == Text3D.PATH_RIGHT || path == Text3D.PATH_UP) {
108 		k = 0;
109 		numTotal = numChars + 1;
110 	    } else if (path == Text3D.PATH_LEFT || path == Text3D.PATH_DOWN) {
111 		k = 1;
112 		numTotal = numChars;
113 		// Reset bounds to bounding box if first character
114 		bounds = glyphVecs[0].getVisualBounds();
115 		u.x += bounds.getWidth();
116 		u.y += bounds.getHeight();
117 	    }
118 
119 	    for (i=1; i<numTotal; i++, k++) {
120 		width = glyphVecs[k].getLogicalBounds().getWidth();
121 		bounds = glyphVecs[k].getVisualBounds();
122 		// 'switch' could be outside loop with little hacking,
123 		width += charSpacing;
124 		height = bounds.getHeight();
125 
126 		switch (this.path) {
127 		case Text3D.PATH_RIGHT:
128 		    u.x    += (width);
129 		    if (u.y < (height + location.y)) {
130 			u.y = location.y + height;
131 		    }
132 		    break;
133 		case Text3D.PATH_LEFT:
134 		    l.x    -= (width);
135 		    if (u.y < ( height + location.y)) {
136 			u.y = location.y + height;
137 		    }
138 		    break;
139 		case Text3D.PATH_UP:
140 		    u.y    += height;
141 		    if (u.x < (bounds.getWidth() + location.x)) {
142 			u.x = location.x + bounds.getWidth();
143 		    }
144 		    break;
145 		case Text3D.PATH_DOWN:
146 		    l.y    -= height;
147 		    if (u.x < (bounds.getWidth() + location.x)) {
148 			u.x = location.x + bounds.getWidth();
149 		    }
150 		    break;
151 		}
152 	    }
153 
154 	    // Handle string alignment. ALIGN_FIRST is handled by default
155 	    if (alignment != Text3D.ALIGN_FIRST) {
156 		double cx = (u.x - l.x);
157 		double cy = (u.y - l.y);
158 
159 		if (alignment == Text3D.ALIGN_CENTER) {
160 		    cx *= .5;
161 		    cy *= .5;
162 		}
163 		switch (path) {
164 		case Text3D.PATH_RIGHT:
165 		    l.x -= cx;
166 		    u.x -= cx;
167 		    break;
168 		case Text3D.PATH_LEFT:
169 		    l.x += cx;
170 		    u.x += cx;
171 		    break;
172 		case Text3D.PATH_UP:
173 		    l.y -= cy;
174 		    u.y -= cy;
175 		    break;
176 		case Text3D.PATH_DOWN:
177 		    l.y += cy;
178 		    u.y += cy;
179 		    break;
180 
181 		}
182 	    }
183 	}
184 
185         l.z = 0.0f;
186         if ((font3D == null) || (font3D.fontExtrusion == null)) {
187 	    u.z = l.z;
188         } else {
189 	    u.z = l.z + font3D.fontExtrusion.length;
190 	}
191     }
192 
update()193     void update() {}
194 
195 
196     /**
197      * Returns the Font3D objects used by this Text3D NodeComponent object.
198      *
199      * @return the Font3D object of this Text3D node - null if no Font3D
200      *  has been associated with this node.
201      *
202      * @exception CapabilityNotSetException if appropriate capability is
203      * not set and this object is part of live or compiled scene graph
204      */
getFont3D()205     final Font3D getFont3D() {
206         return this.font3D;
207     }
208 
209     /**
210      * Sets the Font3D object used by this Text3D NodeComponent object.
211      *
212      * @param font3d the Font3D object to associate with this Text3D node.
213      *
214      * @exception CapabilityNotSetException if appropriate capability is
215      * not set and this object is part of live or compiled scene graph
216      */
setFont3D(Font3D font3d)217     final void setFont3D(Font3D font3d) {
218 	geomLock.getLock();
219 	this.font3D = font3d;
220 	updateCharacterData();
221 	geomLock.unLock();
222 	sendDataChangedMessage();
223     }
224 
225     /**
226      * Copies the character string used in the construction of the
227      * Text3D node into the supplied parameter.
228      *
229      * @return a copy of the String object in this Text3D node.
230      *
231      * @exception CapabilityNotSetException if appropriate capability is
232      * not set and this object is part of live or compiled scene graph
233      */
getString()234     final String getString() {
235 	return this.string;
236     }
237 
238     /**
239      * Copies the character string from the supplied parameter into Tex3D
240      * node.
241      *
242      * @param string the String object to recieve the Text3D node's string.
243      *
244      * @exception CapabilityNotSetException if appropriate capability is
245      * not set and this object is part of live or compiled scene graph
246      */
setString(String string)247     final void setString(String string) {
248 	geomLock.getLock();
249 	this.string = string;
250 	if (string == null) {
251 	    numChars = 0;
252 	} else {
253 	    numChars = string.length();
254 	}
255 	updateCharacterData();
256 	geomLock.unLock();
257 	sendDataChangedMessage();
258     }
259 
260     /**
261      * Copies the node's <code>position</code> field into the supplied
262      * parameter.  The <code>position</code> is used to determine the
263      * initial placement of the Text3D string.  The position, combined with
264      * the path and alignment control how the text is displayed.
265      *
266      * @param position the point to position the text.
267      *
268      * @exception CapabilityNotSetException if appropriate capability is
269      * not set and this object is part of live or compiled scene graph
270      *
271      * @see #getAlignment
272      * @see #getPath
273      */
getPosition(Point3f position)274     final void getPosition(Point3f position) {
275         position.set(this.position);
276     }
277 
278     /**
279      * Sets the node's <code>position</code> field to the supplied
280      * parameter.  The <code>position</code> is used to determine the
281      * initial placement of the Text3D string.  The position, combined with
282      * the path and alignment control how the text is displayed.
283      *
284      * @param position the point to position the text.
285      *
286      * @exception CapabilityNotSetException if appropriate capability is
287      * not set and this object is part of live or compiled scene graph
288      *
289      * @see #getAlignment
290      * @see #getPath
291      */
setPosition(Point3f position)292     final void setPosition(Point3f position) {
293 	geomLock.getLock();
294         this.position.set(position);
295         updateTransformData();
296 	geomLock.unLock();
297         sendTransformChangedMessage();
298     }
299 
300     /**
301      * Retrieves the text alignment policy for this Text3D NodeComponent
302      * object. The <code>alignment</code> is used to specify how
303      * glyphs in the string are placed in relation to the
304      * <code>position</code> field.  Valid values for this field
305      * are:
306      * <UL>
307      * <LI> ALIGN_CENTER - the center of the string is placed on the
308      *  <code>position</code> point.
309      * <LI> ALIGN_FIRST - the first character of the string is placed on
310      *   the <code>position</code> point.
311      * <LI> ALIGN_LAST - the last character of the string is placed on the
312      *   <code>position</code> point.
313      * </UL>
314      * The default value of this field is <code>ALIGN_FIRST</code>.
315      *
316      * @return the current alingment policy for this node.
317      *
318      * @exception CapabilityNotSetException if appropriate capability is
319      * not set and this object is part of live or compiled scene graph
320      *
321      * @see #getPosition
322      */
getAlignment()323     final int getAlignment() {
324         return alignment;
325     }
326 
327     /**
328      * Sets the text alignment policy for this Text3D NodeComponent
329      * object. The <code>alignment</code> is used to specify how
330      * glyphs in the string are placed in relation to the
331      * <code>position</code> field.  Valid values for this field
332      * are:
333      * <UL>
334      * <LI> ALIGN_CENTER - the center of the string is placed on the
335      *  <code>position</code> point.
336      * <LI> ALIGN_FIRST - the first character of the string is placed on
337      *   the <code>position</code> point.
338      * <LI> ALIGN_LAST - the last character of the string is placed on the
339      *   <code>position</code> point.
340      * </UL>
341      * The default value of this field is <code>ALIGN_FIRST</code>.
342      *
343      * @return the current alingment policy for this node.
344      *
345      * @exception CapabilityNotSetException if appropriate capability is
346      * not set and this object is part of live or compiled scene graph
347      *
348      * @see #getPosition
349      */
setAlignment(int alignment)350     final void setAlignment(int alignment) {
351 	geomLock.getLock();
352         this.alignment = alignment;
353         updateTransformData();
354 	geomLock.unLock();
355         sendTransformChangedMessage();
356     }
357 
358     /**
359      * Retrieves the node's <code>path</code> field.  This field
360      * is used  to specify how succeeding
361      * glyphs in the string are placed in relation to the previous glyph.
362      * Valid values for this field are:
363      * <UL>
364      * <LI> PATH_LEFT: - succeeding glyphs are placed to the left of the
365      *  current glyph.
366      * <LI> PATH_RIGHT: - succeeding glyphs are placed to the right of the
367      *  current glyph.
368      * <LI> PATH_UP: - succeeding glyphs are placed above the current glyph.
369      * <LI> PATH_DOWN: - succeeding glyphs are placed below the current glyph.
370      * </UL>
371      * The default value of this field is <code>PATH_RIGHT</code>.
372      *
373      * @return the current alingment policy for this node.
374      *
375      * @exception CapabilityNotSetException if appropriate capability is
376      * not set and this object is part of live or compiled scene graph
377      */
getPath()378     final int getPath() {
379         return this.path;
380     }
381 
382     /**
383      * Sets the node's <code>path</code> field.  This field
384      * is used  to specify how succeeding
385      * glyphs in the string are placed in relation to the previous glyph.
386      * Valid values for this field are:
387      * <UL>
388      * <LI> PATH_LEFT - succeeding glyphs are placed to the left of the
389      *  current glyph.
390      * <LI> PATH_RIGHT - succeeding glyphs are placed to the right of the
391      *  current glyph.
392      * <LI> PATH_UP - succeeding glyphs are placed above the current glyph.
393      * <LI> PATH_DOWN - succeeding glyphs are placed below the current glyph.
394      * </UL>
395      * The default value of this field is <code>PATH_RIGHT</code>.
396      *
397      * @param path the value to set the path to.
398      *
399      * @return the current alingment policy for this node.
400      *
401      * @exception CapabilityNotSetException if appropriate capability is
402      * not set and this object is part of live or compiled scene graph
403      */
setPath(int path)404     final void setPath(int path) {
405         this.path = path;
406         updateTransformData();
407         sendTransformChangedMessage();
408     }
409 
410     /**
411      * Retrieves the 3D bounding box that encloses this Text3D object.
412      *
413      * @param bounds the object to copy the bounding information to.
414      *
415      * @exception CapabilityNotSetException if appropriate capability is
416      * not set and this object is part of live or compiled scene graph
417      *
418      * @see BoundingBox
419      */
getBoundingBox(BoundingBox bounds)420     final void getBoundingBox(BoundingBox bounds) {
421 	synchronized (this) {
422             bounds.setLower(lower);
423             bounds.setUpper(upper);
424 	}
425     }
426 
427     /**
428      * Retrieves the character spacing used to construct the Text3D string.
429      * This spacing is in addition to the regular spacing between glyphs as
430      * defined in the Font object.  1.0 in this space is measured as the
431      * width of the largest glyph in the 2D Font.  The default value is
432      * 0.0.
433      *
434      * @return the current character spacing value
435      *
436      * @exception CapabilityNotSetException if appropriate capability is
437      * not set and this object is part of live or compiled scene graph
438      */
getCharacterSpacing()439     final float getCharacterSpacing() {
440 	return charSpacing;
441     }
442 
443     /**
444      * Sets the character spacing used hwne constructing the Text3D string.
445      * This spacing is in addition to the regular spacing between glyphs as
446      * defined in the Font object.  1.0 in this space is measured as the
447      * width of the largest glyph in the 2D Font.  The default value is
448      * 0.0.
449      *
450      * @param characterSpacing the new character spacing value
451      *
452      * @exception CapabilityNotSetException if appropriate capability is
453      * not set and this object is part of live or compiled scene graph
454      */
setCharacterSpacing(float characterSpacing)455     final void setCharacterSpacing(float characterSpacing) {
456 	geomLock.getLock();
457 	this.charSpacing = characterSpacing;
458         updateTransformData();
459 	geomLock.unLock();
460         sendTransformChangedMessage();
461     }
462 
463 
sendDataChangedMessage()464     final void sendDataChangedMessage() {
465 	J3dMessage[] m;
466 	int i, j, k, kk, numMessages;
467 	int gSize;
468 	ArrayList shapeList, gaList;
469 	Shape3DRetained s;
470 	GeometryAtom[] newGeometryAtoms;
471 	ArrayList tiArrList = new ArrayList();
472 	ArrayList newCtArrArrList = new ArrayList();
473 
474 	synchronized(liveStateLock) {
475 	    if (source.isLive()) {
476 		synchronized (universeList) {
477 		    numMessages = universeList.size();
478 		    m = new J3dMessage[numMessages];
479 		    for (i=0; i<numMessages; i++) {
480 			m[i] = new J3dMessage();
481 			m[i].type = J3dMessage.TEXT3D_DATA_CHANGED;
482 			m[i].threads = targetThreads;
483 			shapeList = (ArrayList)userLists.get(i);
484 			newGeometryAtomList.clear();
485 			oldGeometryAtomList.clear();
486 
487 			for (j=0; j<shapeList.size(); j++) {
488 			    s = (Shape3DRetained)shapeList.get(j);
489 			    if (s.boundsAutoCompute) {
490 				// update combine bounds of mirrorShape3Ds. So we need to
491 				// use its bounds and not localBounds.
492 				// bounds is actually a reference to
493 				// mirrorShape3D.source.localBounds.
494 				// XXXX : Should only need to update distinct localBounds.
495 				s.getCombineBounds((BoundingBox)s.bounds);
496 			    }
497 
498 			    gSize = s.geometryList.size();
499 
500 			    GeometryAtom oldGA = Shape3DRetained.getGeomAtom(s);
501 			    GeometryAtom newGA = new GeometryAtom();
502 
503 			    int geometryCnt = 0;
504 			    for(k = 0; k<gSize; k++) {
505 				GeometryRetained geomRetained =
506 				    (GeometryRetained) s.geometryList.get(k);
507 				if(geomRetained != null) {
508 				    Text3DRetained tempT3d = (Text3DRetained)geomRetained;
509 				    geometryCnt += tempT3d.numChars;
510 				}
511 				else {
512 				    // Slightly wasteful, but not quite worth to optimize yet.
513 				    geometryCnt++;
514 				}
515 			    }
516 
517 			    newGA.geometryArray = new GeometryRetained[geometryCnt];
518 			    newGA.lastLocalTransformArray = new Transform3D[geometryCnt];
519 			    // Reset geometryCnt;
520 			    geometryCnt = 0;
521 
522 			    newGA.locale = s.locale;
523 			    newGA.visible = s.visible;
524 			    newGA.source = s;
525 			    int gaCnt=0;
526 			    GeometryRetained geometry = null;
527 			    for(; gaCnt<gSize; gaCnt++) {
528 				geometry = (GeometryRetained) s.geometryList.get(gaCnt);
529 				if(geometry != null) {
530 				    newGA.geoType = geometry.geoType;
531 				    newGA.alphaEditable = s.isAlphaEditable(geometry);
532 				    break;
533 				}
534 			    }
535 
536 			    for(; gaCnt<gSize; gaCnt++) {
537 				geometry = (GeometryRetained) s.geometryList.get(gaCnt);
538 				if(geometry == null) {
539 				    newGA.geometryArray[gaCnt] = null;
540 				}
541 				else {
542 				    Text3DRetained t = (Text3DRetained)geometry;
543 				    GeometryRetained geo;
544 				    for (k=0; k<t.numChars; k++, geometryCnt++) {
545 					geo = t.geometryList[k];
546 					if (geo != null) {
547 					    newGA.geometryArray[geometryCnt] = geo;
548 					    newGA.lastLocalTransformArray[geometryCnt] =
549 						t.charTransforms[k];
550 
551 					} else {
552 					    newGA.geometryArray[geometryCnt] = null;
553 					    newGA.lastLocalTransformArray[geometryCnt] = null;
554 					}
555 
556 				    }
557 
558 				}
559 			    }
560 
561 			    oldGeometryAtomList.add(oldGA);
562 			    newGeometryAtomList.add(newGA);
563 			    Shape3DRetained.setGeomAtom(s, newGA);
564 			}
565 
566 			Object[] oldGAArray = oldGeometryAtomList.toArray();
567 			Object[] newGAArray = newGeometryAtomList.toArray();
568 			ArrayList uniqueList = getUniqueSource(shapeList);
569 			int numSrc = uniqueList.size();
570 			int numMS3D;
571 			Shape3DRetained ms, src;
572 
573             		for (j=0; j<numSrc; j++) {
574             		    CachedTargets[] newCtArr = null;
575 			    src = (Shape3DRetained)uniqueList.get(j);
576 			    numMS3D = src.mirrorShape3D.size();
577 
578         		    TargetsInterface ti = ((GroupRetained)src.
579 				parent).getClosestTargetsInterface(
580                                         TargetsInterface.TRANSFORM_TARGETS);
581 
582         		    if (ti != null) {
583             		        CachedTargets ct;
584             		        newCtArr = new CachedTargets[numMS3D];
585 
586 				for (k=0; k<numMS3D; k++) {
587 				    ms = (Shape3DRetained)src.mirrorShape3D.get(k);
588 
589 				    GeometryAtom ga =
590 					Shape3DRetained.getGeomAtom(ms);
591 				    for(kk=0; kk<newGAArray.length; kk++) {
592 					if(ga == newGAArray[kk]) {
593 					    break;
594 					}
595 				    }
596 
597 				    if(kk==newGAArray.length) {
598 					System.err.println("Text3DRetained : Problem !!! Can't find matching geomAtom");
599 				    }
600 
601 				    ct = ti.getCachedTargets(TargetsInterface.
602 							     TRANSFORM_TARGETS, k, -1);
603 				    if (ct != null) {
604 					newCtArr[k] = new CachedTargets();
605 					newCtArr[k].copy(ct);
606 					newCtArr[k].replace((NnuId)oldGAArray[kk],
607 							    (NnuId)newGAArray[kk],
608 							    Targets.GEO_TARGETS);
609 				    } else {
610 					newCtArr[k] = null;
611 				    }
612 
613 				}
614 
615             			ti.resetCachedTargets(
616 				  TargetsInterface.TRANSFORM_TARGETS, newCtArr, -1);
617 
618 				tiArrList.add(ti);
619 				newCtArrArrList.add(newCtArr);
620 
621 			    }
622 
623 			}
624 
625 			m[i].args[0] = oldGAArray;
626 			m[i].args[1] = newGAArray;
627 			m[i].universe = (VirtualUniverse)universeList.get(i);
628 
629 			if(tiArrList.size() > 0) {
630 			    m[i].args[2] = tiArrList.toArray();
631 			    m[i].args[3] = newCtArrArrList.toArray();
632 			}
633 
634 			tiArrList.clear();
635 			newCtArrArrList.clear();
636 
637 		    }
638 		    VirtualUniverse.mc.processMessage(m);
639 		}
640 
641 	    }
642 	}
643     }
644 
645 
sendTransformChangedMessage()646     final void sendTransformChangedMessage() {
647 	J3dMessage[] m;
648 	int i, j, numMessages, sCnt;
649 	ArrayList shapeList;
650 	ArrayList gaList = new ArrayList();
651 	Shape3DRetained s;
652 	GeometryRetained geomR;
653 	synchronized(liveStateLock) {
654 	    if (source.isLive()) {
655 		synchronized (universeList) {
656 		    numMessages = universeList.size();
657 		    m = new J3dMessage[numMessages];
658 		    for (i=0; i<numMessages; i++) {
659 			m[i] = new J3dMessage();
660 			m[i].type = J3dMessage.TEXT3D_TRANSFORM_CHANGED;
661 			m[i].threads = targetThreads;
662 			shapeList = (ArrayList)userLists.get(i);
663 			// gaList = new GeometryAtom[shapeList.size() * numChars];
664 			for (j=0; j<shapeList.size(); j++) {
665 			    s = (Shape3DRetained)shapeList.get(j);
666 
667 			    // Find the right geometry.
668 			    for(sCnt=0; sCnt<s.geometryList.size(); sCnt++) {
669 				geomR = (GeometryRetained) s.geometryList.get(sCnt);
670 				if(geomR == this) {
671 				    break;
672 				}
673 			    }
674 
675 			    if(sCnt < s.geometryList.size())
676 				gaList.add(Shape3DRetained.getGeomAtom(s));
677 
678 			}
679 			m[i].args[0] = gaList.toArray();
680 			m[i].args[1] = charTransforms;
681 			m[i].universe = (VirtualUniverse)universeList.get(i);
682 		    }
683 		    VirtualUniverse.mc.processMessage(m);
684 		}
685 	    }
686 	}
687     }
688 
689     /**
690      * Update internal reprsentation of tranform matrices and geometry.
691      * This method will be called whenever string or font3D change.
692      */
updateCharacterData()693     final void updateCharacterData() {
694 	char c[] = new char[1];
695 
696 	if (geometryList.length != numChars) {
697 	    geometryList = new GeometryArrayRetained[numChars];
698 	    glyphVecs = new GlyphVector[numChars];
699 	}
700 
701 	if (font3D != null) {
702             for (int i=0; i<numChars; i++) {
703 		c[0] = string.charAt(i);
704 		glyphVecs[i] = font3D.font.createGlyphVector(font3D.frc, c);
705 	        geometryList[i] = font3D.triangulateGlyphs(glyphVecs[i], c[0]);
706             }
707 	}
708 
709         updateTransformData();
710     }
711 
712     /**
713      * Update per character transform based on Text3D location,
714      * per character size and path.
715      *
716      * WARNING: Caller of this method must make sure SceneGraph is live,
717      * else exceptions may be thrown.
718      */
updateTransformData()719     final void updateTransformData(){
720         int i, k=0, numTotal=0;
721         double width = 0, height = 0;
722         Vector3f location = new Vector3f(this.position);
723         Rectangle2D bounds;
724 
725         //Reset bounds data
726         lower.set(location);
727         upper.set(location);
728 
729 	charTransforms = new Transform3D[numChars];
730 	for (i=0; i<numChars; i++) {
731 	    charTransforms[i] = new Transform3D();
732 	}
733 
734 	if (numChars != 0) {
735 	    charTransforms[0].set(location);
736 
737 	    // Set loop counters based on path type
738 	    if (path == Text3D.PATH_RIGHT || path == Text3D.PATH_UP) {
739 		k = 0;
740 		numTotal = numChars + 1;
741 	    } else if (path == Text3D.PATH_LEFT || path == Text3D.PATH_DOWN) {
742 		k = 1;
743 		numTotal = numChars;
744 		// Reset bounds to bounding box if first character
745 		bounds = glyphVecs[0].getVisualBounds();
746 		upper.x += bounds.getWidth();
747 		upper.y += bounds.getHeight();
748 	    }
749 
750 	    for (i=1; i<numTotal; i++, k++) {
751 		width = glyphVecs[k].getLogicalBounds().getWidth();
752 		bounds = glyphVecs[k].getVisualBounds();
753 		// 'switch' could be outside loop with little hacking,
754 		width += charSpacing;
755 		height = bounds.getHeight();
756 
757 		switch (this.path) {
758 		case Text3D.PATH_RIGHT:
759 		    location.x += width;
760 		    upper.x    += (width);
761 		    if (upper.y < (height + location.y)) {
762 			upper.y = location.y + height;
763 		    }
764 		    break;
765 		case Text3D.PATH_LEFT:
766 		    location.x -= width;
767 		    lower.x    -= (width);
768 		    if (upper.y < ( height + location.y)) {
769 			upper.y = location.y + height;
770 		    }
771 		    break;
772 		case Text3D.PATH_UP:
773 		    location.y += height;
774 		    upper.y    += height;
775 		    if (upper.x < (bounds.getWidth() + location.x)) {
776 			upper.x = location.x + bounds.getWidth();
777 		    }
778 		    break;
779 		case Text3D.PATH_DOWN:
780 		    location.y -= height;
781 		    lower.y    -= height;
782 		    if (upper.x < (bounds.getWidth() + location.x)) {
783 			upper.x = location.x + bounds.getWidth();
784 		    }
785 		    break;
786 		}
787 		if (i < numChars) {
788 		    charTransforms[i].set(location);
789 		}
790 	    }
791 
792 	    // Handle string alignment. ALIGN_FIRST is handled by default
793 	    if (alignment != Text3D.ALIGN_FIRST) {
794 		double cx = (upper.x - lower.x);
795 		double cy = (upper.y - lower.y);
796 
797 		if (alignment == Text3D.ALIGN_CENTER) {
798 		    cx *= .5;
799 		    cy *= .5;
800 		}
801 		switch (path) {
802 		case Text3D.PATH_RIGHT:
803 		    for (i=0;i < numChars;i++) {
804 			charTransforms[i].mat[3] -= cx;
805 		    }
806 		    lower.x -= cx;
807 		    upper.x -= cx;
808 		    break;
809 		case Text3D.PATH_LEFT:
810 		    for (i=0;i < numChars;i++) {
811 			charTransforms[i].mat[3] += cx;
812 		    }
813 		    lower.x += cx;
814 		    upper.x += cx;
815 		    break;
816 
817 		case Text3D.PATH_UP:
818 		    for (i=0;i < numChars;i++) {
819 			charTransforms[i].mat[7] -=cy;
820 		    }
821 		    lower.y -= cy;
822 		    upper.y -= cy;
823 		    break;
824 		case Text3D.PATH_DOWN:
825 		    for (i=0;i < numChars;i++) {
826 			charTransforms[i].mat[7] +=cy;
827 		    }
828 		    lower.y += cy;
829 		    upper.y += cy;
830 		    break;
831 
832 		}
833 	    }
834 	}
835 
836         lower.z = 0.0f;
837         if ((font3D == null) || (font3D.fontExtrusion == null)) {
838 	    upper.z = lower.z;
839         } else {
840 	    upper.z = lower.z + font3D.fontExtrusion.length;
841 	}
842 
843         // update geoBounds
844         getBoundingBox(geoBounds);
845     }
846 
847 
848     /**
849      * This method is called when the SceneGraph becomes live. All characters
850      * used by this.string are tesselated in this method, to avoid wait during
851      * traversal and rendering.
852      */
setLive(boolean inBackgroundGroup, int refCount)853     void setLive(boolean inBackgroundGroup, int refCount) {
854       // Tesselate all character data and update character transforms
855       updateCharacterData();
856       super.doSetLive(inBackgroundGroup, refCount);
857       super.markAsLive();
858     }
859 
860     // TODO -- Need to rethink. Might have to consider charTransform[] in returns pickInfo.
intersect(PickShape pickShape, PickInfo pickInfo, int flags, Point3d iPnt, GeometryRetained geom, int geomIndex)861     boolean intersect(PickShape pickShape, PickInfo pickInfo, int flags, Point3d iPnt,
862                       GeometryRetained geom, int geomIndex) {
863 	Transform3D tempT3D = new Transform3D();
864 	GeometryArrayRetained geo = null;
865 	int sIndex = -1;
866 	PickShape newPS;
867 	double minDist = Double.MAX_VALUE;
868         double distance =0.0;
869         Point3d closestIPnt = new Point3d();
870 
871 	for (int i=0; i < numChars; i++) {
872 	    geo= geometryList[i];
873 	    if (geo != null) {
874 		tempT3D.invert(charTransforms[i]);
875 		newPS = pickShape.transform(tempT3D);
876 		if (geo.intersect(newPS, pickInfo, flags, iPnt, geom,  geomIndex)) {
877 		    if (flags == 0) {
878 			return true;
879 		    }
880                     distance = newPS.distance(iPnt);
881 		    if (distance < minDist) {
882 			sIndex = i;
883 			minDist = distance;
884                         closestIPnt.set(iPnt);
885 		    }
886 		}
887 	    }
888 	}
889 
890 	if (sIndex >= 0) {
891 	    // We need to transform iPnt to the vworld to compute the actual distance.
892 	    // In this method we'll transform iPnt by its char. offset. Shape3D will
893 	    // do the localToVworld transform.
894 	    iPnt.set(closestIPnt);
895 	    charTransforms[sIndex].transform(iPnt);
896 	    return true;
897 	}
898 	return false;
899     }
900 
intersect(Point3d[] pnts)901     boolean intersect(Point3d[] pnts) {
902 	Transform3D tempT3D = new Transform3D();
903 	GeometryArrayRetained ga;
904 	boolean isIntersect = false;
905 	Point3d transPnts[] = new Point3d[pnts.length];
906 	for (int j=pnts.length-1; j >= 0; j--) {
907 	    transPnts[j] = new Point3d();
908 	}
909 
910 	for (int i=numChars-1; i >= 0;  i--) {
911 	    ga = geometryList[i];
912 	    if ( ga != null) {
913 		tempT3D.invert(charTransforms[i]);
914 		for (int j=pnts.length-1; j >= 0; j--) {
915 		    tempT3D.transform(pnts[j], transPnts[j]);
916 		}
917 		if (ga.intersect(transPnts)) {
918 		    isIntersect = true;
919 		    break;
920 		}
921 	    }
922 	}
923 	return isIntersect;
924     }
925 
926 
intersect(Transform3D thisToOtherVworld, GeometryRetained geom)927     boolean intersect(Transform3D thisToOtherVworld, GeometryRetained geom) {
928 	GeometryArrayRetained ga;
929 
930 	for (int i=numChars-1; i >=0; i--) {
931 	    ga = geometryList[i];
932 	    if ((ga != null) && ga.intersect(thisToOtherVworld, geom)) {
933 		return true;
934 	    }
935 	}
936 
937 	return false;
938     }
939 
intersect(Bounds targetBound)940     boolean intersect(Bounds targetBound) {
941 	GeometryArrayRetained ga;
942 
943 	for (int i=numChars-1; i >=0; i--) {
944 	    ga = geometryList[i];
945 	    if ((ga != null) && ga.intersect(targetBound)) {
946 		return true;
947 	    }
948 	}
949 
950 	return false;
951 
952     }
953 
setModelViewMatrix(Transform3D vpcToEc, Transform3D drawTransform)954     void setModelViewMatrix(Transform3D vpcToEc, Transform3D drawTransform) {
955 	this.vpcToEc = vpcToEc;
956 	this.drawTransform = drawTransform;
957     }
958 
959 
execute(Canvas3D cv, RenderAtom ra, boolean isNonUniformScale, boolean updateAlpha, float alpha, int screen, boolean ignoreVertexColors)960     void execute(Canvas3D cv, RenderAtom ra, boolean isNonUniformScale,
961 		 boolean updateAlpha, float alpha,
962 		 int screen,
963 		 boolean ignoreVertexColors) {
964 
965 	Transform3D trans = new Transform3D();
966 
967 	for (int i = 0; i < geometryList.length; i++) {
968 	    trans.set(drawTransform);
969 	    trans.mul(charTransforms[i]);
970 	    cv.setModelViewMatrix(cv.ctx, vpcToEc.mat, trans);
971 	    geometryList[i].execute(cv, ra, isNonUniformScale, updateAlpha, alpha,
972 				    screen, ignoreVertexColors);
973 	}
974     }
975 
getClassType()976     int getClassType() {
977 	return TEXT3D_TYPE;
978     }
979 
980 
getUniqueSource(ArrayList shapeList)981     ArrayList getUniqueSource(ArrayList shapeList) {
982 	ArrayList uniqueList = new ArrayList();
983 	int size = shapeList.size();
984 	Object src;
985 	int i, index;
986 
987 	for (i=0; i<size; i++) {
988 	    src = ((Shape3DRetained)shapeList.get(i)).sourceNode;
989             index = uniqueList.indexOf(src);
990             if (index == -1) {
991                 uniqueList.add(src);
992             }
993         }
994 	return uniqueList;
995     }
996 }
997 
998