Package javax.media.j3d

Source Code of javax.media.j3d.Font3D$IntVector

/*
* 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);
    }
      }
  }
    }
}
TOP

Related Classes of javax.media.j3d.Font3D$IntVector

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
div>