aboutsummaryrefslogtreecommitdiffstats
path: root/src/javax/media/j3d/Font3D.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/javax/media/j3d/Font3D.java')
-rw-r--r--src/javax/media/j3d/Font3D.java1184
1 files changed, 1184 insertions, 0 deletions
diff --git a/src/javax/media/j3d/Font3D.java b/src/javax/media/j3d/Font3D.java
new file mode 100644
index 0000000..7dc3a44
--- /dev/null
+++ b/src/javax/media/j3d/Font3D.java
@@ -0,0 +1,1184 @@
+/*
+ * Copyright 1997-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ *
+ */
+
+package javax.media.j3d;
+
+import java.awt.Font;
+import java.awt.Shape;
+import java.awt.font.FontRenderContext;
+import java.awt.font.GlyphMetrics;
+import java.awt.font.GlyphVector;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Hashtable;
+
+import javax.vecmath.Point3d;
+import javax.vecmath.Point3f;
+import javax.vecmath.Vector3f;
+
+import com.sun.j3d.utils.geometry.GeometryInfo;
+import com.sun.j3d.utils.geometry.NormalGenerator;
+
+/**
+ * The Font3D object is used to store extruded 2D glyphs. These
+ * 3D glyphs can then be used to construct Text3D NodeComponent
+ * objects.
+ * <P>
+ * A 3D Font consists of a Java 2D font, a tesellation tolerance,
+ * and an extrusion path. The extrusion
+ * path creates depth by describing how the edge of a glyph varies
+ * in the Z axis.
+ * <P>
+ * The construction of a Text3D object requires a Font3D object.
+ * The Font3D object describes the style of the text, such as its
+ * depth. The text also needs other classes, such as java.awt.Font and
+ * FontExtrusion. The Font object describes the font name (Helvetica,
+ * Courier, etc.), the font style (bold, Italic, etc.), and point
+ * size. The FontExtrusion object extends Font3D by describing
+ * the extrusion path for the Font3D object (how the edge of the
+ * font glyph varies in the Z axis).
+ *<P>
+ * To ensure correct rendering, the 2D Font object should be created
+ * with the default AffineTransform. The point size of the 2D font will
+ * be used as a rough measure of how fine a tesselation to use when
+ * creating the Font3D object: the larger the point size, in
+ * general, the finer the tesselation.
+ * <P>
+ * Custom 3D fonts as well as methods to store 3D fonts
+ * to disk will be addressed in a future release.
+ *
+ * @see java.awt.Font
+ * @see FontExtrusion
+ * @see Text3D
+ */
+public class Font3D extends NodeComponent {
+
+ Font font;
+ double tessellationTolerance;
+ FontExtrusion fontExtrusion;
+ FontRenderContext frc;
+ // Used by triangulateGlyphs method to split contour data into islands.
+ final static float EPS = 0.000001f;
+
+// Map glyph code to GeometryArrayRetained
+Hashtable<Character, GeometryArrayRetained> geomHash = new Hashtable<Character, GeometryArrayRetained>(20);
+
+ /**
+ * Constructs a Font3D object from the specified Font and
+ * FontExtrusion objects, using the default value for the
+ * tessellation tolerance. The default value is as follows:
+ *
+ * <ul>
+ * tessellation tolerance : 0.01<br>
+ * </ul>
+ * <P>
+ * The FontExtrusion object contains the extrusion path to use on
+ * the 2D Font glyphs. To ensure correct rendering the font must
+ * be created with the default AffineTransform. Passing null for
+ * the FontExtrusion parameter results in no extrusion being done.
+ *
+ * @param font the Java 2D font used to create the 3D font object
+ * @param extrudePath the extrusion path used to describe how
+ * the edge of the font varies along the Z axis
+ */
+ public Font3D(Font font, FontExtrusion extrudePath) {
+ this(font, 0.01, extrudePath);
+ }
+
+ /**
+ * Constructs a Font3D object from the specified Font and
+ * FontExtrusion objects, using the specified tessellation
+ * tolerance.
+ * The FontExtrusion object contains the extrusion path to use on
+ * the 2D Font glyphs. To ensure correct rendering, the font must
+ * be created with the default AffineTransform. Passing null for
+ * the FontExtrusion parameter results in no extrusion being done.
+ *
+ * @param font the Java 2D font used to create the 3D font object.
+ * @param tessellationTolerance the tessellation tolerance value
+ * used in tessellating the glyphs of the 2D Font.
+ * This corresponds to the <code>flatness</code> parameter in
+ * the <code>java.awt.Shape.getPathIterator</code> method.
+ * @param extrudePath the extrusion path used to describe how
+ * the edge of the font varies along the Z axis.
+ *
+ * @since Java 3D 1.2
+ */
+ public Font3D(Font font,
+ double tessellationTolerance,
+ FontExtrusion extrudePath) {
+
+ this.font = font;
+ this.tessellationTolerance = tessellationTolerance;
+ this.fontExtrusion = extrudePath;
+ this.frc = new FontRenderContext(new AffineTransform(),
+ true, true);
+ }
+
+ /**
+ * Returns the Java 2D Font used to create this Font3D object.
+ * @return Font object used by this Font3D
+ */
+ public Font getFont() {
+ return this.font;
+ }
+
+
+ /**
+ * Returns the tessellation tolerance with which this Font3D was
+ * created.
+ * @return the tessellation tolerance used by this Font3D
+ *
+ * @since Java 3D 1.2
+ */
+ public double getTessellationTolerance() {
+ return tessellationTolerance;
+ }
+
+
+ /**
+ * Copies the FontExtrusion object used to create this Font3D object
+ * into the specified parameter.
+ *
+ * @param extrudePath object that will receive the
+ * FontExtrusion information for this Font3D object
+ */
+ public void getFontExtrusion(FontExtrusion extrudePath) {
+ extrudePath = this.fontExtrusion;
+ }
+
+ /**
+ * Returns the 3D bounding box of the specified glyph code.
+ *
+ * @param glyphCode the glyphCode from the original 2D Font
+ * @param bounds the 3D glyph's bounds
+ */
+ public void getBoundingBox(int glyphCode, BoundingBox bounds){
+ int[] gCodes = {glyphCode};
+ GlyphVector gVec = font.createGlyphVector(frc, gCodes);
+ Rectangle2D.Float bounds2d = (Rectangle2D.Float)
+ (((GlyphMetrics)(gVec.getGlyphMetrics(0))).getBounds2D());
+
+ Point3d lower = new Point3d(bounds2d.x, bounds2d.y, 0.0);
+ Point3d upper;
+ if (fontExtrusion != null) {
+ upper = new Point3d(bounds2d.x + bounds2d.width,
+ bounds2d.y + bounds2d.height,
+ fontExtrusion.length);
+ } else {
+ upper = new Point3d(bounds2d.x + bounds2d.width,
+ bounds2d.y + bounds2d.height,
+ 0.0);
+ }
+ bounds.setLower(lower);
+ bounds.setUpper(upper);
+ }
+
+/**
+ * An append-only array-based integer list
+ */
+private static class IntVector {
+ int[] data;
+ int size;
+
+ IntVector() {
+ data = new int[10];
+ size = 0;
+ }
+
+ void add(int i) {
+ // need to expand backing
+ if (size == data.length)
+ data = Arrays.copyOf(data, 2 * size);
+
+ data[size] = i;
+ size++;
+ }
+}
+
+ // BY MIK OF CLASSX
+ /**
+ * Returns a GeometryArray of a glyph in this Font3D.
+ *
+ * @param c character from which to generate a tessellated glyph.
+ *
+ * @return a GeometryArray
+ *
+ * @since Java 3D 1.4
+ */
+ public GeometryArray getGlyphGeometry(char c) {
+ char code[] = { c };
+ GlyphVector gv = font.createGlyphVector(frc, code);
+
+ // triangulate the glyph
+ GeometryArrayRetained glyph_gar = triangulateGlyphs(gv, code[0]);
+
+ // Assume that triangulateGlyphs returns a triangle array with only coords & normals
+ // (and without by-ref, interleaved, etc.)
+ assert glyph_gar instanceof TriangleArrayRetained :
+ "Font3D: GeometryArray is not an instance of TrangleArray";
+ assert glyph_gar.getVertexFormat() == (GeometryArray.COORDINATES | GeometryArray.NORMALS) :
+ "Font3D: Illegal GeometryArray format -- only coordinates and normals expected";
+
+ // create a correctly sized TriangleArray
+ TriangleArray ga = new TriangleArray(glyph_gar.getVertexCount(),glyph_gar.getVertexFormat());
+
+ // temp storage for coords, normals
+ float tmp[] = new float[3];
+
+ int vertexCount = ga.getVertexCount();
+ for(int i=0; i<vertexCount; i++) {
+ // copy the glyph geometry to the TriangleArray
+ glyph_gar.getCoordinate(i,tmp);
+ ga.setCoordinate(i,tmp);
+
+ glyph_gar.getNormal(i,tmp);
+ ga.setNormal(i,tmp);
+ }
+
+ return ga;
+ }
+
+
+ // Triangulate glyph with 'unicode' if not already done.
+ GeometryArrayRetained triangulateGlyphs(GlyphVector gv, char c) {
+ Character ch = new Character(c);
+ GeometryArrayRetained geo = geomHash.get(ch);
+
+ if (geo == null) {
+ // Font Y-axis is downwards, so send affine transform to flip it.
+ Rectangle2D bnd = gv.getVisualBounds();
+ AffineTransform aTran = new AffineTransform();
+ double tx = bnd.getX() + 0.5 * bnd.getWidth();
+ double ty = bnd.getY() + 0.5 * bnd.getHeight();
+ aTran.setToTranslation(-tx, -ty);
+ aTran.scale(1.0, -1.0);
+ aTran.translate(tx, -ty);
+ Shape shape = gv.getOutline();
+ PathIterator pIt = shape.getPathIterator(aTran, tessellationTolerance);
+ int flag= -1, numPoints = 0, i, j, k, num=0, vertCnt;
+ UnorderList coords = new UnorderList(100, Point3f.class);
+ float tmpCoords[] = new float[6];
+ float lastX= .0f, lastY= .0f;
+ float firstPntx = Float.MAX_VALUE, firstPnty = Float.MAX_VALUE;
+ IntVector contours = new IntVector();
+ float maxY = -Float.MAX_VALUE;
+ int maxYIndex = 0, beginIdx = 0, endIdx = 0, start = 0;
+
+ boolean setMaxY = false;
+
+
+ while (!pIt.isDone()) {
+ Point3f vertex = new Point3f();
+ flag = pIt.currentSegment(tmpCoords);
+ if (flag == PathIterator.SEG_CLOSE){
+ if (num > 0) {
+ if (setMaxY) {
+ // Get Previous point
+ beginIdx = start;
+ endIdx = numPoints-1;
+ }
+ contours.add(num);
+ num = 0;
+ }
+ } else if (flag == PathIterator.SEG_MOVETO){
+ vertex.x = tmpCoords[0];
+ vertex.y = tmpCoords[1];
+ lastX = vertex.x;
+ lastY = vertex.y;
+
+ if ((lastX == firstPntx) && (lastY == firstPnty)) {
+ pIt.next();
+ continue;
+ }
+ setMaxY = false;
+ coords.add(vertex);
+ firstPntx = lastX;
+ firstPnty = lastY;
+ if (num> 0){
+ contours.add(num);
+ num = 0;
+ }
+ num++;
+ numPoints++;
+ // skip checking of first point,
+ // since the last point will repeat this.
+ start = numPoints ;
+ } else if (flag == PathIterator.SEG_LINETO){
+ vertex.x = tmpCoords[0];
+ vertex.y = tmpCoords[1];
+ //Check here for duplicate points. Code
+ //later in this function can not handle
+ //duplicate points.
+
+ if ((vertex.x == lastX) && (vertex.y == lastY)) {
+ pIt.next();
+ continue;
+ }
+ if (vertex.y > maxY) {
+ maxY = vertex.y;
+ maxYIndex = numPoints;
+ setMaxY = true;
+ }
+ lastX = vertex.x;
+ lastY = vertex.y;
+ coords.add(vertex);
+ num++;
+ numPoints++;
+ }
+ pIt.next();
+ }
+
+ // No data(e.g space, control characters)
+ // Two point can't form a valid contour
+ if (numPoints == 0){
+ return null;
+ }
+
+
+
+ // Determine font winding order use for side triangles
+ Point3f p1 = new Point3f(), p2 = new Point3f(), p3 = new Point3f();
+ boolean flip_side_orient = true;
+ Point3f vertices[] = (Point3f []) coords.toArray(false);
+
+ if (endIdx - beginIdx > 0) {
+ // must be true unless it is a single line
+ // define as "MoveTo p1 LineTo p2 Close" which is
+ // not a valid font definition.
+
+ if (maxYIndex == beginIdx) {
+ p1.set(vertices[endIdx]);
+ } else {
+ p1.set(vertices[maxYIndex-1]);
+ }
+ p2.set(vertices[maxYIndex]);
+ if (maxYIndex == endIdx) {
+ p3.set(vertices[beginIdx]);
+ } else {
+ p3.set(vertices[maxYIndex+1]);
+ }
+
+ if (p3.x != p2.x) {
+ if (p1.x != p2.x) {
+ // Use the one with smallest slope
+ if (Math.abs((p2.y - p1.y)/(p2.x - p1.x)) >
+ Math.abs((p3.y - p2.y)/(p3.x - p2.x))) {
+ flip_side_orient = (p3.x > p2.x);
+ } else {
+ flip_side_orient = (p2.x > p1.x);
+ }
+ } else {
+ flip_side_orient = (p3.x > p2.x);
+ }
+ } else {
+ // p1.x != p2.x, otherwise all three
+ // point form a straight vertical line with
+ // the middle point the highest. This is not a
+ // valid font definition.
+ flip_side_orient = (p2.x > p1.x);
+ }
+ }
+
+ // Build a Tree of Islands
+ int startIdx = 0;
+ IslandsNode islandsTree = new IslandsNode(-1, -1);
+
+ for (int cIdx = 0; cIdx < contours.size; cIdx++) {
+ endIdx = startIdx + contours.data[cIdx];
+ islandsTree.insert(new IslandsNode(startIdx, endIdx), vertices);
+ startIdx = endIdx;
+ }
+
+ coords = null; // Free memory
+ contours = null;
+
+ // Compute islandCounts[][] and outVerts[][]
+ UnorderList islandsList = new UnorderList(10, IslandsNode.class);
+ islandsTree.collectOddLevelNode(islandsList, 0);
+ IslandsNode nodes[] = (IslandsNode []) islandsList.toArray(false);
+ int islandCounts[][] = new int[islandsList.arraySize()][];
+ Point3f outVerts[][] = new Point3f[islandCounts.length][];
+ int nchild, sum;
+ IslandsNode node;
+
+ for (i=0; i < islandCounts.length; i++) {
+ node = nodes[i];
+ nchild = node.numChild();
+ islandCounts[i] = new int[nchild + 1];
+ islandCounts[i][0] = node.numVertices();
+ sum = 0;
+ sum += islandCounts[i][0];
+ for (j=0; j < nchild; j++) {
+ islandCounts[i][j+1] = node.getChild(j).numVertices();
+ sum += islandCounts[i][j+1];
+ }
+ outVerts[i] = new Point3f[sum];
+ startIdx = 0;
+ for (k=node.startIdx; k < node.endIdx; k++) {
+ outVerts[i][startIdx++] = vertices[k];
+ }
+
+ for (j=0; j < nchild; j++) {
+ endIdx = node.getChild(j).endIdx;
+ for (k=node.getChild(j).startIdx; k < endIdx; k++) {
+ outVerts[i][startIdx++] = vertices[k];
+ }
+ }
+ }
+
+
+
+ islandsTree = null; // Free memory
+ islandsList = null;
+ vertices = null;
+
+ int[] contourCounts = new int[1];
+ int currCoordIndex = 0, vertOffset = 0;
+ ArrayList<GeometryArray> triangData = new ArrayList<GeometryArray>();
+
+ Point3f q1 = new Point3f(), q2 = new Point3f(), q3 = new Point3f();
+ Vector3f n1 = new Vector3f(), n2 = new Vector3f();
+ numPoints = 0;
+ //Now loop thru each island, calling triangulator once per island.
+ //Combine triangle data for all islands together in one object.
+ NormalGenerator ng = new NormalGenerator();
+ for (i = 0; i < islandCounts.length; i++) {
+ contourCounts[0] = islandCounts[i].length;
+ numPoints += outVerts[i].length;
+ GeometryInfo gi = new GeometryInfo(GeometryInfo.POLYGON_ARRAY);
+ gi.setCoordinates(outVerts[i]);
+ gi.setStripCounts(islandCounts[i]);
+ gi.setContourCounts(contourCounts);
+ ng.generateNormals(gi);
+
+ GeometryArray ga = gi.getGeometryArray(false, false, false);
+ vertOffset += ga.getVertexCount();
+
+ triangData.add(ga);
+ }
+ // Multiply by 2 since we create 2 faces of the font
+ // Second term is for side-faces along depth of the font
+ if (fontExtrusion == null)
+ vertCnt = vertOffset;
+ else{
+ if (fontExtrusion.shape == null)
+ vertCnt = vertOffset * 2 + numPoints *6;
+ else{
+ vertCnt = vertOffset * 2 + numPoints * 6 *
+ (fontExtrusion.pnts.length -1);
+ }
+ }
+
+ // XXXX: Should use IndexedTriangleArray to avoid
+ // duplication of vertices. To create triangles for
+ // side faces, every vertex is duplicated currently.
+ TriangleArray triAry = new TriangleArray(vertCnt,
+ GeometryArray.COORDINATES |
+ GeometryArray.NORMALS);
+
+ boolean flip_orient[] = new boolean[islandCounts.length];
+ boolean findOrient;
+ // last known non-degenerate normal
+ Vector3f goodNormal = new Vector3f();
+
+
+ for (j=0;j < islandCounts.length;j++) {
+ GeometryArray ga = triangData.get(j);
+ vertOffset = ga.getVertexCount();
+
+ findOrient = false;
+
+ //Create the triangle array
+ for (i= 0; i < vertOffset; i+= 3, currCoordIndex += 3){
+ //Get 3 points. Since triangle is known to be flat, normal
+ // must be same for all 3 points.
+ ga.getCoordinate(i, p1);
+ ga.getNormal(i, n1);
+ ga.getCoordinate(i+1, p2);
+ ga.getCoordinate(i+2, p3);
+
+ if (!findOrient) {
+ //Check here if triangles are wound incorrectly and need
+ //to be flipped.
+ if (!getNormal(p1,p2, p3, n2)) {
+ continue;
+ }
+
+ if (n2.z >= EPS) {
+ flip_orient[j] = false;
+ } else if (n2.z <= -EPS) {
+ flip_orient[j] = true;
+ } else {
+ continue;
+ }
+ findOrient = true;
+ }
+ if (flip_orient[j]){
+ //New Triangulator preserves contour orientation. If contour
+ //input is wound incorrectly, swap 2nd and 3rd points to
+ //sure all triangles are wound correctly for j3d.
+ q1.x = p2.x; q1.y = p2.y; q1.z = p2.z;
+ p2.x = p3.x; p2.y = p3.y; p2.z = p3.z;
+ p3.x = q1.x; p3.y = q1.y; p3.z = q1.z;
+ n1.x = -n1.x; n1.y = -n1.y; n1.z = -n1.z;
+ }
+
+
+ if (fontExtrusion != null) {
+ n2.x = -n1.x;n2.y = -n1.y;n2.z = -n1.z;
+
+ triAry.setCoordinate(currCoordIndex, p1);
+ triAry.setNormal(currCoordIndex, n2);
+ triAry.setCoordinate(currCoordIndex+1, p3);
+ triAry.setNormal(currCoordIndex+1, n2);
+ triAry.setCoordinate(currCoordIndex+2, p2);
+ triAry.setNormal(currCoordIndex+2, n2);
+
+ q1.x = p1.x; q1.y = p1.y; q1.z = p1.z + fontExtrusion.length;
+ q2.x = p2.x; q2.y = p2.y; q2.z = p2.z + fontExtrusion.length;
+ q3.x = p3.x; q3.y = p3.y; q3.z = p3.z + fontExtrusion.length;
+
+ triAry.setCoordinate(currCoordIndex+vertOffset, q1);
+ triAry.setNormal(currCoordIndex+vertOffset, n1);
+ triAry.setCoordinate(currCoordIndex+1+vertOffset, q2);
+ triAry.setNormal(currCoordIndex+1+vertOffset, n1);
+ triAry.setCoordinate(currCoordIndex+2+vertOffset, q3);
+ triAry.setNormal(currCoordIndex+2+vertOffset, n1);
+ } else {
+ triAry.setCoordinate(currCoordIndex, p1);
+ triAry.setNormal(currCoordIndex, n1);
+ triAry.setCoordinate(currCoordIndex+1, p2);
+ triAry.setNormal(currCoordIndex+1, n1);
+ triAry.setCoordinate(currCoordIndex+2, p3);
+ triAry.setNormal(currCoordIndex+2, n1);
+ }
+
+ }
+ if (fontExtrusion != null) {
+ currCoordIndex += vertOffset;
+ }
+ }
+
+ //Now add side triangles in both cases.
+
+ // Since we duplicated triangles with different Z, make sure
+ // currCoordIndex points to correct location.
+ if (fontExtrusion != null){
+ if (fontExtrusion.shape == null){
+ boolean smooth;
+ // we'll put a crease if the angle between the normals is
+ // greater than 44 degrees
+ float threshold = (float) Math.cos(44.0*Math.PI/180.0);
+ float cosine;
+ // need the previous normals to check for smoothing
+ Vector3f pn1 = null, pn2 = null;
+ // need the next normals to check for smoothing
+ Vector3f n3 = new Vector3f(), n4 = new Vector3f();
+ // store the normals for each point because they are
+ // the same for both triangles
+ Vector3f p1Normal = new Vector3f();
+ Vector3f p2Normal = new Vector3f();
+ Vector3f p3Normal = new Vector3f();
+ Vector3f q1Normal = new Vector3f();
+ Vector3f q2Normal = new Vector3f();
+ Vector3f q3Normal = new Vector3f();
+
+ for (i=0;i < islandCounts.length;i++){
+ for (j=0, k=0, num =0;j < islandCounts[i].length;j++){
+ num += islandCounts[i][j];
+ p1.x = outVerts[i][num - 1].x;
+ p1.y = outVerts[i][num - 1].y;
+ p1.z = 0.0f;
+ q1.x = p1.x; q1.y = p1.y; q1.z = p1.z+fontExtrusion.length;
+ p2.z = 0.0f;
+ q2.z = p2.z+fontExtrusion.length;
+ for (int m=0; m < num;m++) {
+ p2.x = outVerts[i][m].x;
+ p2.y = outVerts[i][m].y;
+ q2.x = p2.x;
+ q2.y = p2.y;
+ if (getNormal(p1, q1, p2, n1)) {
+
+ if (!flip_side_orient) {
+ n1.negate();
+ }
+ goodNormal.set(n1);
+ break;
+ }
+ }
+
+ for (;k < num;k++){
+ p2.x = outVerts[i][k].x;p2.y = outVerts[i][k].y;p2.z = 0.0f;
+ q2.x = p2.x; q2.y = p2.y; q2.z = p2.z+fontExtrusion.length;
+
+ if (!getNormal(p1, q1, p2, n1)) {
+ n1.set(goodNormal);
+ } else {
+ if (!flip_side_orient) {
+ n1.negate();
+ }
+ goodNormal.set(n1);
+ }
+
+ if (!getNormal(p2, q1, q2, n2)) {
+ n2.set(goodNormal);
+ } else {
+ if (!flip_side_orient) {
+ n2.negate();
+ }
+ goodNormal.set(n2);
+ }
+ // if there is a previous normal, see if we need to smooth
+ // this normal or make a crease
+
+ if (pn1 != null) {
+ cosine = n1.dot(pn2);
+ smooth = cosine > threshold;
+ if (smooth) {
+ p1Normal.x = (pn1.x + pn2.x + n1.x);
+ p1Normal.y = (pn1.y + pn2.y + n1.y);
+ p1Normal.z = (pn1.z + pn2.z + n1.z);
+ normalize(p1Normal);
+
+ q1Normal.x = (pn2.x + n1.x + n2.x);
+ q1Normal.y = (pn2.y + n1.y + n2.y);
+ q1Normal.z = (pn2.z + n1.z + n2.z);
+ normalize(q1Normal);
+ } // if smooth
+ else {
+ p1Normal.x = n1.x; p1Normal.y = n1.y; p1Normal.z = n1.z;
+ q1Normal.x = n1.x+n2.x;
+ q1Normal.y = n1.y+n2.y;
+ q1Normal.z = n1.z+ n2.z;
+ normalize(q1Normal);
+ } // else
+ } // if pn1 != null
+ else {
+ pn1 = new Vector3f();
+ pn2 = new Vector3f();
+ p1Normal.x = n1.x;
+ p1Normal.y = n1.y;
+ p1Normal.z = n1.z;
+
+ q1Normal.x = (n1.x + n2.x);
+ q1Normal.y = (n1.y + n2.y);
+ q1Normal.z = (n1.z + n2.z);
+ normalize(q1Normal);
+ } // else
+
+ // if there is a next, check if we should smooth normal
+
+ if (k+1 < num) {
+ p3.x = outVerts[i][k+1].x; p3.y = outVerts[i][k+1].y;
+ p3.z = 0.0f;
+ q3.x = p3.x; q3.y = p3.y; q3.z = p3.z + fontExtrusion.length;
+
+ if (!getNormal(p2, q2, p3, n3)) {
+ n3.set(goodNormal);
+ } else {
+ if (!flip_side_orient) {
+ n3.negate();
+ }
+ goodNormal.set(n3);
+ }
+
+ if (!getNormal(p3, q2, q3, n4)) {
+ n4.set(goodNormal);
+ } else {
+ if (!flip_side_orient) {
+ n4.negate();
+ }
+ goodNormal.set(n4);
+ }
+
+ cosine = n2.dot(n3);
+ smooth = cosine > threshold;
+
+ if (smooth) {
+ p2Normal.x = (n1.x + n2.x + n3.x);
+ p2Normal.y = (n1.y + n2.y + n3.y);
+ p2Normal.z = (n1.z + n2.z + n3.z);
+ normalize(p2Normal);
+
+ q2Normal.x = (n2.x + n3.x + n4.x);
+ q2Normal.y = (n2.y + n3.y + n4.y);
+ q2Normal.z = (n2.z + n3.z + n4.z);
+ normalize(q2Normal);
+ } else { // if smooth
+ p2Normal.x = n1.x + n2.x;
+ p2Normal.y = n1.y + n2.y;
+ p2Normal.z = n1.z + n2.z;
+ normalize(p2Normal);
+ q2Normal.x = n2.x; q2Normal.y = n2.y; q2Normal.z = n2.z;
+ } // else
+ } else { // if k+1 < num
+ p2Normal.x = (n1.x + n2.x);
+ p2Normal.y = (n1.y + n2.y);
+ p2Normal.z = (n1.z + n2.z);
+ normalize(p2Normal);
+
+ q2Normal.x = n2.x;
+ q2Normal.y = n2.y;
+ q2Normal.z = n2.z;
+ } // else
+
+ // add pts for the 2 tris
+ // p1, q1, p2 and p2, q1, q2
+
+ if (flip_side_orient) {
+ triAry.setCoordinate(currCoordIndex, p1);
+ triAry.setNormal(currCoordIndex, p1Normal);
+ currCoordIndex++;
+
+ triAry.setCoordinate(currCoordIndex, q1);
+ triAry.setNormal(currCoordIndex, q1Normal);
+ currCoordIndex++;
+
+ triAry.setCoordinate(currCoordIndex, p2);
+ triAry.setNormal(currCoordIndex, p2Normal);
+ currCoordIndex++;
+
+ triAry.setCoordinate(currCoordIndex, p2);
+ triAry.setNormal(currCoordIndex, p2Normal);
+ currCoordIndex++;
+
+ triAry.setCoordinate(currCoordIndex, q1);
+ triAry.setNormal(currCoordIndex, q1Normal);
+ currCoordIndex++;
+ } else {
+ triAry.setCoordinate(currCoordIndex, q1);
+ triAry.setNormal(currCoordIndex, q1Normal);
+ currCoordIndex++;
+
+ triAry.setCoordinate(currCoordIndex, p1);
+ triAry.setNormal(currCoordIndex, p1Normal);
+ currCoordIndex++;
+
+ triAry.setCoordinate(currCoordIndex, p2);
+ triAry.setNormal(currCoordIndex, p2Normal);
+ currCoordIndex++;
+
+ triAry.setCoordinate(currCoordIndex, q1);
+ triAry.setNormal(currCoordIndex, q1Normal);
+ currCoordIndex++;
+
+ triAry.setCoordinate(currCoordIndex, p2);
+ triAry.setNormal(currCoordIndex, p2Normal);
+ currCoordIndex++;
+ }
+ triAry.setCoordinate(currCoordIndex, q2);
+ triAry.setNormal(currCoordIndex, q2Normal);
+ currCoordIndex++;
+ pn1.x = n1.x; pn1.y = n1.y; pn1.z = n1.z;
+ pn2.x = n2.x; pn2.y = n2.y; pn2.z = n2.z;
+ p1.x = p2.x; p1.y = p2.y; p1.z = p2.z;
+ q1.x = q2.x; q1.y = q2.y; q1.z = q2.z;
+
+ }// for k
+
+ // set the previous normals to null when we are done
+ pn1 = null;
+ pn2 = null;
+ }// for j
+ }//for i
+ } else { // if shape
+ int m, offset=0;
+ Point3f P2 = new Point3f(), Q2 = new Point3f(), P1=new Point3f();
+ Vector3f nn = new Vector3f(), nn1= new Vector3f(),
+ nn2= new Vector3f(), nn3= new Vector3f();
+ Vector3f nna = new Vector3f(), nnb=new Vector3f();
+ float length;
+ boolean validNormal = false;
+
+ // fontExtrusion.shape is specified, and is NOT straight line
+ for (i=0;i < islandCounts.length;i++){
+ for (j=0, k= 0, offset = num =0;j < islandCounts[i].length;j++){
+ num += islandCounts[i][j];
+
+ p1.x = outVerts[i][num - 1].x;
+ p1.y = outVerts[i][num - 1].y;
+ p1.z = 0.0f;
+ q1.x = p1.x; q1.y = p1.y; q1.z = p1.z+fontExtrusion.length;
+ p3.z = 0.0f;
+ for (m=num-2; m >= 0; m--) {
+ p3.x = outVerts[i][m].x;
+ p3.y = outVerts[i][m].y;
+
+ if (getNormal(p3, q1, p1, nn1)) {
+ if (!flip_side_orient) {
+ nn1.negate();
+ }
+ goodNormal.set(nn1);
+ break;
+ }
+ }
+ for (;k < num;k++){
+ p2.x = outVerts[i][k].x;p2.y = outVerts[i][k].y;p2.z = 0.0f;
+ q2.x = p2.x; q2.y = p2.y; q2.z = p2.z+fontExtrusion.length;
+ getNormal(p1, q1, p2, nn2);
+
+ p3.x = outVerts[i][(k+1)==num ? offset:(k+1)].x;
+ p3.y = outVerts[i][(k+1)==num ? offset:(k+1)].y;
+ p3.z = 0.0f;
+ if (!getNormal(p3,p2,q2, nn3)) {
+ nn3.set(goodNormal);
+ } else {
+ if (!flip_side_orient) {
+ nn3.negate();
+ }
+ goodNormal.set(nn3);
+ }
+
+ // Calculate normals at the point by averaging normals
+ // of two faces on each side of the point.
+ nna.x = (nn1.x+nn2.x);
+ nna.y = (nn1.y+nn2.y);
+ nna.z = (nn1.z+nn2.z);
+ normalize(nna);
+
+ nnb.x = (nn3.x+nn2.x);
+ nnb.y = (nn3.y+nn2.y);
+ nnb.z = (nn3.z+nn2.z);
+ normalize(nnb);
+
+ P1.x = p1.x;P1.y = p1.y;P1.z = p1.z;
+ P2.x = p2.x;P2.y = p2.y; P2.z = p2.z;
+ Q2.x = q2.x;Q2.y = q2.y; Q2.z = q2.z;
+ for (m=1;m < fontExtrusion.pnts.length;m++){
+ q1.z = q2.z = fontExtrusion.pnts[m].x;
+ q1.x = P1.x + nna.x * fontExtrusion.pnts[m].y;
+ q1.y = P1.y + nna.y * fontExtrusion.pnts[m].y;
+ q2.x = P2.x + nnb.x * fontExtrusion.pnts[m].y;
+ q2.y = P2.y + nnb.y * fontExtrusion.pnts[m].y;
+
+ if (!getNormal(p1, q1, p2, n1)) {
+ n1.set(goodNormal);
+ } else {
+ if (!flip_side_orient) {
+ n1.negate();
+ }
+ goodNormal.set(n1);
+ }
+
+ if (flip_side_orient) {
+ triAry.setCoordinate(currCoordIndex, p1);
+ triAry.setNormal(currCoordIndex, n1);
+ currCoordIndex++;
+
+ triAry.setCoordinate(currCoordIndex, q1);
+ triAry.setNormal(currCoordIndex, n1);
+ currCoordIndex++;
+ } else {
+ triAry.setCoordinate(currCoordIndex, q1);
+ triAry.setNormal(currCoordIndex, n1);
+ currCoordIndex++;
+
+ triAry.setCoordinate(currCoordIndex, p1);
+ triAry.setNormal(currCoordIndex, n1);
+ currCoordIndex++;
+ }
+ triAry.setCoordinate(currCoordIndex, p2);
+ triAry.setNormal(currCoordIndex, n1);
+ currCoordIndex++;
+
+ if (!getNormal(p2, q1, q2, n1)) {
+ n1.set(goodNormal);
+ } else {
+ if (!flip_side_orient) {
+ n1.negate();
+ }
+ goodNormal.set(n1);
+ }
+
+ if (flip_side_orient) {
+ triAry.setCoordinate(currCoordIndex, p2);
+ triAry.setNormal(currCoordIndex, n1);
+ currCoordIndex++;
+
+ triAry.setCoordinate(currCoordIndex, q1);
+ triAry.setNormal(currCoordIndex, n1);
+ currCoordIndex++;
+ } else {
+ triAry.setCoordinate(currCoordIndex, q1);
+ triAry.setNormal(currCoordIndex, n1);
+ currCoordIndex++;
+
+ triAry.setCoordinate(currCoordIndex, p2);
+ triAry.setNormal(currCoordIndex, n1);
+ currCoordIndex++;
+ }
+ triAry.setCoordinate(currCoordIndex, q2);
+ triAry.setNormal(currCoordIndex, n1);
+ currCoordIndex++;
+
+ p1.x = q1.x;p1.y = q1.y;p1.z = q1.z;
+ p2.x = q2.x;p2.y = q2.y;p2.z = q2.z;
+ }// for m
+ p1.x = P2.x; p1.y = P2.y; p1.z = P2.z;
+ q1.x = Q2.x; q1.y = Q2.y; q1.z = Q2.z;
+ nn1.x = nn2.x;nn1.y = nn2.y;nn1.z = nn2.z;
+ }// for k
+ offset = num;
+ }// for j
+ }//for i
+ }// if shape
+ }// if fontExtrusion
+ geo = (GeometryArrayRetained) triAry.retained;
+ geomHash.put(ch, geo);
+ }
+
+ return geo;
+ }
+
+
+ static boolean getNormal(Point3f p1, Point3f p2, Point3f p3, Vector3f normal) {
+ Vector3f v1 = new Vector3f();
+ Vector3f v2 = new Vector3f();
+
+ // Must compute normal
+ v1.sub(p2, p1);
+ v2.sub(p2, p3);
+ normal.cross(v1, v2);
+ normal.negate();
+
+ float length = normal.length();
+
+ if (length > 0) {
+ length = 1 / length;
+ normal.x *= length;
+ normal.y *= length;
+ normal.z *= length;
+ return true;
+ }
+ return false;
+ }
+
+
+ // check if 2 contours are inside/outside/intersect one another
+ // INPUT:
+ // vertCnt1, vertCnt2 - number of vertices in 2 contours
+ // begin1, begin2 - starting indices into vertices for 2 contours
+ // vertices - actual vertex data
+ // OUTPUT:
+ // status == 1 - intersecting contours
+ // 2 - first contour inside the second
+ // 3 - second contour inside the first
+ // 0 - disjoint contours(2 islands)
+
+ static int check2Contours(int begin1, int end1, int begin2, int end2,
+ Point3f[] vertices) {
+ int i;
+ boolean inside2, inside1;
+
+ inside2 = pointInPolygon2D(vertices[begin1].x, vertices[begin1].y,
+ begin2, end2, vertices);
+
+ for (i=begin1+1; i < end1;i++) {
+ if (pointInPolygon2D(vertices[i].x, vertices[i].y,
+ begin2, end2, vertices) != inside2) {
+ return 1; //intersecting contours
+ }
+ }
+
+ // Since we are using point in polygon test and not
+ // line in polygon test. There are cases we miss the interesting
+ // if we are not checking the reverse for all points. This happen
+ // when two points form a line pass through a polygon but the two
+ // points are outside of it.
+
+ inside1 = pointInPolygon2D(vertices[begin2].x, vertices[begin2].y,
+ begin1, end1, vertices);
+
+ for (i=begin2+1; i < end2;i++) {
+ if (pointInPolygon2D(vertices[i].x, vertices[i].y,
+ begin1, end1, vertices) != inside1) {
+ return 1; //intersecting contours
+ }
+ }
+
+ if (!inside2) {
+ if (!inside1) {
+ return 0; // disjoint countours
+ }
+ // inside2 = false and inside1 = true
+ return 3; // second contour inside first
+ }
+
+ // must be inside2 = true and inside1 = false
+ // Note that it is not possible inside2 = inside1 = true
+ // unless two contour overlap to each others.
+ //
+ return 2; // first contour inside second
+ }
+
+ // Test if 2D point (x,y) lies inside polygon represented by verts.
+ // z-value of polygon vertices is ignored. Sent only to avoid data-copy.
+ // Uses ray-shooting algorithm to compute intersections along +X axis.
+ // This algorithm works for all polygons(concave, self-intersecting) and
+ // is best solution here due to large number of polygon vertices.
+ // Point is INSIDE if number of intersections is odd, OUTSIDE if number
+ // of intersections is even.
+ static boolean pointInPolygon2D(float x, float y, int begIdx, int endIdx,
+ Point3f[] verts){
+
+ int i, num_intersections = 0;
+ float xi;
+
+ for (i=begIdx;i < endIdx-1;i++) {
+ if ((verts[i].y >= y && verts[i+1].y >= y) ||
+ (verts[i].y < y && verts[i+1].y < y))
+ continue;
+
+ xi = verts[i].x + (verts[i].x - verts[i+1].x)*(y - verts[i].y)/
+ (verts[i].y - verts[i+1].y);
+
+ if (x < xi) num_intersections++;
+ }
+
+ // Check for segment from last vertex to first vertex.
+
+ if (!((verts[i].y >= y && verts[begIdx].y >= y) ||
+ (verts[i].y < y && verts[begIdx].y < y))) {
+ xi = verts[i].x + (verts[i].x - verts[begIdx].x)*(y - verts[i].y)/
+ (verts[i].y - verts[begIdx].y);
+
+ if (x < xi) num_intersections++;
+ }
+
+ return ((num_intersections % 2) != 0);
+ }
+
+
+ static final boolean normalize(Vector3f v) {
+ float len = v.length();
+
+ if (len > 0) {
+ len = 1.0f/len;
+ v.x *= len;
+ v.y *= len;
+ v.z *= len;
+ return true;
+ }
+ return false;
+ }
+
+
+ // A Tree of islands form based on contour, each parent's contour
+ // enclosed all the child. We built this since Triangular fail to
+ // handle the case of multiple concentrated contours. i.e. if
+ // 4 contours A > B > C > D. Triangular will fail recongized
+ // two island, one form by A & B and the other by C & D.
+ // Using this tree we can separate out every 2 levels and pass
+ // in to triangular to workaround its limitation.
+ static private class IslandsNode {
+
+private ArrayList<IslandsNode> islandsList = null;
+ int startIdx, endIdx;
+
+ IslandsNode(int startIdx, int endIdx) {
+ this.startIdx = startIdx;
+ this.endIdx = endIdx;
+ islandsList = null;
+ }
+
+void addChild(IslandsNode node) {
+ if (islandsList == null)
+ islandsList = new ArrayList<IslandsNode>(5);
+ islandsList.add(node);
+}
+
+ void removeChild(IslandsNode node) {
+ islandsList.remove(islandsList.indexOf(node));
+ }
+
+IslandsNode getChild(int idx) {
+ return islandsList.get(idx);
+}
+
+ int numChild() {
+ return (islandsList == null ? 0 : islandsList.size());
+ }
+
+ int numVertices() {
+ return endIdx - startIdx;
+ }
+
+ void insert(IslandsNode newNode, Point3f[] vertices) {
+ boolean createNewLevel = false;
+
+ if (islandsList != null) {
+ IslandsNode childNode;
+ int status;
+
+ for (int i=numChild()-1; i>=0; i--) {
+ childNode = getChild(i);
+ status = check2Contours(newNode.startIdx, newNode.endIdx,
+ childNode.startIdx, childNode.endIdx,
+ vertices);
+ switch (status) {
+ case 2: // newNode inside childNode, go down recursively
+ childNode.insert(newNode, vertices);
+ return;
+ case 3:// childNode inside newNode,
+ // continue to search other childNode also
+ // inside this one and group them together.
+ newNode.addChild(childNode);
+ createNewLevel = true;
+ break;
+ default: // intersecting or disjoint
+
+ }
+ }
+ }
+
+ if (createNewLevel) {
+ // Remove child in newNode from this
+ for (int i=newNode.numChild()-1; i>=0; i--) {
+ removeChild(newNode.getChild(i));
+ }
+ // Add the newNode to parent
+ }
+ addChild(newNode);
+ }
+
+ // Return a list of node with odd number of level
+ void collectOddLevelNode(UnorderList list, int level) {
+ if ((level % 2) == 1) {
+ list.add(this);
+ }
+ if (islandsList != null) {
+ level++;
+ for (int i=numChild()-1; i>=0; i--) {
+ getChild(i).collectOddLevelNode(list, level);
+ }
+ }
+ }
+ }
+}