Example #1
0
  /**
   * 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;
  }
Example #2
0
  public Tetrahedron() {
    int i;

    TriangleArray tetra =
        new TriangleArray(
            12,
            TriangleArray.COORDINATES | TriangleArray.NORMALS | TriangleArray.TEXTURE_COORDINATE_2);

    tetra.setCoordinates(0, verts);
    for (i = 0; i < 12; i++) {
      tetra.setTextureCoordinate(i, texCoord[i % 3]);
    }

    int face;
    Vector3f normal = new Vector3f();
    Vector3f v1 = new Vector3f();
    Vector3f v2 = new Vector3f();
    Point3f[] pts = new Point3f[3];
    for (i = 0; i < 3; i++) pts[i] = new Point3f();

    for (face = 0; face < 4; face++) {
      tetra.getCoordinates(face * 3, pts);
      v1.sub(pts[1], pts[0]);
      v2.sub(pts[2], pts[0]);
      normal.cross(v1, v2);
      normal.normalize();
      for (i = 0; i < 3; i++) {
        tetra.setNormal((face * 3 + i), normal);
      }
    }
    this.setGeometry(tetra);
    this.setAppearance(new Appearance());
  }
Example #3
0
  // 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, numContours = 0, 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;
      GeometryInfo gi = null;
      NormalGenerator ng = new NormalGenerator();
      FastVector contours = new FastVector(10);
      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.addElement(num);
            num = 0;
            numContours++;
          }
        } 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.addElement(num);
            num = 0;
            numContours++;
          }
          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);
      int contourCounts[] = contours.getData();

      for (i = 0; i < contours.getSize(); i++) {
        endIdx = startIdx + contourCounts[i];
        islandsTree.insert(new IslandsNode(startIdx, endIdx), vertices);
        startIdx = endIdx;
      }

      coords = null; // Free memory
      contours = null;
      contourCounts = 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;

      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.
      for (i = 0; i < islandCounts.length; i++) {
        contourCounts[0] = islandCounts[i].length;
        numPoints += outVerts[i].length;
        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;
  }