@Override
  public void decode(double[] param, CalibratedPoseAndPoint outputModel) {

    int paramIndex = 0;

    // first decode the transformation
    for (int i = 0; i < numViews; i++) {
      // don't decode if it is already known
      if (knownView[i]) continue;

      Se3_F64 se = outputModel.getWorldToCamera(i);

      rotation.setParamVector(param[paramIndex++], param[paramIndex++], param[paramIndex++]);

      RotationMatrixGenerator.rodriguesToMatrix(rotation, se.getR());

      Vector3D_F64 T = se.getT();
      T.x = param[paramIndex++];
      T.y = param[paramIndex++];
      T.z = param[paramIndex++];
    }

    // now decode the points
    for (int i = 0; i < numPoints; i++) {
      Point3D_F64 p = outputModel.getPoint(i);
      p.x = param[paramIndex++];
      p.y = param[paramIndex++];
      p.z = param[paramIndex++];
    }
  }
  /**
   * Extracts the epipoles from the trifocal tensor. Extracted epipoles will have a norm of 1 as an
   * artifact of using SVD.
   *
   * @param tensor Input: Trifocal tensor. Not Modified
   * @param e2 Output: Epipole in image 2. Homogeneous coordinates. Modified
   * @param e3 Output: Epipole in image 3. Homogeneous coordinates. Modified
   */
  public void process(TrifocalTensor tensor, Point3D_F64 e2, Point3D_F64 e3) {
    svd.decompose(tensor.T1);
    SingularOps.nullVector(svd, true, v1);
    SingularOps.nullVector(svd, false, u1);

    svd.decompose(tensor.T2);
    SingularOps.nullVector(svd, true, v2);
    SingularOps.nullVector(svd, false, u2);

    svd.decompose(tensor.T3);
    SingularOps.nullVector(svd, true, v3);
    SingularOps.nullVector(svd, false, u3);

    for (int i = 0; i < 3; i++) {
      U.set(i, 0, u1.get(i));
      U.set(i, 1, u2.get(i));
      U.set(i, 2, u3.get(i));

      V.set(i, 0, v1.get(i));
      V.set(i, 1, v2.get(i));
      V.set(i, 2, v3.get(i));
    }

    svd.decompose(U);
    SingularOps.nullVector(svd, false, tempE);
    e2.set(tempE.get(0), tempE.get(1), tempE.get(2));

    svd.decompose(V);
    SingularOps.nullVector(svd, false, tempE);
    e3.set(tempE.get(0), tempE.get(1), tempE.get(2));
  }
  private void back_and_forth(double mirror) {
    CameraUniversalOmni model = createModel(mirror);

    UniOmniPtoS_F64 pixelToUnit = new UniOmniPtoS_F64();
    pixelToUnit.setModel(model);

    UniOmniStoP_F64 unitToPixel = new UniOmniStoP_F64();
    unitToPixel.setModel(model);

    List<Point2D_F64> listPixels = new ArrayList<>();
    listPixels.add(new Point2D_F64(320, 240));
    listPixels.add(new Point2D_F64(320, 200));
    listPixels.add(new Point2D_F64(320, 280));
    listPixels.add(new Point2D_F64(280, 240));
    listPixels.add(new Point2D_F64(360, 240));
    listPixels.add(new Point2D_F64(280, 240));
    listPixels.add(new Point2D_F64(240, 180));

    for (Point2D_F64 pixel : listPixels) {
      Point3D_F64 circle = new Point3D_F64(10, 10, 10);
      pixelToUnit.compute(pixel.x, pixel.y, circle); // directly forward on unit sphere

      // it should be on the unit circle
      assertEquals(1.0, circle.norm(), GrlConstants.DOUBLE_TEST_TOL);

      Point2D_F64 found = new Point2D_F64();
      unitToPixel.compute(circle.x, circle.y, circle.z, found);

      assertEquals(pixel.x, found.x, GrlConstants.DOUBLE_TEST_TOL_SQRT);
      assertEquals(pixel.y, found.y, GrlConstants.DOUBLE_TEST_TOL_SQRT);
    }
  }
  public static Point3D_F64 createPt(Sphere3D_F64 sphere, double phi, double theta) {
    Point3D_F64 p = new Point3D_F64();
    p.set(0, 0, sphere.radius);

    Rodrigues_F64 rodX = new Rodrigues_F64(phi, new Vector3D_F64(1, 0, 0));
    DenseMatrix64F rotX = ConvertRotation3D_F64.rodriguesToMatrix(rodX, null);
    Rodrigues_F64 rodZ = new Rodrigues_F64(theta, new Vector3D_F64(0, 0, 1));
    DenseMatrix64F rotZ = ConvertRotation3D_F64.rodriguesToMatrix(rodZ, null);

    GeometryMath_F64.mult(rotX, p, p);
    GeometryMath_F64.mult(rotZ, p, p);
    p.x += sphere.center.x;
    p.y += sphere.center.y;
    p.z += sphere.center.z;

    return p;
  }
  private void checkPlaneToWorld(PlaneNormal3D_F64 planeN) {

    planeN.getN().normalize();
    PlaneGeneral3D_F64 planeG = UtilPlane3D_F64.convert(planeN, null);

    List<Point2D_F64> points2D = UtilPoint2D_F64.random(-2, 2, 100, rand);

    Se3_F64 planeToWorld = UtilPlane3D_F64.planeToWorld(planeG, null);
    Point3D_F64 p3 = new Point3D_F64();
    Point3D_F64 l3 = new Point3D_F64();
    Point3D_F64 k3 = new Point3D_F64();
    for (Point2D_F64 p : points2D) {
      p3.set(p.x, p.y, 0);
      SePointOps_F64.transform(planeToWorld, p3, l3);

      // see if it created a valid transform
      SePointOps_F64.transformReverse(planeToWorld, l3, k3);
      assertEquals(0, k3.distance(p3), GrlConstants.DOUBLE_TEST_TOL);

      assertEquals(0, UtilPlane3D_F64.evaluate(planeG, l3), GrlConstants.DOUBLE_TEST_TOL);
    }
  }
  /**
   * Randomly generate points on a plane by randomly selecting two vectors on the plane using cross
   * products
   */
  private List<Point3D_F64> randPointOnPlane(PlaneNormal3D_F64 plane, int N) {
    Vector3D_F64 v = new Vector3D_F64(-2, 0, 1);
    Vector3D_F64 a = UtilTrig_F64.cross(plane.n, v);
    a.normalize();
    Vector3D_F64 b = UtilTrig_F64.cross(plane.n, a);
    b.normalize();

    List<Point3D_F64> ret = new ArrayList<Point3D_F64>();

    for (int i = 0; i < N; i++) {
      double v0 = rand.nextGaussian();
      double v1 = rand.nextGaussian();

      Point3D_F64 p = new Point3D_F64();
      p.x = plane.p.x + v0 * a.x + v1 * b.x;
      p.y = plane.p.y + v0 * a.y + v1 * b.y;
      p.z = plane.p.z + v0 * a.z + v1 * b.z;

      ret.add(p);
    }

    return ret;
  }