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