0001: /*
0002: * $RCSfile: Font3D.java,v $
0003: *
0004: * Copyright 1997-2008 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0006: *
0007: * This code is free software; you can redistribute it and/or modify it
0008: * under the terms of the GNU General Public License version 2 only, as
0009: * published by the Free Software Foundation. Sun designates this
0010: * particular file as subject to the "Classpath" exception as provided
0011: * by Sun in the LICENSE file that accompanied this code.
0012: *
0013: * This code is distributed in the hope that it will be useful, but WITHOUT
0014: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0015: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0016: * version 2 for more details (a copy is included in the LICENSE file that
0017: * accompanied this code).
0018: *
0019: * You should have received a copy of the GNU General Public License version
0020: * 2 along with this work; if not, write to the Free Software Foundation,
0021: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0022: *
0023: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0024: * CA 95054 USA or visit www.sun.com if you need additional information or
0025: * have any questions.
0026: *
0027: * $Revision: 1.6 $
0028: * $Date: 2008/02/28 20:17:21 $
0029: * $State: Exp $
0030: */
0031:
0032: package javax.media.j3d;
0033:
0034: import com.sun.j3d.utils.geometry.*;
0035: import com.sun.j3d.internal.FastVector;
0036: import java.awt.Font;
0037: import java.awt.font.*;
0038: import java.awt.geom.Rectangle2D;
0039: import java.awt.geom.AffineTransform;
0040: import javax.vecmath.*;
0041: import java.awt.Shape;
0042: import java.awt.geom.PathIterator;
0043: import java.util.*;
0044:
0045: /**
0046: * The Font3D object is used to store extruded 2D glyphs. These
0047: * 3D glyphs can then be used to construct Text3D NodeComponent
0048: * objects.
0049: * <P>
0050: * A 3D Font consists of a Java 2D font, a tesellation tolerance,
0051: * and an extrusion path. The extrusion
0052: * path creates depth by describing how the edge of a glyph varies
0053: * in the Z axis.
0054: * <P>
0055: * The construction of a Text3D object requires a Font3D object.
0056: * The Font3D object describes the style of the text, such as its
0057: * depth. The text also needs other classes, such as java.awt.Font and
0058: * FontExtrusion. The Font object describes the font name (Helvetica,
0059: * Courier, etc.), the font style (bold, Italic, etc.), and point
0060: * size. The FontExtrusion object extends Font3D by describing
0061: * the extrusion path for the Font3D object (how the edge of the
0062: * font glyph varies in the Z axis).
0063: *<P>
0064: * To ensure correct rendering, the 2D Font object should be created
0065: * with the default AffineTransform. The point size of the 2D font will
0066: * be used as a rough measure of how fine a tesselation to use when
0067: * creating the Font3D object: the larger the point size, in
0068: * general, the finer the tesselation.
0069: * <P>
0070: * Custom 3D fonts as well as methods to store 3D fonts
0071: * to disk will be addressed in a future release.
0072: *
0073: * @see java.awt.Font
0074: * @see FontExtrusion
0075: * @see Text3D
0076: */
0077: public class Font3D extends NodeComponent {
0078:
0079: Font font;
0080: double tessellationTolerance;
0081: FontExtrusion fontExtrusion;
0082: FontRenderContext frc;
0083: // Used by triangulateGlyphs method to split contour data into islands.
0084: final static float EPS = 0.000001f;
0085:
0086: // Map glyph code to GeometryArrayRetained
0087: Hashtable geomHash = new Hashtable(20);
0088:
0089: /**
0090: * Constructs a Font3D object from the specified Font and
0091: * FontExtrusion objects, using the default value for the
0092: * tessellation tolerance. The default value is as follows:
0093: *
0094: * <ul>
0095: * tessellation tolerance : 0.01<br>
0096: * </ul>
0097: * <P>
0098: * The FontExtrusion object contains the extrusion path to use on
0099: * the 2D Font glyphs. To ensure correct rendering the font must
0100: * be created with the default AffineTransform. Passing null for
0101: * the FontExtrusion parameter results in no extrusion being done.
0102: *
0103: * @param font the Java 2D font used to create the 3D font object
0104: * @param extrudePath the extrusion path used to describe how
0105: * the edge of the font varies along the Z axis
0106: */
0107: public Font3D(Font font, FontExtrusion extrudePath) {
0108: this (font, 0.01, extrudePath);
0109: }
0110:
0111: /**
0112: * Constructs a Font3D object from the specified Font and
0113: * FontExtrusion objects, using the specified tessellation
0114: * tolerance.
0115: * The FontExtrusion object contains the extrusion path to use on
0116: * the 2D Font glyphs. To ensure correct rendering, the font must
0117: * be created with the default AffineTransform. Passing null for
0118: * the FontExtrusion parameter results in no extrusion being done.
0119: *
0120: * @param font the Java 2D font used to create the 3D font object.
0121: * @param tessellationTolerance the tessellation tolerance value
0122: * used in tessellating the glyphs of the 2D Font.
0123: * This corresponds to the <code>flatness</code> parameter in
0124: * the <code>java.awt.Shape.getPathIterator</code> method.
0125: * @param extrudePath the extrusion path used to describe how
0126: * the edge of the font varies along the Z axis.
0127: *
0128: * @since Java 3D 1.2
0129: */
0130: public Font3D(Font font, double tessellationTolerance,
0131: FontExtrusion extrudePath) {
0132:
0133: this .font = font;
0134: this .tessellationTolerance = tessellationTolerance;
0135: this .fontExtrusion = extrudePath;
0136: this .frc = new FontRenderContext(new AffineTransform(), true,
0137: true);
0138: }
0139:
0140: /**
0141: * Returns the Java 2D Font used to create this Font3D object.
0142: * @return Font object used by this Font3D
0143: */
0144: public Font getFont() {
0145: return this .font;
0146: }
0147:
0148: /**
0149: * Returns the tessellation tolerance with which this Font3D was
0150: * created.
0151: * @return the tessellation tolerance used by this Font3D
0152: *
0153: * @since Java 3D 1.2
0154: */
0155: public double getTessellationTolerance() {
0156: return tessellationTolerance;
0157: }
0158:
0159: /**
0160: * Copies the FontExtrusion object used to create this Font3D object
0161: * into the specified parameter.
0162: *
0163: * @param extrudePath object that will receive the
0164: * FontExtrusion information for this Font3D object
0165: */
0166: public void getFontExtrusion(FontExtrusion extrudePath) {
0167: extrudePath = this .fontExtrusion;
0168: }
0169:
0170: /**
0171: * Returns the 3D bounding box of the specified glyph code.
0172: *
0173: * @param glyphCode the glyphCode from the original 2D Font
0174: * @param bounds the 3D glyph's bounds
0175: */
0176: public void getBoundingBox(int glyphCode, BoundingBox bounds) {
0177: int[] gCodes = { glyphCode };
0178: GlyphVector gVec = font.createGlyphVector(frc, gCodes);
0179: Rectangle2D.Float bounds2d = (Rectangle2D.Float) (((GlyphMetrics) (gVec
0180: .getGlyphMetrics(0))).getBounds2D());
0181:
0182: Point3d lower = new Point3d(bounds2d.x, bounds2d.y, 0.0);
0183: Point3d upper;
0184: if (fontExtrusion != null) {
0185: upper = new Point3d(bounds2d.x + bounds2d.width, bounds2d.y
0186: + bounds2d.height, fontExtrusion.length);
0187: } else {
0188: upper = new Point3d(bounds2d.x + bounds2d.width, bounds2d.y
0189: + bounds2d.height, 0.0);
0190: }
0191: bounds.setLower(lower);
0192: bounds.setUpper(upper);
0193: }
0194:
0195: // BY MIK OF CLASSX
0196: /**
0197: * Returns a GeometryArray of a glyph in this Font3D.
0198: *
0199: * @param c character from which to generate a tessellated glyph.
0200: *
0201: * @return a GeometryArray
0202: *
0203: * @since Java 3D 1.4
0204: */
0205: public GeometryArray getGlyphGeometry(char c) {
0206: char code[] = { c };
0207: GlyphVector gv = font.createGlyphVector(frc, code);
0208:
0209: // triangulate the glyph
0210: GeometryArrayRetained glyph_gar = triangulateGlyphs(gv, code[0]);
0211:
0212: // Assume that triangulateGlyphs returns a triangle array with only coords & normals
0213: // (and without by-ref, interleaved, etc.)
0214: assert glyph_gar instanceof TriangleArrayRetained : "Font3D: GeometryArray is not an instance of TrangleArray";
0215: assert glyph_gar.getVertexFormat() == (GeometryArray.COORDINATES | GeometryArray.NORMALS) : "Font3D: Illegal GeometryArray format -- only coordinates and normals expected";
0216:
0217: // create a correctly sized TriangleArray
0218: TriangleArray ga = new TriangleArray(
0219: glyph_gar.getVertexCount(), glyph_gar.getVertexFormat());
0220:
0221: // temp storage for coords, normals
0222: float tmp[] = new float[3];
0223:
0224: int vertexCount = ga.getVertexCount();
0225: for (int i = 0; i < vertexCount; i++) {
0226: // copy the glyph geometry to the TriangleArray
0227: glyph_gar.getCoordinate(i, tmp);
0228: ga.setCoordinate(i, tmp);
0229:
0230: glyph_gar.getNormal(i, tmp);
0231: ga.setNormal(i, tmp);
0232: }
0233:
0234: return ga;
0235: }
0236:
0237: // Triangulate glyph with 'unicode' if not already done.
0238: GeometryArrayRetained triangulateGlyphs(GlyphVector gv, char c) {
0239: Character ch = new Character(c);
0240: GeometryArrayRetained geo = (GeometryArrayRetained) geomHash
0241: .get(ch);
0242:
0243: if (geo == null) {
0244: // Font Y-axis is downwards, so send affine transform to flip it.
0245: Rectangle2D bnd = gv.getVisualBounds();
0246: AffineTransform aTran = new AffineTransform();
0247: double tx = bnd.getX() + 0.5 * bnd.getWidth();
0248: double ty = bnd.getY() + 0.5 * bnd.getHeight();
0249: aTran.setToTranslation(-tx, -ty);
0250: aTran.scale(1.0, -1.0);
0251: aTran.translate(tx, -ty);
0252: Shape shape = gv.getOutline();
0253: PathIterator pIt = shape.getPathIterator(aTran,
0254: tessellationTolerance);
0255: int flag = -1, numContours = 0, numPoints = 0, i, j, k, num = 0, vertCnt;
0256: UnorderList coords = new UnorderList(100, Point3f.class);
0257: float tmpCoords[] = new float[6];
0258: float lastX = .0f, lastY = .0f;
0259: float firstPntx = Float.MAX_VALUE, firstPnty = Float.MAX_VALUE;
0260: GeometryInfo gi = null;
0261: NormalGenerator ng = new NormalGenerator();
0262: FastVector contours = new FastVector(10);
0263: float maxY = -Float.MAX_VALUE;
0264: int maxYIndex = 0, beginIdx = 0, endIdx = 0, start = 0;
0265:
0266: boolean setMaxY = false;
0267:
0268: while (!pIt.isDone()) {
0269: Point3f vertex = new Point3f();
0270: flag = pIt.currentSegment(tmpCoords);
0271: if (flag == PathIterator.SEG_CLOSE) {
0272: if (num > 0) {
0273: if (setMaxY) {
0274: // Get Previous point
0275: beginIdx = start;
0276: endIdx = numPoints - 1;
0277: }
0278: contours.addElement(num);
0279: num = 0;
0280: numContours++;
0281: }
0282: } else if (flag == PathIterator.SEG_MOVETO) {
0283: vertex.x = tmpCoords[0];
0284: vertex.y = tmpCoords[1];
0285: lastX = vertex.x;
0286: lastY = vertex.y;
0287:
0288: if ((lastX == firstPntx) && (lastY == firstPnty)) {
0289: pIt.next();
0290: continue;
0291: }
0292: setMaxY = false;
0293: coords.add(vertex);
0294: firstPntx = lastX;
0295: firstPnty = lastY;
0296: if (num > 0) {
0297: contours.addElement(num);
0298: num = 0;
0299: numContours++;
0300: }
0301: num++;
0302: numPoints++;
0303: // skip checking of first point,
0304: // since the last point will repeat this.
0305: start = numPoints;
0306: } else if (flag == PathIterator.SEG_LINETO) {
0307: vertex.x = tmpCoords[0];
0308: vertex.y = tmpCoords[1];
0309: //Check here for duplicate points. Code
0310: //later in this function can not handle
0311: //duplicate points.
0312:
0313: if ((vertex.x == lastX) && (vertex.y == lastY)) {
0314: pIt.next();
0315: continue;
0316: }
0317: if (vertex.y > maxY) {
0318: maxY = vertex.y;
0319: maxYIndex = numPoints;
0320: setMaxY = true;
0321: }
0322: lastX = vertex.x;
0323: lastY = vertex.y;
0324: coords.add(vertex);
0325: num++;
0326: numPoints++;
0327: }
0328: pIt.next();
0329: }
0330:
0331: // No data(e.g space, control characters)
0332: // Two point can't form a valid contour
0333: if (numPoints == 0) {
0334: return null;
0335: }
0336:
0337: // Determine font winding order use for side triangles
0338: Point3f p1 = new Point3f(), p2 = new Point3f(), p3 = new Point3f();
0339: boolean flip_side_orient = true;
0340: Point3f vertices[] = (Point3f[]) coords.toArray(false);
0341:
0342: if (endIdx - beginIdx > 0) {
0343: // must be true unless it is a single line
0344: // define as "MoveTo p1 LineTo p2 Close" which is
0345: // not a valid font definition.
0346:
0347: if (maxYIndex == beginIdx) {
0348: p1.set(vertices[endIdx]);
0349: } else {
0350: p1.set(vertices[maxYIndex - 1]);
0351: }
0352: p2.set(vertices[maxYIndex]);
0353: if (maxYIndex == endIdx) {
0354: p3.set(vertices[beginIdx]);
0355: } else {
0356: p3.set(vertices[maxYIndex + 1]);
0357: }
0358:
0359: if (p3.x != p2.x) {
0360: if (p1.x != p2.x) {
0361: // Use the one with smallest slope
0362: if (Math.abs((p2.y - p1.y) / (p2.x - p1.x)) > Math
0363: .abs((p3.y - p2.y) / (p3.x - p2.x))) {
0364: flip_side_orient = (p3.x > p2.x);
0365: } else {
0366: flip_side_orient = (p2.x > p1.x);
0367: }
0368: } else {
0369: flip_side_orient = (p3.x > p2.x);
0370: }
0371: } else {
0372: // p1.x != p2.x, otherwise all three
0373: // point form a straight vertical line with
0374: // the middle point the highest. This is not a
0375: // valid font definition.
0376: flip_side_orient = (p2.x > p1.x);
0377: }
0378: }
0379:
0380: // Build a Tree of Islands
0381: int startIdx = 0;
0382: IslandsNode islandsTree = new IslandsNode(-1, -1);
0383: int contourCounts[] = contours.getData();
0384:
0385: for (i = 0; i < contours.getSize(); i++) {
0386: endIdx = startIdx + contourCounts[i];
0387: islandsTree.insert(new IslandsNode(startIdx, endIdx),
0388: vertices);
0389: startIdx = endIdx;
0390: }
0391:
0392: coords = null; // Free memory
0393: contours = null;
0394: contourCounts = null;
0395:
0396: // Compute islandCounts[][] and outVerts[][]
0397: UnorderList islandsList = new UnorderList(10,
0398: IslandsNode.class);
0399: islandsTree.collectOddLevelNode(islandsList, 0);
0400: IslandsNode nodes[] = (IslandsNode[]) islandsList
0401: .toArray(false);
0402: int islandCounts[][] = new int[islandsList.arraySize()][];
0403: Point3f outVerts[][] = new Point3f[islandCounts.length][];
0404: int nchild, sum;
0405: IslandsNode node;
0406:
0407: for (i = 0; i < islandCounts.length; i++) {
0408: node = nodes[i];
0409: nchild = node.numChild();
0410: islandCounts[i] = new int[nchild + 1];
0411: islandCounts[i][0] = node.numVertices();
0412: sum = 0;
0413: sum += islandCounts[i][0];
0414: for (j = 0; j < nchild; j++) {
0415: islandCounts[i][j + 1] = node.getChild(j)
0416: .numVertices();
0417: sum += islandCounts[i][j + 1];
0418: }
0419: outVerts[i] = new Point3f[sum];
0420: startIdx = 0;
0421: for (k = node.startIdx; k < node.endIdx; k++) {
0422: outVerts[i][startIdx++] = vertices[k];
0423: }
0424:
0425: for (j = 0; j < nchild; j++) {
0426: endIdx = node.getChild(j).endIdx;
0427: for (k = node.getChild(j).startIdx; k < endIdx; k++) {
0428: outVerts[i][startIdx++] = vertices[k];
0429: }
0430: }
0431: }
0432:
0433: islandsTree = null; // Free memory
0434: islandsList = null;
0435: vertices = null;
0436:
0437: contourCounts = new int[1];
0438: int currCoordIndex = 0, vertOffset = 0;
0439: ArrayList triangData = new ArrayList();
0440:
0441: Point3f q1 = new Point3f(), q2 = new Point3f(), q3 = new Point3f();
0442: Vector3f n1 = new Vector3f(), n2 = new Vector3f();
0443: numPoints = 0;
0444: //Now loop thru each island, calling triangulator once per island.
0445: //Combine triangle data for all islands together in one object.
0446: for (i = 0; i < islandCounts.length; i++) {
0447: contourCounts[0] = islandCounts[i].length;
0448: numPoints += outVerts[i].length;
0449: gi = new GeometryInfo(GeometryInfo.POLYGON_ARRAY);
0450: gi.setCoordinates(outVerts[i]);
0451: gi.setStripCounts(islandCounts[i]);
0452: gi.setContourCounts(contourCounts);
0453: ng.generateNormals(gi);
0454:
0455: GeometryArray ga = gi.getGeometryArray(false, false,
0456: false);
0457: vertOffset += ga.getVertexCount();
0458:
0459: triangData.add(ga);
0460: }
0461: // Multiply by 2 since we create 2 faces of the font
0462: // Second term is for side-faces along depth of the font
0463: if (fontExtrusion == null)
0464: vertCnt = vertOffset;
0465: else {
0466: if (fontExtrusion.shape == null)
0467: vertCnt = vertOffset * 2 + numPoints * 6;
0468: else {
0469: vertCnt = vertOffset * 2 + numPoints * 6
0470: * (fontExtrusion.pnts.length - 1);
0471: }
0472: }
0473:
0474: // XXXX: Should use IndexedTriangleArray to avoid
0475: // duplication of vertices. To create triangles for
0476: // side faces, every vertex is duplicated currently.
0477: TriangleArray triAry = new TriangleArray(vertCnt,
0478: GeometryArray.COORDINATES | GeometryArray.NORMALS);
0479:
0480: boolean flip_orient[] = new boolean[islandCounts.length];
0481: boolean findOrient;
0482: // last known non-degenerate normal
0483: Vector3f goodNormal = new Vector3f();
0484:
0485: for (j = 0; j < islandCounts.length; j++) {
0486: GeometryArray ga = (GeometryArray) triangData.get(j);
0487: vertOffset = ga.getVertexCount();
0488:
0489: findOrient = false;
0490:
0491: //Create the triangle array
0492: for (i = 0; i < vertOffset; i += 3, currCoordIndex += 3) {
0493: //Get 3 points. Since triangle is known to be flat, normal
0494: // must be same for all 3 points.
0495: ga.getCoordinate(i, p1);
0496: ga.getNormal(i, n1);
0497: ga.getCoordinate(i + 1, p2);
0498: ga.getCoordinate(i + 2, p3);
0499:
0500: if (!findOrient) {
0501: //Check here if triangles are wound incorrectly and need
0502: //to be flipped.
0503: if (!getNormal(p1, p2, p3, n2)) {
0504: continue;
0505: }
0506:
0507: if (n2.z >= EPS) {
0508: flip_orient[j] = false;
0509: } else if (n2.z <= -EPS) {
0510: flip_orient[j] = true;
0511: } else {
0512: continue;
0513: }
0514: findOrient = true;
0515: }
0516: if (flip_orient[j]) {
0517: //New Triangulator preserves contour orientation. If contour
0518: //input is wound incorrectly, swap 2nd and 3rd points to
0519: //sure all triangles are wound correctly for j3d.
0520: q1.x = p2.x;
0521: q1.y = p2.y;
0522: q1.z = p2.z;
0523: p2.x = p3.x;
0524: p2.y = p3.y;
0525: p2.z = p3.z;
0526: p3.x = q1.x;
0527: p3.y = q1.y;
0528: p3.z = q1.z;
0529: n1.x = -n1.x;
0530: n1.y = -n1.y;
0531: n1.z = -n1.z;
0532: }
0533:
0534: if (fontExtrusion != null) {
0535: n2.x = -n1.x;
0536: n2.y = -n1.y;
0537: n2.z = -n1.z;
0538:
0539: triAry.setCoordinate(currCoordIndex, p1);
0540: triAry.setNormal(currCoordIndex, n2);
0541: triAry.setCoordinate(currCoordIndex + 1, p3);
0542: triAry.setNormal(currCoordIndex + 1, n2);
0543: triAry.setCoordinate(currCoordIndex + 2, p2);
0544: triAry.setNormal(currCoordIndex + 2, n2);
0545:
0546: q1.x = p1.x;
0547: q1.y = p1.y;
0548: q1.z = p1.z + fontExtrusion.length;
0549: q2.x = p2.x;
0550: q2.y = p2.y;
0551: q2.z = p2.z + fontExtrusion.length;
0552: q3.x = p3.x;
0553: q3.y = p3.y;
0554: q3.z = p3.z + fontExtrusion.length;
0555:
0556: triAry.setCoordinate(currCoordIndex
0557: + vertOffset, q1);
0558: triAry.setNormal(currCoordIndex + vertOffset,
0559: n1);
0560: triAry.setCoordinate(currCoordIndex + 1
0561: + vertOffset, q2);
0562: triAry.setNormal(currCoordIndex + 1
0563: + vertOffset, n1);
0564: triAry.setCoordinate(currCoordIndex + 2
0565: + vertOffset, q3);
0566: triAry.setNormal(currCoordIndex + 2
0567: + vertOffset, n1);
0568: } else {
0569: triAry.setCoordinate(currCoordIndex, p1);
0570: triAry.setNormal(currCoordIndex, n1);
0571: triAry.setCoordinate(currCoordIndex + 1, p2);
0572: triAry.setNormal(currCoordIndex + 1, n1);
0573: triAry.setCoordinate(currCoordIndex + 2, p3);
0574: triAry.setNormal(currCoordIndex + 2, n1);
0575: }
0576:
0577: }
0578: if (fontExtrusion != null) {
0579: currCoordIndex += vertOffset;
0580: }
0581: }
0582:
0583: //Now add side triangles in both cases.
0584:
0585: // Since we duplicated triangles with different Z, make sure
0586: // currCoordIndex points to correct location.
0587: if (fontExtrusion != null) {
0588: if (fontExtrusion.shape == null) {
0589: boolean smooth;
0590: // we'll put a crease if the angle between the normals is
0591: // greater than 44 degrees
0592: float threshold = (float) Math
0593: .cos(44.0 * Math.PI / 180.0);
0594: float cosine;
0595: // need the previous normals to check for smoothing
0596: Vector3f pn1 = null, pn2 = null;
0597: // need the next normals to check for smoothing
0598: Vector3f n3 = new Vector3f(), n4 = new Vector3f();
0599: // store the normals for each point because they are
0600: // the same for both triangles
0601: Vector3f p1Normal = new Vector3f();
0602: Vector3f p2Normal = new Vector3f();
0603: Vector3f p3Normal = new Vector3f();
0604: Vector3f q1Normal = new Vector3f();
0605: Vector3f q2Normal = new Vector3f();
0606: Vector3f q3Normal = new Vector3f();
0607:
0608: for (i = 0; i < islandCounts.length; i++) {
0609: for (j = 0, k = 0, num = 0; j < islandCounts[i].length; j++) {
0610: num += islandCounts[i][j];
0611: p1.x = outVerts[i][num - 1].x;
0612: p1.y = outVerts[i][num - 1].y;
0613: p1.z = 0.0f;
0614: q1.x = p1.x;
0615: q1.y = p1.y;
0616: q1.z = p1.z + fontExtrusion.length;
0617: p2.z = 0.0f;
0618: q2.z = p2.z + fontExtrusion.length;
0619: for (int m = 0; m < num; m++) {
0620: p2.x = outVerts[i][m].x;
0621: p2.y = outVerts[i][m].y;
0622: q2.x = p2.x;
0623: q2.y = p2.y;
0624: if (getNormal(p1, q1, p2, n1)) {
0625:
0626: if (!flip_side_orient) {
0627: n1.negate();
0628: }
0629: goodNormal.set(n1);
0630: break;
0631: }
0632: }
0633:
0634: for (; k < num; k++) {
0635: p2.x = outVerts[i][k].x;
0636: p2.y = outVerts[i][k].y;
0637: p2.z = 0.0f;
0638: q2.x = p2.x;
0639: q2.y = p2.y;
0640: q2.z = p2.z + fontExtrusion.length;
0641:
0642: if (!getNormal(p1, q1, p2, n1)) {
0643: n1.set(goodNormal);
0644: } else {
0645: if (!flip_side_orient) {
0646: n1.negate();
0647: }
0648: goodNormal.set(n1);
0649: }
0650:
0651: if (!getNormal(p2, q1, q2, n2)) {
0652: n2.set(goodNormal);
0653: } else {
0654: if (!flip_side_orient) {
0655: n2.negate();
0656: }
0657: goodNormal.set(n2);
0658: }
0659: // if there is a previous normal, see if we need to smooth
0660: // this normal or make a crease
0661:
0662: if (pn1 != null) {
0663: cosine = n1.dot(pn2);
0664: smooth = cosine > threshold;
0665: if (smooth) {
0666: p1Normal.x = (pn1.x + pn2.x + n1.x);
0667: p1Normal.y = (pn1.y + pn2.y + n1.y);
0668: p1Normal.z = (pn1.z + pn2.z + n1.z);
0669: normalize(p1Normal);
0670:
0671: q1Normal.x = (pn2.x + n1.x + n2.x);
0672: q1Normal.y = (pn2.y + n1.y + n2.y);
0673: q1Normal.z = (pn2.z + n1.z + n2.z);
0674: normalize(q1Normal);
0675: } // if smooth
0676: else {
0677: p1Normal.x = n1.x;
0678: p1Normal.y = n1.y;
0679: p1Normal.z = n1.z;
0680: q1Normal.x = n1.x + n2.x;
0681: q1Normal.y = n1.y + n2.y;
0682: q1Normal.z = n1.z + n2.z;
0683: normalize(q1Normal);
0684: } // else
0685: } // if pn1 != null
0686: else {
0687: pn1 = new Vector3f();
0688: pn2 = new Vector3f();
0689: p1Normal.x = n1.x;
0690: p1Normal.y = n1.y;
0691: p1Normal.z = n1.z;
0692:
0693: q1Normal.x = (n1.x + n2.x);
0694: q1Normal.y = (n1.y + n2.y);
0695: q1Normal.z = (n1.z + n2.z);
0696: normalize(q1Normal);
0697: } // else
0698:
0699: // if there is a next, check if we should smooth normal
0700:
0701: if (k + 1 < num) {
0702: p3.x = outVerts[i][k + 1].x;
0703: p3.y = outVerts[i][k + 1].y;
0704: p3.z = 0.0f;
0705: q3.x = p3.x;
0706: q3.y = p3.y;
0707: q3.z = p3.z + fontExtrusion.length;
0708:
0709: if (!getNormal(p2, q2, p3, n3)) {
0710: n3.set(goodNormal);
0711: } else {
0712: if (!flip_side_orient) {
0713: n3.negate();
0714: }
0715: goodNormal.set(n3);
0716: }
0717:
0718: if (!getNormal(p3, q2, q3, n4)) {
0719: n4.set(goodNormal);
0720: } else {
0721: if (!flip_side_orient) {
0722: n4.negate();
0723: }
0724: goodNormal.set(n4);
0725: }
0726:
0727: cosine = n2.dot(n3);
0728: smooth = cosine > threshold;
0729:
0730: if (smooth) {
0731: p2Normal.x = (n1.x + n2.x + n3.x);
0732: p2Normal.y = (n1.y + n2.y + n3.y);
0733: p2Normal.z = (n1.z + n2.z + n3.z);
0734: normalize(p2Normal);
0735:
0736: q2Normal.x = (n2.x + n3.x + n4.x);
0737: q2Normal.y = (n2.y + n3.y + n4.y);
0738: q2Normal.z = (n2.z + n3.z + n4.z);
0739: normalize(q2Normal);
0740: } else { // if smooth
0741: p2Normal.x = n1.x + n2.x;
0742: p2Normal.y = n1.y + n2.y;
0743: p2Normal.z = n1.z + n2.z;
0744: normalize(p2Normal);
0745: q2Normal.x = n2.x;
0746: q2Normal.y = n2.y;
0747: q2Normal.z = n2.z;
0748: } // else
0749: } else { // if k+1 < num
0750: p2Normal.x = (n1.x + n2.x);
0751: p2Normal.y = (n1.y + n2.y);
0752: p2Normal.z = (n1.z + n2.z);
0753: normalize(p2Normal);
0754:
0755: q2Normal.x = n2.x;
0756: q2Normal.y = n2.y;
0757: q2Normal.z = n2.z;
0758: } // else
0759:
0760: // add pts for the 2 tris
0761: // p1, q1, p2 and p2, q1, q2
0762:
0763: if (flip_side_orient) {
0764: triAry.setCoordinate(
0765: currCoordIndex, p1);
0766: triAry.setNormal(currCoordIndex,
0767: p1Normal);
0768: currCoordIndex++;
0769:
0770: triAry.setCoordinate(
0771: currCoordIndex, q1);
0772: triAry.setNormal(currCoordIndex,
0773: q1Normal);
0774: currCoordIndex++;
0775:
0776: triAry.setCoordinate(
0777: currCoordIndex, p2);
0778: triAry.setNormal(currCoordIndex,
0779: p2Normal);
0780: currCoordIndex++;
0781:
0782: triAry.setCoordinate(
0783: currCoordIndex, p2);
0784: triAry.setNormal(currCoordIndex,
0785: p2Normal);
0786: currCoordIndex++;
0787:
0788: triAry.setCoordinate(
0789: currCoordIndex, q1);
0790: triAry.setNormal(currCoordIndex,
0791: q1Normal);
0792: currCoordIndex++;
0793: } else {
0794: triAry.setCoordinate(
0795: currCoordIndex, q1);
0796: triAry.setNormal(currCoordIndex,
0797: q1Normal);
0798: currCoordIndex++;
0799:
0800: triAry.setCoordinate(
0801: currCoordIndex, p1);
0802: triAry.setNormal(currCoordIndex,
0803: p1Normal);
0804: currCoordIndex++;
0805:
0806: triAry.setCoordinate(
0807: currCoordIndex, p2);
0808: triAry.setNormal(currCoordIndex,
0809: p2Normal);
0810: currCoordIndex++;
0811:
0812: triAry.setCoordinate(
0813: currCoordIndex, q1);
0814: triAry.setNormal(currCoordIndex,
0815: q1Normal);
0816: currCoordIndex++;
0817:
0818: triAry.setCoordinate(
0819: currCoordIndex, p2);
0820: triAry.setNormal(currCoordIndex,
0821: p2Normal);
0822: currCoordIndex++;
0823: }
0824: triAry
0825: .setCoordinate(currCoordIndex,
0826: q2);
0827: triAry.setNormal(currCoordIndex,
0828: q2Normal);
0829: currCoordIndex++;
0830: pn1.x = n1.x;
0831: pn1.y = n1.y;
0832: pn1.z = n1.z;
0833: pn2.x = n2.x;
0834: pn2.y = n2.y;
0835: pn2.z = n2.z;
0836: p1.x = p2.x;
0837: p1.y = p2.y;
0838: p1.z = p2.z;
0839: q1.x = q2.x;
0840: q1.y = q2.y;
0841: q1.z = q2.z;
0842:
0843: }// for k
0844:
0845: // set the previous normals to null when we are done
0846: pn1 = null;
0847: pn2 = null;
0848: }// for j
0849: }//for i
0850: } else { // if shape
0851: int m, offset = 0;
0852: Point3f P2 = new Point3f(), Q2 = new Point3f(), P1 = new Point3f();
0853: Vector3f nn = new Vector3f(), nn1 = new Vector3f(), nn2 = new Vector3f(), nn3 = new Vector3f();
0854: Vector3f nna = new Vector3f(), nnb = new Vector3f();
0855: float length;
0856: boolean validNormal = false;
0857:
0858: // fontExtrusion.shape is specified, and is NOT straight line
0859: for (i = 0; i < islandCounts.length; i++) {
0860: for (j = 0, k = 0, offset = num = 0; j < islandCounts[i].length; j++) {
0861: num += islandCounts[i][j];
0862:
0863: p1.x = outVerts[i][num - 1].x;
0864: p1.y = outVerts[i][num - 1].y;
0865: p1.z = 0.0f;
0866: q1.x = p1.x;
0867: q1.y = p1.y;
0868: q1.z = p1.z + fontExtrusion.length;
0869: p3.z = 0.0f;
0870: for (m = num - 2; m >= 0; m--) {
0871: p3.x = outVerts[i][m].x;
0872: p3.y = outVerts[i][m].y;
0873:
0874: if (getNormal(p3, q1, p1, nn1)) {
0875: if (!flip_side_orient) {
0876: nn1.negate();
0877: }
0878: goodNormal.set(nn1);
0879: break;
0880: }
0881: }
0882: for (; k < num; k++) {
0883: p2.x = outVerts[i][k].x;
0884: p2.y = outVerts[i][k].y;
0885: p2.z = 0.0f;
0886: q2.x = p2.x;
0887: q2.y = p2.y;
0888: q2.z = p2.z + fontExtrusion.length;
0889: getNormal(p1, q1, p2, nn2);
0890:
0891: p3.x = outVerts[i][(k + 1) == num ? offset
0892: : (k + 1)].x;
0893: p3.y = outVerts[i][(k + 1) == num ? offset
0894: : (k + 1)].y;
0895: p3.z = 0.0f;
0896: if (!getNormal(p3, p2, q2, nn3)) {
0897: nn3.set(goodNormal);
0898: } else {
0899: if (!flip_side_orient) {
0900: nn3.negate();
0901: }
0902: goodNormal.set(nn3);
0903: }
0904:
0905: // Calculate normals at the point by averaging normals
0906: // of two faces on each side of the point.
0907: nna.x = (nn1.x + nn2.x);
0908: nna.y = (nn1.y + nn2.y);
0909: nna.z = (nn1.z + nn2.z);
0910: normalize(nna);
0911:
0912: nnb.x = (nn3.x + nn2.x);
0913: nnb.y = (nn3.y + nn2.y);
0914: nnb.z = (nn3.z + nn2.z);
0915: normalize(nnb);
0916:
0917: P1.x = p1.x;
0918: P1.y = p1.y;
0919: P1.z = p1.z;
0920: P2.x = p2.x;
0921: P2.y = p2.y;
0922: P2.z = p2.z;
0923: Q2.x = q2.x;
0924: Q2.y = q2.y;
0925: Q2.z = q2.z;
0926: for (m = 1; m < fontExtrusion.pnts.length; m++) {
0927: q1.z = q2.z = fontExtrusion.pnts[m].x;
0928: q1.x = P1.x + nna.x
0929: * fontExtrusion.pnts[m].y;
0930: q1.y = P1.y + nna.y
0931: * fontExtrusion.pnts[m].y;
0932: q2.x = P2.x + nnb.x
0933: * fontExtrusion.pnts[m].y;
0934: q2.y = P2.y + nnb.y
0935: * fontExtrusion.pnts[m].y;
0936:
0937: if (!getNormal(p1, q1, p2, n1)) {
0938: n1.set(goodNormal);
0939: } else {
0940: if (!flip_side_orient) {
0941: n1.negate();
0942: }
0943: goodNormal.set(n1);
0944: }
0945:
0946: if (flip_side_orient) {
0947: triAry.setCoordinate(
0948: currCoordIndex, p1);
0949: triAry.setNormal(
0950: currCoordIndex, n1);
0951: currCoordIndex++;
0952:
0953: triAry.setCoordinate(
0954: currCoordIndex, q1);
0955: triAry.setNormal(
0956: currCoordIndex, n1);
0957: currCoordIndex++;
0958: } else {
0959: triAry.setCoordinate(
0960: currCoordIndex, q1);
0961: triAry.setNormal(
0962: currCoordIndex, n1);
0963: currCoordIndex++;
0964:
0965: triAry.setCoordinate(
0966: currCoordIndex, p1);
0967: triAry.setNormal(
0968: currCoordIndex, n1);
0969: currCoordIndex++;
0970: }
0971: triAry.setCoordinate(
0972: currCoordIndex, p2);
0973: triAry
0974: .setNormal(currCoordIndex,
0975: n1);
0976: currCoordIndex++;
0977:
0978: if (!getNormal(p2, q1, q2, n1)) {
0979: n1.set(goodNormal);
0980: } else {
0981: if (!flip_side_orient) {
0982: n1.negate();
0983: }
0984: goodNormal.set(n1);
0985: }
0986:
0987: if (flip_side_orient) {
0988: triAry.setCoordinate(
0989: currCoordIndex, p2);
0990: triAry.setNormal(
0991: currCoordIndex, n1);
0992: currCoordIndex++;
0993:
0994: triAry.setCoordinate(
0995: currCoordIndex, q1);
0996: triAry.setNormal(
0997: currCoordIndex, n1);
0998: currCoordIndex++;
0999: } else {
1000: triAry.setCoordinate(
1001: currCoordIndex, q1);
1002: triAry.setNormal(
1003: currCoordIndex, n1);
1004: currCoordIndex++;
1005:
1006: triAry.setCoordinate(
1007: currCoordIndex, p2);
1008: triAry.setNormal(
1009: currCoordIndex, n1);
1010: currCoordIndex++;
1011: }
1012: triAry.setCoordinate(
1013: currCoordIndex, q2);
1014: triAry
1015: .setNormal(currCoordIndex,
1016: n1);
1017: currCoordIndex++;
1018:
1019: p1.x = q1.x;
1020: p1.y = q1.y;
1021: p1.z = q1.z;
1022: p2.x = q2.x;
1023: p2.y = q2.y;
1024: p2.z = q2.z;
1025: }// for m
1026: p1.x = P2.x;
1027: p1.y = P2.y;
1028: p1.z = P2.z;
1029: q1.x = Q2.x;
1030: q1.y = Q2.y;
1031: q1.z = Q2.z;
1032: nn1.x = nn2.x;
1033: nn1.y = nn2.y;
1034: nn1.z = nn2.z;
1035: }// for k
1036: offset = num;
1037: }// for j
1038: }//for i
1039: }// if shape
1040: }// if fontExtrusion
1041: geo = (GeometryArrayRetained) triAry.retained;
1042: geomHash.put(ch, geo);
1043: }
1044:
1045: return geo;
1046: }
1047:
1048: static boolean getNormal(Point3f p1, Point3f p2, Point3f p3,
1049: Vector3f normal) {
1050: Vector3f v1 = new Vector3f();
1051: Vector3f v2 = new Vector3f();
1052:
1053: // Must compute normal
1054: v1.sub(p2, p1);
1055: v2.sub(p2, p3);
1056: normal.cross(v1, v2);
1057: normal.negate();
1058:
1059: float length = normal.length();
1060:
1061: if (length > 0) {
1062: length = 1 / length;
1063: normal.x *= length;
1064: normal.y *= length;
1065: normal.z *= length;
1066: return true;
1067: }
1068: return false;
1069: }
1070:
1071: // check if 2 contours are inside/outside/intersect one another
1072: // INPUT:
1073: // vertCnt1, vertCnt2 - number of vertices in 2 contours
1074: // begin1, begin2 - starting indices into vertices for 2 contours
1075: // vertices - actual vertex data
1076: // OUTPUT:
1077: // status == 1 - intersecting contours
1078: // 2 - first contour inside the second
1079: // 3 - second contour inside the first
1080: // 0 - disjoint contours(2 islands)
1081:
1082: static int check2Contours(int begin1, int end1, int begin2,
1083: int end2, Point3f[] vertices) {
1084: int i, j;
1085: boolean inside2, inside1;
1086:
1087: inside2 = pointInPolygon2D(vertices[begin1].x,
1088: vertices[begin1].y, begin2, end2, vertices);
1089:
1090: for (i = begin1 + 1; i < end1; i++) {
1091: if (pointInPolygon2D(vertices[i].x, vertices[i].y, begin2,
1092: end2, vertices) != inside2) {
1093: return 1; //intersecting contours
1094: }
1095: }
1096:
1097: // Since we are using point in polygon test and not
1098: // line in polygon test. There are cases we miss the interesting
1099: // if we are not checking the reverse for all points. This happen
1100: // when two points form a line pass through a polygon but the two
1101: // points are outside of it.
1102:
1103: inside1 = pointInPolygon2D(vertices[begin2].x,
1104: vertices[begin2].y, begin1, end1, vertices);
1105:
1106: for (i = begin2 + 1; i < end2; i++) {
1107: if (pointInPolygon2D(vertices[i].x, vertices[i].y, begin1,
1108: end1, vertices) != inside1) {
1109: return 1; //intersecting contours
1110: }
1111: }
1112:
1113: if (!inside2) {
1114: if (!inside1) {
1115: return 0; // disjoint countours
1116: }
1117: // inside2 = false and inside1 = true
1118: return 3; // second contour inside first
1119: }
1120:
1121: // must be inside2 = true and inside1 = false
1122: // Note that it is not possible inside2 = inside1 = true
1123: // unless two contour overlap to each others.
1124: //
1125: return 2; // first contour inside second
1126: }
1127:
1128: // Test if 2D point (x,y) lies inside polygon represented by verts.
1129: // z-value of polygon vertices is ignored. Sent only to avoid data-copy.
1130: // Uses ray-shooting algorithm to compute intersections along +X axis.
1131: // This algorithm works for all polygons(concave, self-intersecting) and
1132: // is best solution here due to large number of polygon vertices.
1133: // Point is INSIDE if number of intersections is odd, OUTSIDE if number
1134: // of intersections is even.
1135: static boolean pointInPolygon2D(float x, float y, int begIdx,
1136: int endIdx, Point3f[] verts) {
1137:
1138: int i, num_intersections = 0;
1139: float xi;
1140:
1141: for (i = begIdx; i < endIdx - 1; i++) {
1142: if ((verts[i].y >= y && verts[i + 1].y >= y)
1143: || (verts[i].y < y && verts[i + 1].y < y))
1144: continue;
1145:
1146: xi = verts[i].x + (verts[i].x - verts[i + 1].x)
1147: * (y - verts[i].y) / (verts[i].y - verts[i + 1].y);
1148:
1149: if (x < xi)
1150: num_intersections++;
1151: }
1152:
1153: // Check for segment from last vertex to first vertex.
1154:
1155: if (!((verts[i].y >= y && verts[begIdx].y >= y) || (verts[i].y < y && verts[begIdx].y < y))) {
1156: xi = verts[i].x + (verts[i].x - verts[begIdx].x)
1157: * (y - verts[i].y) / (verts[i].y - verts[begIdx].y);
1158:
1159: if (x < xi)
1160: num_intersections++;
1161: }
1162:
1163: return ((num_intersections % 2) != 0);
1164: }
1165:
1166: static final boolean normalize(Vector3f v) {
1167: float len = v.length();
1168:
1169: if (len > 0) {
1170: len = 1.0f / len;
1171: v.x *= len;
1172: v.y *= len;
1173: v.z *= len;
1174: return true;
1175: }
1176: return false;
1177: }
1178:
1179: // A Tree of islands form based on contour, each parent's contour
1180: // enclosed all the child. We built this since Triangular fail to
1181: // handle the case of multiple concentrated contours. i.e. if
1182: // 4 contours A > B > C > D. Triangular will fail recongized
1183: // two island, one form by A & B and the other by C & D.
1184: // Using this tree we can separate out every 2 levels and pass
1185: // in to triangular to workaround its limitation.
1186: static private class IslandsNode {
1187:
1188: private ArrayList islandsList = null;
1189: int startIdx, endIdx;
1190:
1191: IslandsNode(int startIdx, int endIdx) {
1192: this .startIdx = startIdx;
1193: this .endIdx = endIdx;
1194: islandsList = null;
1195: }
1196:
1197: void addChild(IslandsNode node) {
1198:
1199: if (islandsList == null) {
1200: islandsList = new ArrayList(5);
1201: }
1202: islandsList.add(node);
1203: }
1204:
1205: void removeChild(IslandsNode node) {
1206: islandsList.remove(islandsList.indexOf(node));
1207: }
1208:
1209: IslandsNode getChild(int idx) {
1210: return (IslandsNode) islandsList.get(idx);
1211: }
1212:
1213: int numChild() {
1214: return (islandsList == null ? 0 : islandsList.size());
1215: }
1216:
1217: int numVertices() {
1218: return endIdx - startIdx;
1219: }
1220:
1221: void insert(IslandsNode newNode, Point3f[] vertices) {
1222: boolean createNewLevel = false;
1223:
1224: if (islandsList != null) {
1225: IslandsNode childNode;
1226: int status;
1227:
1228: for (int i = numChild() - 1; i >= 0; i--) {
1229: childNode = getChild(i);
1230: status = check2Contours(newNode.startIdx,
1231: newNode.endIdx, childNode.startIdx,
1232: childNode.endIdx, vertices);
1233: switch (status) {
1234: case 2: // newNode inside childNode, go down recursively
1235: childNode.insert(newNode, vertices);
1236: return;
1237: case 3:// childNode inside newNode,
1238: // continue to search other childNode also
1239: // inside this one and group them together.
1240: newNode.addChild(childNode);
1241: createNewLevel = true;
1242: break;
1243: default: // intersecting or disjoint
1244:
1245: }
1246: }
1247: }
1248:
1249: if (createNewLevel) {
1250: // Remove child in newNode from this
1251: for (int i = newNode.numChild() - 1; i >= 0; i--) {
1252: removeChild(newNode.getChild(i));
1253: }
1254: // Add the newNode to parent
1255: }
1256: addChild(newNode);
1257: }
1258:
1259: // Return a list of node with odd number of level
1260: void collectOddLevelNode(UnorderList list, int level) {
1261: if ((level % 2) == 1) {
1262: list.add(this );
1263: }
1264: if (islandsList != null) {
1265: level++;
1266: for (int i = numChild() - 1; i >= 0; i--) {
1267: getChild(i).collectOddLevelNode(list, level);
1268: }
1269: }
1270: }
1271: }
1272: }
|