public static void calculateAndSetNormalVectorsForCurve(IndexedLineSet ils) {
    double[][] polygon = ils.getVertexAttributes(Attribute.COORDINATES).toDoubleArrayArray(null);
    int n = polygon.length;
    double[][] normals = new double[n][4];

    if (n <= 1) {
      throw new IllegalArgumentException("Can't tube a vertex list of length less than 2");
    }

    double[][] polygon2 = new double[n + 2][];
    for (int i = 0; i < n; ++i) {
      polygon2[i + 1] = polygon[i];
      polygon2[0] = Rn.add(null, polygon[0], Rn.subtract(null, polygon[0], polygon[1]));
      polygon2[n + 1] =
          Rn.add(null, polygon[n - 1], Rn.subtract(null, polygon[n - 1], polygon[n - 2]));
    }
    FrameInfo[] frames =
        new TubeFactory().makeFrameField(polygon2, FrameFieldType.FRENET, Pn.EUCLIDEAN);

    for (int i = 0; i < n; ++i) {
      for (int j = 0; j < 4; ++j) {
        normals[i][j] = frames[i].frame[4 * j];
      }
      normals[i][3] *= -1;
      Pn.normalize(normals[i], normals[i], Pn.EUCLIDEAN);
    }
    ils.setVertexAttributes(
        Attribute.NORMALS, StorageModel.DOUBLE_ARRAY.array(4).createReadOnly(normals));
  }
 static {
   int n = octagonalCrossSection.length;
   urTubeLength = n;
   urTubeVerts = new double[2 * n][3];
   for (int i = 0; i < 2; ++i) {
     for (int j = 0; j < n; ++j) {
       int q = n - j - 1;
       System.arraycopy(octagonalCrossSection[j], 0, urTubeVerts[i * n + q], 0, 3);
       if (i == 0) urTubeVerts[i * n + q][2] = -0.5;
       else urTubeVerts[i * n + q][2] = 0.5;
     }
   }
   DataList verts =
       StorageModel.DOUBLE_ARRAY.array(urTubeVerts[0].length).createReadOnly(urTubeVerts);
   for (int k = 0; k < 3; ++k) {
     canonicalTranslation[k] = P3.makeTranslationMatrix(null, translation, metrics[k]);
     QuadMeshFactory qmf = new QuadMeshFactory(); // metrics[k], n, 2, true, false);
     qmf.setMetric(Pn.EUCLIDEAN); // metrics[k]);
     qmf.setULineCount(n);
     qmf.setVLineCount(2);
     qmf.setClosedInUDirection(true);
     qmf.setVertexCoordinates(verts);
     qmf.setGenerateEdgesFromFaces(true);
     qmf.setEdgeFromQuadMesh(true);
     qmf.setGenerateFaceNormals(true);
     qmf.setGenerateVertexNormals(true);
     qmf.setGenerateTextureCoordinates(true);
     qmf.update();
     urTube[k] = qmf.getIndexedFaceSet();
     urTube[k].setName("urTube" + k);
     if (k == 1)
       urTube[k].setGeometryAttributes(
           CommonAttributes.RMAN_PROXY_COMMAND, "Cylinder 1.0 -.5 .5 360");
   }
 }