protected static double[] getInitialBinormal(double[][] polygon, int metric) {
   int n = polygon.length;
   double[] B = new double[4];
   for (int i = 1; i < n - 1; ++i) {
     Pn.polarize(B, P3.planeFromPoints(null, polygon[i - 1], polygon[i], polygon[i + 1]), metric);
     if (Rn.euclideanNormSquared(B) > 10E-16) return B;
   }
   B = new double[] {Math.random(), Math.random(), Math.random(), 1.0};
   return Pn.polarizePlane(null, P3.planeFromPoints(null, B, polygon[1], polygon[2]), metric);
 }
 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");
   }
 }
  public ToolEvent process(VirtualDeviceContext context) throws MissingSlotException {
    if (context.getEvent().getInputSlot() == cameraToWorld) return null;
    DoubleArray pointer = context.getTransformationMatrix(pointerNDC);
    if (pointer == null) throw new MissingSlotException(pointerNDC);
    if (oldX == Integer.MAX_VALUE) {
      oldX = pointer.getValueAt(3);
      oldY = pointer.getValueAt(7);
      return null;
    }
    double x = pointer.getValueAt(3);
    double y = pointer.getValueAt(7);
    double dist = x * x + y * y;
    double z = 2 > dist ? Math.sqrt(2 - dist) : 0;
    Rn.setToValue(mouseCoords, x, y, z);

    mouseCoords = Rn.normalize(mouseCoords, mouseCoords);
    double[] cross = Rn.crossProduct(null, mouseCoordsOld, mouseCoords);
    double angle = gain * Math.asin(Rn.euclideanNorm(cross));
    double[] cross4 = {cross[0], cross[1], cross[2], 0};
    cross = new Matrix(context.getTransformationMatrix(cameraToWorld)).multiplyVector(cross4);
    result.assignFrom(P3.makeRotationMatrix(null, cross, angle));
    Rn.setToValue(mouseCoordsOld, x, y, z);
    return new ToolEvent(this, context.getEvent().getTimeStamp(), out, da);
  }
  public static SceneGraphComponent tubeOneEdge(
      SceneGraphComponent sgc,
      double[] ip1,
      double[] ip2,
      double rad,
      double[][] crossSection,
      int metric) {
    if (ip1.length < 3 || ip1.length > 4 || ip2.length < 3 || ip2.length > 4) {
      throw new IllegalArgumentException("Invalid dimension");
    }
    double p1[] = new double[4];
    double p2[] = new double[4];
    if (ip1.length == 3) Pn.homogenize(p1, ip1);
    else p1 = ip1;
    if (ip2.length == 3) Pn.homogenize(p2, ip2);
    else p2 = ip2;
    boolean isValid1 = Pn.isValidCoordinate(p1, 3, metric);
    boolean isValid2 = Pn.isValidCoordinate(p2, 3, metric);
    if (!isValid1 && !isValid2) return new SceneGraphComponent();
    if (!isValid1) Rn.linearCombination(p1, .99, p1, .01, p2); // (p1, p1, p2, .999, metric);
    else if (!isValid2) Rn.linearCombination(p2, .99, p2, .01, p1); // (p2, p2, p1, .999, metric);
    Pn.normalize(p1, p1, metric);
    Pn.normalize(p2, p2, metric);

    if ((debug & 2) != 0) theLogger.log(Level.FINE, "p1 is " + Rn.toString(p1));
    if ((debug & 2) != 0) theLogger.log(Level.FINE, "p2 is " + Rn.toString(p2));
    double[] polarPlane = Pn.polarizePoint(null, p1, metric);
    if ((debug & 2) != 0) theLogger.log(Level.FINE, "Polar plane is " + Rn.toString(polarPlane));

    double[] tangent = P3.lineIntersectPlane(null, p1, p2, polarPlane);

    double[] diff = Rn.subtract(null, p2, p1);
    if (Rn.innerProduct(diff, tangent) < 0.0) Rn.times(tangent, -1.0, tangent);

    Pn.setToLength(tangent, tangent, 1.0, metric);

    double[] normal = Pn.polarizePlane(null, P3.planeFromPoints(null, p1, tangent, e1), metric);
    double[] binormal =
        Pn.polarizePlane(null, P3.planeFromPoints(null, p1, tangent, normal), metric);
    Pn.setToLength(normal, normal, 1.0, metric);
    Pn.setToLength(binormal, binormal, 1.0, metric);

    double[] frame = new double[16];
    // for reasons unknown/murky, to get a RH.C.S. the vectors have to be assembled as follows in
    // the matrix
    System.arraycopy(binormal, 0, frame, 0, 4);
    System.arraycopy(normal, 0, frame, 4, 4);
    System.arraycopy(tangent, 0, frame, 8, 4);
    System.arraycopy(p1, 0, frame, 12, 4);
    // make sure the transformation is orientation-preserving
    // NOTE: If there appear shading problems on tubes, un-comment this code
    //			if (Rn.determinant(frame) < 0)		{
    //				System.arraycopy(normal, 0, frame, 0, 4);
    //				System.arraycopy(binormal, 0, frame, 4, 4);
    //
    //			}
    if ((debug & 16) != 0) {
      theLogger.log(Level.FINE, "Frame is " + Rn.matrixToString(frame));
      theLogger.log(Level.FINE, "Det is " + Rn.determinant(frame));
    }
    Rn.transpose(frame, frame);

    double[] scaler = Rn.identityMatrix(4);
    double dist = Pn.distanceBetween(p1, p2, metric);
    double coord = dist / 2;
    if (Double.isNaN(coord)) {
      LoggingSystem.getLogger(TubeUtility.class).warning("bad coord");
      return new SceneGraphComponent();
      // throw new IllegalStateException("bad coord");
    }
    if (metric == Pn.HYPERBOLIC) coord = Pn.tanh(dist / 2.0);
    else if (metric == Pn.ELLIPTIC) coord = Math.tan(dist / 2.0);
    scaler[10] = 2 * coord;

    double radcoord = rad;
    if (metric == Pn.HYPERBOLIC) radcoord = Math.sqrt(1 - coord * coord) * Pn.tanh(rad);
    else if (metric == Pn.ELLIPTIC) radcoord = Math.sqrt(1 + coord * coord) * Math.tan(rad);
    scaler[0] = scaler[5] = radcoord;

    if ((debug & 1) != 0) {
      theLogger.log(
          Level.FINE,
          "distance is \t"
              + dist
              + " scaler is \t"
              + coord
              + " and radius factor is \t"
              + radcoord);
    }
    // LoggingSystem.getLogger().log(Level.FINE,"Frame is "+Rn.matrixToString(frames[0]));
    // LoggingSystem.getLogger().log(Level.FINE,"Scaler is "+Rn.matrixToString(scaler));
    double[] translate = {0, 0, coord, 1};
    double[] translateM = P3.makeTranslationMatrix(null, translate, metric);
    // the matrix net should  be a transformation that takes the two input points
    // to the (dehomogenized) points (0,0,+/-.5,1).
    double[] net = Rn.times(null, frame, Rn.times(null, translateM, scaler));
    if ((debug & 64) != 0) theLogger.log(Level.FINE, "net is \n" + Rn.matrixToString(net));
    //			double[] inet = Rn.inverse(null, net);
    //			double[] inp1 = Rn.matrixTimesVector(null, inet, p1);
    //			double[] inp2 = Rn.matrixTimesVector(null, inet, p2);
    //			if ((debug & 64) != 0) theLogger.log(Level.FINE,"Image of end points:
    // "+Rn.toString(Pn.dehomogenize(null,inp1), 6)+"  "+Rn.toString(Pn.dehomogenize(null,inp2),6));
    if (sgc == null) sgc = new SceneGraphComponent();
    sgc.setGeometry(urTube[metric + 1]);
    if (sgc.getTransformation() == null) sgc.setTransformation(new Transformation());
    sgc.getTransformation().setMatrix(net);
    // LoggingSystem.getLogger().log(Level.FINE,"Matrix is
    // "+Rn.matrixToString(sgc.getTransformation().getMatrix()));
    return sgc;
  }