diff options
Diffstat (limited to 'src/javax/media/j3d/Font3D.java')
-rw-r--r-- | src/javax/media/j3d/Font3D.java | 1184 |
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); + } + } + } + } +} |