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