1 /* 2 * $RCSfile: Font3D.java,v $ 3 * 4 * Copyright 1997-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.6 $ 28 * $Date: 2008/02/28 20:17:21 $ 29 * $State: Exp $ 30 */ 31 32 package javax.media.j3d; 33 34 import com.sun.j3d.utils.geometry.*; 35 import com.sun.j3d.internal.FastVector; 36 import java.awt.Font; 37 import java.awt.font.*; 38 import java.awt.geom.Rectangle2D; 39 import java.awt.geom.AffineTransform; 40 import javax.vecmath.*; 41 import java.awt.Shape; 42 import java.awt.geom.PathIterator; 43 import java.util.*; 44 45 /** 46 * The Font3D object is used to store extruded 2D glyphs. These 47 * 3D glyphs can then be used to construct Text3D NodeComponent 48 * objects. 49 * <P> 50 * A 3D Font consists of a Java 2D font, a tesellation tolerance, 51 * and an extrusion path. The extrusion 52 * path creates depth by describing how the edge of a glyph varies 53 * in the Z axis. 54 * <P> 55 * The construction of a Text3D object requires a Font3D object. 56 * The Font3D object describes the style of the text, such as its 57 * depth. The text also needs other classes, such as java.awt.Font and 58 * FontExtrusion. The Font object describes the font name (Helvetica, 59 * Courier, etc.), the font style (bold, Italic, etc.), and point 60 * size. The FontExtrusion object extends Font3D by describing 61 * the extrusion path for the Font3D object (how the edge of the 62 * font glyph varies in the Z axis). 63 *<P> 64 * To ensure correct rendering, the 2D Font object should be created 65 * with the default AffineTransform. The point size of the 2D font will 66 * be used as a rough measure of how fine a tesselation to use when 67 * creating the Font3D object: the larger the point size, in 68 * general, the finer the tesselation. 69 * <P> 70 * Custom 3D fonts as well as methods to store 3D fonts 71 * to disk will be addressed in a future release. 72 * 73 * @see java.awt.Font 74 * @see FontExtrusion 75 * @see Text3D 76 */ 77 public class Font3D extends NodeComponent { 78 79 Font font; 80 double tessellationTolerance; 81 FontExtrusion fontExtrusion; 82 FontRenderContext frc; 83 // Used by triangulateGlyphs method to split contour data into islands. 84 final static float EPS = 0.000001f; 85 86 // Map glyph code to GeometryArrayRetained 87 Hashtable geomHash = new Hashtable(20); 88 89 /** 90 * Constructs a Font3D object from the specified Font and 91 * FontExtrusion objects, using the default value for the 92 * tessellation tolerance. The default value is as follows: 93 * 94 * <ul> 95 * tessellation tolerance : 0.01<br> 96 * </ul> 97 * <P> 98 * The FontExtrusion object contains the extrusion path to use on 99 * the 2D Font glyphs. To ensure correct rendering the font must 100 * be created with the default AffineTransform. Passing null for 101 * the FontExtrusion parameter results in no extrusion being done. 102 * 103 * @param font the Java 2D font used to create the 3D font object 104 * @param extrudePath the extrusion path used to describe how 105 * the edge of the font varies along the Z axis 106 */ Font3D(Font font, FontExtrusion extrudePath)107 public Font3D(Font font, FontExtrusion extrudePath) { 108 this(font, 0.01, extrudePath); 109 } 110 111 /** 112 * Constructs a Font3D object from the specified Font and 113 * FontExtrusion objects, using the specified tessellation 114 * tolerance. 115 * The FontExtrusion object contains the extrusion path to use on 116 * the 2D Font glyphs. To ensure correct rendering, the font must 117 * be created with the default AffineTransform. Passing null for 118 * the FontExtrusion parameter results in no extrusion being done. 119 * 120 * @param font the Java 2D font used to create the 3D font object. 121 * @param tessellationTolerance the tessellation tolerance value 122 * used in tessellating the glyphs of the 2D Font. 123 * This corresponds to the <code>flatness</code> parameter in 124 * the <code>java.awt.Shape.getPathIterator</code> method. 125 * @param extrudePath the extrusion path used to describe how 126 * the edge of the font varies along the Z axis. 127 * 128 * @since Java 3D 1.2 129 */ Font3D(Font font, double tessellationTolerance, FontExtrusion extrudePath)130 public Font3D(Font font, 131 double tessellationTolerance, 132 FontExtrusion extrudePath) { 133 134 this.font = font; 135 this.tessellationTolerance = tessellationTolerance; 136 this.fontExtrusion = extrudePath; 137 this.frc = new FontRenderContext(new AffineTransform(), 138 true, true); 139 } 140 141 /** 142 * Returns the Java 2D Font used to create this Font3D object. 143 * @return Font object used by this Font3D 144 */ getFont()145 public Font getFont() { 146 return this.font; 147 } 148 149 150 /** 151 * Returns the tessellation tolerance with which this Font3D was 152 * created. 153 * @return the tessellation tolerance used by this Font3D 154 * 155 * @since Java 3D 1.2 156 */ getTessellationTolerance()157 public double getTessellationTolerance() { 158 return tessellationTolerance; 159 } 160 161 162 /** 163 * Copies the FontExtrusion object used to create this Font3D object 164 * into the specified parameter. 165 * 166 * @param extrudePath object that will receive the 167 * FontExtrusion information for this Font3D object 168 */ getFontExtrusion(FontExtrusion extrudePath)169 public void getFontExtrusion(FontExtrusion extrudePath) { 170 extrudePath = this.fontExtrusion; 171 } 172 173 /** 174 * Returns the 3D bounding box of the specified glyph code. 175 * 176 * @param glyphCode the glyphCode from the original 2D Font 177 * @param bounds the 3D glyph's bounds 178 */ getBoundingBox(int glyphCode, BoundingBox bounds)179 public void getBoundingBox(int glyphCode, BoundingBox bounds){ 180 int[] gCodes = {glyphCode}; 181 GlyphVector gVec = font.createGlyphVector(frc, gCodes); 182 Rectangle2D.Float bounds2d = (Rectangle2D.Float) 183 (((GlyphMetrics)(gVec.getGlyphMetrics(0))).getBounds2D()); 184 185 Point3d lower = new Point3d(bounds2d.x, bounds2d.y, 0.0); 186 Point3d upper; 187 if (fontExtrusion != null) { 188 upper = new Point3d(bounds2d.x + bounds2d.width, 189 bounds2d.y + bounds2d.height, 190 fontExtrusion.length); 191 } else { 192 upper = new Point3d(bounds2d.x + bounds2d.width, 193 bounds2d.y + bounds2d.height, 194 0.0); 195 } 196 bounds.setLower(lower); 197 bounds.setUpper(upper); 198 } 199 200 // BY MIK OF CLASSX 201 /** 202 * Returns a GeometryArray of a glyph in this Font3D. 203 * 204 * @param c character from which to generate a tessellated glyph. 205 * 206 * @return a GeometryArray 207 * 208 * @since Java 3D 1.4 209 */ getGlyphGeometry(char c)210 public GeometryArray getGlyphGeometry(char c) { 211 char code[] = { c }; 212 GlyphVector gv = font.createGlyphVector(frc, code); 213 214 // triangulate the glyph 215 GeometryArrayRetained glyph_gar = triangulateGlyphs(gv, code[0]); 216 217 // Assume that triangulateGlyphs returns a triangle array with only coords & normals 218 // (and without by-ref, interleaved, etc.) 219 assert glyph_gar instanceof TriangleArrayRetained : 220 "Font3D: GeometryArray is not an instance of TrangleArray"; 221 assert glyph_gar.getVertexFormat() == (GeometryArray.COORDINATES | GeometryArray.NORMALS) : 222 "Font3D: Illegal GeometryArray format -- only coordinates and normals expected"; 223 224 // create a correctly sized TriangleArray 225 TriangleArray ga = new TriangleArray(glyph_gar.getVertexCount(),glyph_gar.getVertexFormat()); 226 227 // temp storage for coords, normals 228 float tmp[] = new float[3]; 229 230 int vertexCount = ga.getVertexCount(); 231 for(int i=0; i<vertexCount; i++) { 232 // copy the glyph geometry to the TriangleArray 233 glyph_gar.getCoordinate(i,tmp); 234 ga.setCoordinate(i,tmp); 235 236 glyph_gar.getNormal(i,tmp); 237 ga.setNormal(i,tmp); 238 } 239 240 return ga; 241 } 242 243 244 // Triangulate glyph with 'unicode' if not already done. triangulateGlyphs(GlyphVector gv, char c)245 GeometryArrayRetained triangulateGlyphs(GlyphVector gv, char c) { 246 Character ch = new Character(c); 247 GeometryArrayRetained geo = (GeometryArrayRetained) geomHash.get(ch); 248 249 if (geo == null) { 250 // Font Y-axis is downwards, so send affine transform to flip it. 251 Rectangle2D bnd = gv.getVisualBounds(); 252 AffineTransform aTran = new AffineTransform(); 253 double tx = bnd.getX() + 0.5 * bnd.getWidth(); 254 double ty = bnd.getY() + 0.5 * bnd.getHeight(); 255 aTran.setToTranslation(-tx, -ty); 256 aTran.scale(1.0, -1.0); 257 aTran.translate(tx, -ty); 258 Shape shape = gv.getOutline(); 259 PathIterator pIt = shape.getPathIterator(aTran, tessellationTolerance); 260 int flag= -1, numContours = 0, numPoints = 0, i, j, k, num=0, vertCnt; 261 UnorderList coords = new UnorderList(100, Point3f.class); 262 float tmpCoords[] = new float[6]; 263 float lastX= .0f, lastY= .0f; 264 float firstPntx = Float.MAX_VALUE, firstPnty = Float.MAX_VALUE; 265 GeometryInfo gi = null; 266 NormalGenerator ng = new NormalGenerator(); 267 FastVector contours = new FastVector(10); 268 float maxY = -Float.MAX_VALUE; 269 int maxYIndex = 0, beginIdx = 0, endIdx = 0, start = 0; 270 271 boolean setMaxY = false; 272 273 274 while (!pIt.isDone()) { 275 Point3f vertex = new Point3f(); 276 flag = pIt.currentSegment(tmpCoords); 277 if (flag == PathIterator.SEG_CLOSE){ 278 if (num > 0) { 279 if (setMaxY) { 280 // Get Previous point 281 beginIdx = start; 282 endIdx = numPoints-1; 283 } 284 contours.addElement(num); 285 num = 0; 286 numContours++; 287 } 288 } else if (flag == PathIterator.SEG_MOVETO){ 289 vertex.x = tmpCoords[0]; 290 vertex.y = tmpCoords[1]; 291 lastX = vertex.x; 292 lastY = vertex.y; 293 294 if ((lastX == firstPntx) && (lastY == firstPnty)) { 295 pIt.next(); 296 continue; 297 } 298 setMaxY = false; 299 coords.add(vertex); 300 firstPntx = lastX; 301 firstPnty = lastY; 302 if (num> 0){ 303 contours.addElement(num); 304 num = 0; 305 numContours++; 306 } 307 num++; 308 numPoints++; 309 // skip checking of first point, 310 // since the last point will repeat this. 311 start = numPoints ; 312 } else if (flag == PathIterator.SEG_LINETO){ 313 vertex.x = tmpCoords[0]; 314 vertex.y = tmpCoords[1]; 315 //Check here for duplicate points. Code 316 //later in this function can not handle 317 //duplicate points. 318 319 if ((vertex.x == lastX) && (vertex.y == lastY)) { 320 pIt.next(); 321 continue; 322 } 323 if (vertex.y > maxY) { 324 maxY = vertex.y; 325 maxYIndex = numPoints; 326 setMaxY = true; 327 } 328 lastX = vertex.x; 329 lastY = vertex.y; 330 coords.add(vertex); 331 num++; 332 numPoints++; 333 } 334 pIt.next(); 335 } 336 337 // No data(e.g space, control characters) 338 // Two point can't form a valid contour 339 if (numPoints == 0){ 340 return null; 341 } 342 343 344 345 // Determine font winding order use for side triangles 346 Point3f p1 = new Point3f(), p2 = new Point3f(), p3 = new Point3f(); 347 boolean flip_side_orient = true; 348 Point3f vertices[] = (Point3f []) coords.toArray(false); 349 350 if (endIdx - beginIdx > 0) { 351 // must be true unless it is a single line 352 // define as "MoveTo p1 LineTo p2 Close" which is 353 // not a valid font definition. 354 355 if (maxYIndex == beginIdx) { 356 p1.set(vertices[endIdx]); 357 } else { 358 p1.set(vertices[maxYIndex-1]); 359 } 360 p2.set(vertices[maxYIndex]); 361 if (maxYIndex == endIdx) { 362 p3.set(vertices[beginIdx]); 363 } else { 364 p3.set(vertices[maxYIndex+1]); 365 } 366 367 if (p3.x != p2.x) { 368 if (p1.x != p2.x) { 369 // Use the one with smallest slope 370 if (Math.abs((p2.y - p1.y)/(p2.x - p1.x)) > 371 Math.abs((p3.y - p2.y)/(p3.x - p2.x))) { 372 flip_side_orient = (p3.x > p2.x); 373 } else { 374 flip_side_orient = (p2.x > p1.x); 375 } 376 } else { 377 flip_side_orient = (p3.x > p2.x); 378 } 379 } else { 380 // p1.x != p2.x, otherwise all three 381 // point form a straight vertical line with 382 // the middle point the highest. This is not a 383 // valid font definition. 384 flip_side_orient = (p2.x > p1.x); 385 } 386 } 387 388 // Build a Tree of Islands 389 int startIdx = 0; 390 IslandsNode islandsTree = new IslandsNode(-1, -1); 391 int contourCounts[] = contours.getData(); 392 393 394 for (i= 0;i < contours.getSize(); i++) { 395 endIdx = startIdx + contourCounts[i]; 396 islandsTree.insert(new IslandsNode(startIdx, endIdx), vertices); 397 startIdx = endIdx; 398 } 399 400 coords = null; // Free memory 401 contours = null; 402 contourCounts = null; 403 404 // Compute islandCounts[][] and outVerts[][] 405 UnorderList islandsList = new UnorderList(10, IslandsNode.class); 406 islandsTree.collectOddLevelNode(islandsList, 0); 407 IslandsNode nodes[] = (IslandsNode []) islandsList.toArray(false); 408 int islandCounts[][] = new int[islandsList.arraySize()][]; 409 Point3f outVerts[][] = new Point3f[islandCounts.length][]; 410 int nchild, sum; 411 IslandsNode node; 412 413 for (i=0; i < islandCounts.length; i++) { 414 node = nodes[i]; 415 nchild = node.numChild(); 416 islandCounts[i] = new int[nchild + 1]; 417 islandCounts[i][0] = node.numVertices(); 418 sum = 0; 419 sum += islandCounts[i][0]; 420 for (j=0; j < nchild; j++) { 421 islandCounts[i][j+1] = node.getChild(j).numVertices(); 422 sum += islandCounts[i][j+1]; 423 } 424 outVerts[i] = new Point3f[sum]; 425 startIdx = 0; 426 for (k=node.startIdx; k < node.endIdx; k++) { 427 outVerts[i][startIdx++] = vertices[k]; 428 } 429 430 for (j=0; j < nchild; j++) { 431 endIdx = node.getChild(j).endIdx; 432 for (k=node.getChild(j).startIdx; k < endIdx; k++) { 433 outVerts[i][startIdx++] = vertices[k]; 434 } 435 } 436 } 437 438 439 440 islandsTree = null; // Free memory 441 islandsList = null; 442 vertices = null; 443 444 contourCounts = new int[1]; 445 int currCoordIndex = 0, vertOffset = 0; 446 ArrayList triangData = new ArrayList(); 447 448 Point3f q1 = new Point3f(), q2 = new Point3f(), q3 = new Point3f(); 449 Vector3f n1 = new Vector3f(), n2 = new Vector3f(); 450 numPoints = 0; 451 //Now loop thru each island, calling triangulator once per island. 452 //Combine triangle data for all islands together in one object. 453 for (i=0;i < islandCounts.length;i++) { 454 contourCounts[0] = islandCounts[i].length; 455 numPoints += outVerts[i].length; 456 gi = new GeometryInfo(GeometryInfo.POLYGON_ARRAY); 457 gi.setCoordinates(outVerts[i]); 458 gi.setStripCounts(islandCounts[i]); 459 gi.setContourCounts(contourCounts); 460 ng.generateNormals(gi); 461 462 GeometryArray ga = gi.getGeometryArray(false, false, false); 463 vertOffset += ga.getVertexCount(); 464 465 triangData.add(ga); 466 } 467 // Multiply by 2 since we create 2 faces of the font 468 // Second term is for side-faces along depth of the font 469 if (fontExtrusion == null) 470 vertCnt = vertOffset; 471 else{ 472 if (fontExtrusion.shape == null) 473 vertCnt = vertOffset * 2 + numPoints *6; 474 else{ 475 vertCnt = vertOffset * 2 + numPoints * 6 * 476 (fontExtrusion.pnts.length -1); 477 } 478 } 479 480 // XXXX: Should use IndexedTriangleArray to avoid 481 // duplication of vertices. To create triangles for 482 // side faces, every vertex is duplicated currently. 483 TriangleArray triAry = new TriangleArray(vertCnt, 484 GeometryArray.COORDINATES | 485 GeometryArray.NORMALS); 486 487 boolean flip_orient[] = new boolean[islandCounts.length]; 488 boolean findOrient; 489 // last known non-degenerate normal 490 Vector3f goodNormal = new Vector3f(); 491 492 493 for (j=0;j < islandCounts.length;j++) { 494 GeometryArray ga = (GeometryArray)triangData.get(j); 495 vertOffset = ga.getVertexCount(); 496 497 findOrient = false; 498 499 //Create the triangle array 500 for (i= 0; i < vertOffset; i+= 3, currCoordIndex += 3){ 501 //Get 3 points. Since triangle is known to be flat, normal 502 // must be same for all 3 points. 503 ga.getCoordinate(i, p1); 504 ga.getNormal(i, n1); 505 ga.getCoordinate(i+1, p2); 506 ga.getCoordinate(i+2, p3); 507 508 if (!findOrient) { 509 //Check here if triangles are wound incorrectly and need 510 //to be flipped. 511 if (!getNormal(p1,p2, p3, n2)) { 512 continue; 513 } 514 515 if (n2.z >= EPS) { 516 flip_orient[j] = false; 517 } else if (n2.z <= -EPS) { 518 flip_orient[j] = true; 519 } else { 520 continue; 521 } 522 findOrient = true; 523 } 524 if (flip_orient[j]){ 525 //New Triangulator preserves contour orientation. If contour 526 //input is wound incorrectly, swap 2nd and 3rd points to 527 //sure all triangles are wound correctly for j3d. 528 q1.x = p2.x; q1.y = p2.y; q1.z = p2.z; 529 p2.x = p3.x; p2.y = p3.y; p2.z = p3.z; 530 p3.x = q1.x; p3.y = q1.y; p3.z = q1.z; 531 n1.x = -n1.x; n1.y = -n1.y; n1.z = -n1.z; 532 } 533 534 535 if (fontExtrusion != null) { 536 n2.x = -n1.x;n2.y = -n1.y;n2.z = -n1.z; 537 538 triAry.setCoordinate(currCoordIndex, p1); 539 triAry.setNormal(currCoordIndex, n2); 540 triAry.setCoordinate(currCoordIndex+1, p3); 541 triAry.setNormal(currCoordIndex+1, n2); 542 triAry.setCoordinate(currCoordIndex+2, p2); 543 triAry.setNormal(currCoordIndex+2, n2); 544 545 q1.x = p1.x; q1.y = p1.y; q1.z = p1.z + fontExtrusion.length; 546 q2.x = p2.x; q2.y = p2.y; q2.z = p2.z + fontExtrusion.length; 547 q3.x = p3.x; q3.y = p3.y; q3.z = p3.z + fontExtrusion.length; 548 549 triAry.setCoordinate(currCoordIndex+vertOffset, q1); 550 triAry.setNormal(currCoordIndex+vertOffset, n1); 551 triAry.setCoordinate(currCoordIndex+1+vertOffset, q2); 552 triAry.setNormal(currCoordIndex+1+vertOffset, n1); 553 triAry.setCoordinate(currCoordIndex+2+vertOffset, q3); 554 triAry.setNormal(currCoordIndex+2+vertOffset, n1); 555 } else { 556 triAry.setCoordinate(currCoordIndex, p1); 557 triAry.setNormal(currCoordIndex, n1); 558 triAry.setCoordinate(currCoordIndex+1, p2); 559 triAry.setNormal(currCoordIndex+1, n1); 560 triAry.setCoordinate(currCoordIndex+2, p3); 561 triAry.setNormal(currCoordIndex+2, n1); 562 } 563 564 } 565 if (fontExtrusion != null) { 566 currCoordIndex += vertOffset; 567 } 568 } 569 570 //Now add side triangles in both cases. 571 572 // Since we duplicated triangles with different Z, make sure 573 // currCoordIndex points to correct location. 574 if (fontExtrusion != null){ 575 if (fontExtrusion.shape == null){ 576 boolean smooth; 577 // we'll put a crease if the angle between the normals is 578 // greater than 44 degrees 579 float threshold = (float) Math.cos(44.0*Math.PI/180.0); 580 float cosine; 581 // need the previous normals to check for smoothing 582 Vector3f pn1 = null, pn2 = null; 583 // need the next normals to check for smoothing 584 Vector3f n3 = new Vector3f(), n4 = new Vector3f(); 585 // store the normals for each point because they are 586 // the same for both triangles 587 Vector3f p1Normal = new Vector3f(); 588 Vector3f p2Normal = new Vector3f(); 589 Vector3f p3Normal = new Vector3f(); 590 Vector3f q1Normal = new Vector3f(); 591 Vector3f q2Normal = new Vector3f(); 592 Vector3f q3Normal = new Vector3f(); 593 594 for (i=0;i < islandCounts.length;i++){ 595 for (j=0, k=0, num =0;j < islandCounts[i].length;j++){ 596 num += islandCounts[i][j]; 597 p1.x = outVerts[i][num - 1].x; 598 p1.y = outVerts[i][num - 1].y; 599 p1.z = 0.0f; 600 q1.x = p1.x; q1.y = p1.y; q1.z = p1.z+fontExtrusion.length; 601 p2.z = 0.0f; 602 q2.z = p2.z+fontExtrusion.length; 603 for (int m=0; m < num;m++) { 604 p2.x = outVerts[i][m].x; 605 p2.y = outVerts[i][m].y; 606 q2.x = p2.x; 607 q2.y = p2.y; 608 if (getNormal(p1, q1, p2, n1)) { 609 610 if (!flip_side_orient) { 611 n1.negate(); 612 } 613 goodNormal.set(n1); 614 break; 615 } 616 } 617 618 for (;k < num;k++){ 619 p2.x = outVerts[i][k].x;p2.y = outVerts[i][k].y;p2.z = 0.0f; 620 q2.x = p2.x; q2.y = p2.y; q2.z = p2.z+fontExtrusion.length; 621 622 if (!getNormal(p1, q1, p2, n1)) { 623 n1.set(goodNormal); 624 } else { 625 if (!flip_side_orient) { 626 n1.negate(); 627 } 628 goodNormal.set(n1); 629 } 630 631 if (!getNormal(p2, q1, q2, n2)) { 632 n2.set(goodNormal); 633 } else { 634 if (!flip_side_orient) { 635 n2.negate(); 636 } 637 goodNormal.set(n2); 638 } 639 // if there is a previous normal, see if we need to smooth 640 // this normal or make a crease 641 642 if (pn1 != null) { 643 cosine = n1.dot(pn2); 644 smooth = cosine > threshold; 645 if (smooth) { 646 p1Normal.x = (pn1.x + pn2.x + n1.x); 647 p1Normal.y = (pn1.y + pn2.y + n1.y); 648 p1Normal.z = (pn1.z + pn2.z + n1.z); 649 normalize(p1Normal); 650 651 q1Normal.x = (pn2.x + n1.x + n2.x); 652 q1Normal.y = (pn2.y + n1.y + n2.y); 653 q1Normal.z = (pn2.z + n1.z + n2.z); 654 normalize(q1Normal); 655 } // if smooth 656 else { 657 p1Normal.x = n1.x; p1Normal.y = n1.y; p1Normal.z = n1.z; 658 q1Normal.x = n1.x+n2.x; 659 q1Normal.y = n1.y+n2.y; 660 q1Normal.z = n1.z+ n2.z; 661 normalize(q1Normal); 662 } // else 663 } // if pn1 != null 664 else { 665 pn1 = new Vector3f(); 666 pn2 = new Vector3f(); 667 p1Normal.x = n1.x; 668 p1Normal.y = n1.y; 669 p1Normal.z = n1.z; 670 671 q1Normal.x = (n1.x + n2.x); 672 q1Normal.y = (n1.y + n2.y); 673 q1Normal.z = (n1.z + n2.z); 674 normalize(q1Normal); 675 } // else 676 677 // if there is a next, check if we should smooth normal 678 679 if (k+1 < num) { 680 p3.x = outVerts[i][k+1].x; p3.y = outVerts[i][k+1].y; 681 p3.z = 0.0f; 682 q3.x = p3.x; q3.y = p3.y; q3.z = p3.z + fontExtrusion.length; 683 684 if (!getNormal(p2, q2, p3, n3)) { 685 n3.set(goodNormal); 686 } else { 687 if (!flip_side_orient) { 688 n3.negate(); 689 } 690 goodNormal.set(n3); 691 } 692 693 if (!getNormal(p3, q2, q3, n4)) { 694 n4.set(goodNormal); 695 } else { 696 if (!flip_side_orient) { 697 n4.negate(); 698 } 699 goodNormal.set(n4); 700 } 701 702 cosine = n2.dot(n3); 703 smooth = cosine > threshold; 704 705 if (smooth) { 706 p2Normal.x = (n1.x + n2.x + n3.x); 707 p2Normal.y = (n1.y + n2.y + n3.y); 708 p2Normal.z = (n1.z + n2.z + n3.z); 709 normalize(p2Normal); 710 711 q2Normal.x = (n2.x + n3.x + n4.x); 712 q2Normal.y = (n2.y + n3.y + n4.y); 713 q2Normal.z = (n2.z + n3.z + n4.z); 714 normalize(q2Normal); 715 } else { // if smooth 716 p2Normal.x = n1.x + n2.x; 717 p2Normal.y = n1.y + n2.y; 718 p2Normal.z = n1.z + n2.z; 719 normalize(p2Normal); 720 q2Normal.x = n2.x; q2Normal.y = n2.y; q2Normal.z = n2.z; 721 } // else 722 } else { // if k+1 < num 723 p2Normal.x = (n1.x + n2.x); 724 p2Normal.y = (n1.y + n2.y); 725 p2Normal.z = (n1.z + n2.z); 726 normalize(p2Normal); 727 728 q2Normal.x = n2.x; 729 q2Normal.y = n2.y; 730 q2Normal.z = n2.z; 731 } // else 732 733 // add pts for the 2 tris 734 // p1, q1, p2 and p2, q1, q2 735 736 if (flip_side_orient) { 737 triAry.setCoordinate(currCoordIndex, p1); 738 triAry.setNormal(currCoordIndex, p1Normal); 739 currCoordIndex++; 740 741 triAry.setCoordinate(currCoordIndex, q1); 742 triAry.setNormal(currCoordIndex, q1Normal); 743 currCoordIndex++; 744 745 triAry.setCoordinate(currCoordIndex, p2); 746 triAry.setNormal(currCoordIndex, p2Normal); 747 currCoordIndex++; 748 749 triAry.setCoordinate(currCoordIndex, p2); 750 triAry.setNormal(currCoordIndex, p2Normal); 751 currCoordIndex++; 752 753 triAry.setCoordinate(currCoordIndex, q1); 754 triAry.setNormal(currCoordIndex, q1Normal); 755 currCoordIndex++; 756 } else { 757 triAry.setCoordinate(currCoordIndex, q1); 758 triAry.setNormal(currCoordIndex, q1Normal); 759 currCoordIndex++; 760 761 triAry.setCoordinate(currCoordIndex, p1); 762 triAry.setNormal(currCoordIndex, p1Normal); 763 currCoordIndex++; 764 765 triAry.setCoordinate(currCoordIndex, p2); 766 triAry.setNormal(currCoordIndex, p2Normal); 767 currCoordIndex++; 768 769 triAry.setCoordinate(currCoordIndex, q1); 770 triAry.setNormal(currCoordIndex, q1Normal); 771 currCoordIndex++; 772 773 triAry.setCoordinate(currCoordIndex, p2); 774 triAry.setNormal(currCoordIndex, p2Normal); 775 currCoordIndex++; 776 } 777 triAry.setCoordinate(currCoordIndex, q2); 778 triAry.setNormal(currCoordIndex, q2Normal); 779 currCoordIndex++; 780 pn1.x = n1.x; pn1.y = n1.y; pn1.z = n1.z; 781 pn2.x = n2.x; pn2.y = n2.y; pn2.z = n2.z; 782 p1.x = p2.x; p1.y = p2.y; p1.z = p2.z; 783 q1.x = q2.x; q1.y = q2.y; q1.z = q2.z; 784 785 }// for k 786 787 // set the previous normals to null when we are done 788 pn1 = null; 789 pn2 = null; 790 }// for j 791 }//for i 792 } else { // if shape 793 int m, offset=0; 794 Point3f P2 = new Point3f(), Q2 = new Point3f(), P1=new Point3f(); 795 Vector3f nn = new Vector3f(), nn1= new Vector3f(), 796 nn2= new Vector3f(), nn3= new Vector3f(); 797 Vector3f nna = new Vector3f(), nnb=new Vector3f(); 798 float length; 799 boolean validNormal = false; 800 801 // fontExtrusion.shape is specified, and is NOT straight line 802 for (i=0;i < islandCounts.length;i++){ 803 for (j=0, k= 0, offset = num =0;j < islandCounts[i].length;j++){ 804 num += islandCounts[i][j]; 805 806 p1.x = outVerts[i][num - 1].x; 807 p1.y = outVerts[i][num - 1].y; 808 p1.z = 0.0f; 809 q1.x = p1.x; q1.y = p1.y; q1.z = p1.z+fontExtrusion.length; 810 p3.z = 0.0f; 811 for (m=num-2; m >= 0; m--) { 812 p3.x = outVerts[i][m].x; 813 p3.y = outVerts[i][m].y; 814 815 if (getNormal(p3, q1, p1, nn1)) { 816 if (!flip_side_orient) { 817 nn1.negate(); 818 } 819 goodNormal.set(nn1); 820 break; 821 } 822 } 823 for (;k < num;k++){ 824 p2.x = outVerts[i][k].x;p2.y = outVerts[i][k].y;p2.z = 0.0f; 825 q2.x = p2.x; q2.y = p2.y; q2.z = p2.z+fontExtrusion.length; 826 getNormal(p1, q1, p2, nn2); 827 828 p3.x = outVerts[i][(k+1)==num ? offset:(k+1)].x; 829 p3.y = outVerts[i][(k+1)==num ? offset:(k+1)].y; 830 p3.z = 0.0f; 831 if (!getNormal(p3,p2,q2, nn3)) { 832 nn3.set(goodNormal); 833 } else { 834 if (!flip_side_orient) { 835 nn3.negate(); 836 } 837 goodNormal.set(nn3); 838 } 839 840 // Calculate normals at the point by averaging normals 841 // of two faces on each side of the point. 842 nna.x = (nn1.x+nn2.x); 843 nna.y = (nn1.y+nn2.y); 844 nna.z = (nn1.z+nn2.z); 845 normalize(nna); 846 847 nnb.x = (nn3.x+nn2.x); 848 nnb.y = (nn3.y+nn2.y); 849 nnb.z = (nn3.z+nn2.z); 850 normalize(nnb); 851 852 P1.x = p1.x;P1.y = p1.y;P1.z = p1.z; 853 P2.x = p2.x;P2.y = p2.y; P2.z = p2.z; 854 Q2.x = q2.x;Q2.y = q2.y; Q2.z = q2.z; 855 for (m=1;m < fontExtrusion.pnts.length;m++){ 856 q1.z = q2.z = fontExtrusion.pnts[m].x; 857 q1.x = P1.x + nna.x * fontExtrusion.pnts[m].y; 858 q1.y = P1.y + nna.y * fontExtrusion.pnts[m].y; 859 q2.x = P2.x + nnb.x * fontExtrusion.pnts[m].y; 860 q2.y = P2.y + nnb.y * fontExtrusion.pnts[m].y; 861 862 if (!getNormal(p1, q1, p2, n1)) { 863 n1.set(goodNormal); 864 } else { 865 if (!flip_side_orient) { 866 n1.negate(); 867 } 868 goodNormal.set(n1); 869 } 870 871 if (flip_side_orient) { 872 triAry.setCoordinate(currCoordIndex, p1); 873 triAry.setNormal(currCoordIndex, n1); 874 currCoordIndex++; 875 876 triAry.setCoordinate(currCoordIndex, q1); 877 triAry.setNormal(currCoordIndex, n1); 878 currCoordIndex++; 879 } else { 880 triAry.setCoordinate(currCoordIndex, q1); 881 triAry.setNormal(currCoordIndex, n1); 882 currCoordIndex++; 883 884 triAry.setCoordinate(currCoordIndex, p1); 885 triAry.setNormal(currCoordIndex, n1); 886 currCoordIndex++; 887 } 888 triAry.setCoordinate(currCoordIndex, p2); 889 triAry.setNormal(currCoordIndex, n1); 890 currCoordIndex++; 891 892 if (!getNormal(p2, q1, q2, n1)) { 893 n1.set(goodNormal); 894 } else { 895 if (!flip_side_orient) { 896 n1.negate(); 897 } 898 goodNormal.set(n1); 899 } 900 901 if (flip_side_orient) { 902 triAry.setCoordinate(currCoordIndex, p2); 903 triAry.setNormal(currCoordIndex, n1); 904 currCoordIndex++; 905 906 triAry.setCoordinate(currCoordIndex, q1); 907 triAry.setNormal(currCoordIndex, n1); 908 currCoordIndex++; 909 } else { 910 triAry.setCoordinate(currCoordIndex, q1); 911 triAry.setNormal(currCoordIndex, n1); 912 currCoordIndex++; 913 914 triAry.setCoordinate(currCoordIndex, p2); 915 triAry.setNormal(currCoordIndex, n1); 916 currCoordIndex++; 917 } 918 triAry.setCoordinate(currCoordIndex, q2); 919 triAry.setNormal(currCoordIndex, n1); 920 currCoordIndex++; 921 922 p1.x = q1.x;p1.y = q1.y;p1.z = q1.z; 923 p2.x = q2.x;p2.y = q2.y;p2.z = q2.z; 924 }// for m 925 p1.x = P2.x; p1.y = P2.y; p1.z = P2.z; 926 q1.x = Q2.x; q1.y = Q2.y; q1.z = Q2.z; 927 nn1.x = nn2.x;nn1.y = nn2.y;nn1.z = nn2.z; 928 }// for k 929 offset = num; 930 }// for j 931 }//for i 932 }// if shape 933 }// if fontExtrusion 934 geo = (GeometryArrayRetained) triAry.retained; 935 geomHash.put(ch, geo); 936 } 937 938 return geo; 939 } 940 941 getNormal(Point3f p1, Point3f p2, Point3f p3, Vector3f normal)942 static boolean getNormal(Point3f p1, Point3f p2, Point3f p3, Vector3f normal) { 943 Vector3f v1 = new Vector3f(); 944 Vector3f v2 = new Vector3f(); 945 946 // Must compute normal 947 v1.sub(p2, p1); 948 v2.sub(p2, p3); 949 normal.cross(v1, v2); 950 normal.negate(); 951 952 float length = normal.length(); 953 954 if (length > 0) { 955 length = 1 / length; 956 normal.x *= length; 957 normal.y *= length; 958 normal.z *= length; 959 return true; 960 } 961 return false; 962 } 963 964 965 // check if 2 contours are inside/outside/intersect one another 966 // INPUT: 967 // vertCnt1, vertCnt2 - number of vertices in 2 contours 968 // begin1, begin2 - starting indices into vertices for 2 contours 969 // vertices - actual vertex data 970 // OUTPUT: 971 // status == 1 - intersecting contours 972 // 2 - first contour inside the second 973 // 3 - second contour inside the first 974 // 0 - disjoint contours(2 islands) 975 check2Contours(int begin1, int end1, int begin2, int end2, Point3f[] vertices)976 static int check2Contours(int begin1, int end1, int begin2, int end2, 977 Point3f[] vertices) { 978 int i, j; 979 boolean inside2, inside1; 980 981 inside2 = pointInPolygon2D(vertices[begin1].x, vertices[begin1].y, 982 begin2, end2, vertices); 983 984 for (i=begin1+1; i < end1;i++) { 985 if (pointInPolygon2D(vertices[i].x, vertices[i].y, 986 begin2, end2, vertices) != inside2) { 987 return 1; //intersecting contours 988 } 989 } 990 991 // Since we are using point in polygon test and not 992 // line in polygon test. There are cases we miss the interesting 993 // if we are not checking the reverse for all points. This happen 994 // when two points form a line pass through a polygon but the two 995 // points are outside of it. 996 997 inside1 = pointInPolygon2D(vertices[begin2].x, vertices[begin2].y, 998 begin1, end1, vertices); 999 1000 for (i=begin2+1; i < end2;i++) { 1001 if (pointInPolygon2D(vertices[i].x, vertices[i].y, 1002 begin1, end1, vertices) != inside1) { 1003 return 1; //intersecting contours 1004 } 1005 } 1006 1007 if (!inside2) { 1008 if (!inside1) { 1009 return 0; // disjoint countours 1010 } 1011 // inside2 = false and inside1 = true 1012 return 3; // second contour inside first 1013 } 1014 1015 // must be inside2 = true and inside1 = false 1016 // Note that it is not possible inside2 = inside1 = true 1017 // unless two contour overlap to each others. 1018 // 1019 return 2; // first contour inside second 1020 } 1021 1022 // Test if 2D point (x,y) lies inside polygon represented by verts. 1023 // z-value of polygon vertices is ignored. Sent only to avoid data-copy. 1024 // Uses ray-shooting algorithm to compute intersections along +X axis. 1025 // This algorithm works for all polygons(concave, self-intersecting) and 1026 // is best solution here due to large number of polygon vertices. 1027 // Point is INSIDE if number of intersections is odd, OUTSIDE if number 1028 // of intersections is even. pointInPolygon2D(float x, float y, int begIdx, int endIdx, Point3f[] verts)1029 static boolean pointInPolygon2D(float x, float y, int begIdx, int endIdx, 1030 Point3f[] verts){ 1031 1032 int i, num_intersections = 0; 1033 float xi; 1034 1035 for (i=begIdx;i < endIdx-1;i++) { 1036 if ((verts[i].y >= y && verts[i+1].y >= y) || 1037 (verts[i].y < y && verts[i+1].y < y)) 1038 continue; 1039 1040 xi = verts[i].x + (verts[i].x - verts[i+1].x)*(y - verts[i].y)/ 1041 (verts[i].y - verts[i+1].y); 1042 1043 if (x < xi) num_intersections++; 1044 } 1045 1046 // Check for segment from last vertex to first vertex. 1047 1048 if (!((verts[i].y >= y && verts[begIdx].y >= y) || 1049 (verts[i].y < y && verts[begIdx].y < y))) { 1050 xi = verts[i].x + (verts[i].x - verts[begIdx].x)*(y - verts[i].y)/ 1051 (verts[i].y - verts[begIdx].y); 1052 1053 if (x < xi) num_intersections++; 1054 } 1055 1056 return ((num_intersections % 2) != 0); 1057 } 1058 1059 normalize(Vector3f v)1060 static final boolean normalize(Vector3f v) { 1061 float len = v.length(); 1062 1063 if (len > 0) { 1064 len = 1.0f/len; 1065 v.x *= len; 1066 v.y *= len; 1067 v.z *= len; 1068 return true; 1069 } 1070 return false; 1071 } 1072 1073 1074 // A Tree of islands form based on contour, each parent's contour 1075 // enclosed all the child. We built this since Triangular fail to 1076 // handle the case of multiple concentrated contours. i.e. if 1077 // 4 contours A > B > C > D. Triangular will fail recongized 1078 // two island, one form by A & B and the other by C & D. 1079 // Using this tree we can separate out every 2 levels and pass 1080 // in to triangular to workaround its limitation. 1081 static private class IslandsNode { 1082 1083 private ArrayList islandsList = null; 1084 int startIdx, endIdx; 1085 IslandsNode(int startIdx, int endIdx)1086 IslandsNode(int startIdx, int endIdx) { 1087 this.startIdx = startIdx; 1088 this.endIdx = endIdx; 1089 islandsList = null; 1090 } 1091 addChild(IslandsNode node)1092 void addChild(IslandsNode node) { 1093 1094 if (islandsList == null) { 1095 islandsList = new ArrayList(5); 1096 } 1097 islandsList.add(node); 1098 } 1099 removeChild(IslandsNode node)1100 void removeChild(IslandsNode node) { 1101 islandsList.remove(islandsList.indexOf(node)); 1102 } 1103 getChild(int idx)1104 IslandsNode getChild(int idx) { 1105 return (IslandsNode) islandsList.get(idx); 1106 } 1107 numChild()1108 int numChild() { 1109 return (islandsList == null ? 0 : islandsList.size()); 1110 } 1111 numVertices()1112 int numVertices() { 1113 return endIdx - startIdx; 1114 } 1115 insert(IslandsNode newNode, Point3f[] vertices)1116 void insert(IslandsNode newNode, Point3f[] vertices) { 1117 boolean createNewLevel = false; 1118 1119 if (islandsList != null) { 1120 IslandsNode childNode; 1121 int status; 1122 1123 for (int i=numChild()-1; i>=0; i--) { 1124 childNode = getChild(i); 1125 status = check2Contours(newNode.startIdx, newNode.endIdx, 1126 childNode.startIdx, childNode.endIdx, 1127 vertices); 1128 switch (status) { 1129 case 2: // newNode inside childNode, go down recursively 1130 childNode.insert(newNode, vertices); 1131 return; 1132 case 3:// childNode inside newNode, 1133 // continue to search other childNode also 1134 // inside this one and group them together. 1135 newNode.addChild(childNode); 1136 createNewLevel = true; 1137 break; 1138 default: // intersecting or disjoint 1139 1140 } 1141 } 1142 } 1143 1144 if (createNewLevel) { 1145 // Remove child in newNode from this 1146 for (int i=newNode.numChild()-1; i>=0; i--) { 1147 removeChild(newNode.getChild(i)); 1148 } 1149 // Add the newNode to parent 1150 } 1151 addChild(newNode); 1152 } 1153 1154 // Return a list of node with odd number of level collectOddLevelNode(UnorderList list, int level)1155 void collectOddLevelNode(UnorderList list, int level) { 1156 if ((level % 2) == 1) { 1157 list.add(this); 1158 } 1159 if (islandsList != null) { 1160 level++; 1161 for (int i=numChild()-1; i>=0; i--) { 1162 getChild(i).collectOddLevelNode(list, level); 1163 } 1164 } 1165 } 1166 } 1167 } 1168