@Test
  public void convert_general_norm() {
    PlaneGeneral3D_F64 general = new PlaneGeneral3D_F64(1, 2, 3, 4);

    PlaneNormal3D_F64 foundPlane = UtilPlane3D_F64.convert(general, null);
    List<Point3D_F64> points = randPointOnPlane(foundPlane, 10);

    for (Point3D_F64 p : points) {
      double found = UtilPlane3D_F64.evaluate(general, p);
      assertEquals(0, found, GrlConstants.DOUBLE_TEST_TOL);
    }
  }
  @Test
  public void convert_norm_general() {
    PlaneNormal3D_F64 original = new PlaneNormal3D_F64();
    original.n.set(1, 2, 3);
    original.n.normalize();
    original.p.set(-2, 3, 5);

    PlaneGeneral3D_F64 test = UtilPlane3D_F64.convert(original, null);
    List<Point3D_F64> points = randPointOnPlane(original, 10);

    for (Point3D_F64 p : points) {
      double found = UtilPlane3D_F64.evaluate(test, p);
      assertEquals(0, found, GrlConstants.DOUBLE_TEST_TOL);
    }
  }
  @Test
  public void hessianNormalForm() {
    PlaneGeneral3D_F64 a = new PlaneGeneral3D_F64(2, -3, 4, 5);
    double n = Math.sqrt(2 * 2 + 3 * 3 + 4 * 4);

    UtilPlane3D_F64.hessianNormalForm(a);

    assertEquals(2 / n, a.A, GrlConstants.DOUBLE_TEST_TOL);
    assertEquals(-3 / n, a.B, GrlConstants.DOUBLE_TEST_TOL);
    assertEquals(4 / n, a.C, GrlConstants.DOUBLE_TEST_TOL);
    assertEquals(5 / n, a.D, GrlConstants.DOUBLE_TEST_TOL);
  }
  @Test
  public void convert_tangent_norm() {
    PlaneNormal3D_F64 original = new PlaneNormal3D_F64();
    original.n.set(1, 0, 0);
    original.n.normalize();
    original.p.set(-2, 3, 5);

    // create a bunch of points which are on the original plane
    List<Point3D_F64> points = randPointOnPlane(original, 10);

    // now manually construct the plane in tangent form
    PlaneTangent3D_F64 tangent = new PlaneTangent3D_F64(-2, 0, 0);

    // convert this back into normal form
    PlaneNormal3D_F64 conv = UtilPlane3D_F64.convert(tangent, (PlaneNormal3D_F64) null);

    // the points should still be on the plane
    for (Point3D_F64 p : points) {
      double found = UtilPlane3D_F64.evaluate(conv, p);
      assertEquals(0, found, GrlConstants.DOUBLE_TEST_TOL);
    }
  }
  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);
    }
  }
  @Test
  public void equals_planeNorm() {

    for (int i = 0; i < 100; i++) {
      PlaneNormal3D_F64 a =
          new PlaneNormal3D_F64(
              (double) rand.nextGaussian(),
              (double) rand.nextGaussian(),
              (double) rand.nextGaussian(),
              (double) rand.nextGaussian(),
              (double) rand.nextGaussian(),
              (double) rand.nextGaussian());
      PlaneNormal3D_F64 b = new PlaneNormal3D_F64(a);

      b.p.x += (rand.nextDouble() - 0.5) * GrlConstants.DOUBLE_TEST_TOL;
      b.p.y += (rand.nextDouble() - 0.5) * GrlConstants.DOUBLE_TEST_TOL;
      b.p.z += (rand.nextDouble() - 0.5) * GrlConstants.DOUBLE_TEST_TOL;
      b.n.x += (rand.nextDouble() - 0.5) * GrlConstants.DOUBLE_TEST_TOL;
      b.n.y += (rand.nextDouble() - 0.5) * GrlConstants.DOUBLE_TEST_TOL;
      b.n.z += (rand.nextDouble() - 0.5) * GrlConstants.DOUBLE_TEST_TOL;

      // change scaling
      double scale = rand.nextGaussian() * 2;
      b.n.x *= scale;
      b.n.y *= scale;
      b.n.z *= scale;

      assertTrue(UtilPlane3D_F64.equals(a, b, GrlConstants.DOUBLE_TEST_TOL * 50));

      b.p.x += (rand.nextDouble() - 0.5);
      b.p.y += (rand.nextDouble() - 0.5);
      b.p.z += (rand.nextDouble() - 0.5);
      b.n.x += (rand.nextDouble() - 0.5);
      b.n.y += (rand.nextDouble() - 0.5);
      b.n.z += (rand.nextDouble() - 0.5);

      assertFalse(UtilPlane3D_F64.equals(a, b, GrlConstants.DOUBLE_TEST_TOL * 50));
    }
  }
  @Test
  public void evaluate_normal() {
    PlaneNormal3D_F64 input = new PlaneNormal3D_F64();
    input.n.set(1, 2, 3);
    input.n.normalize();
    input.p.set(-2, 3, 5);

    List<Point3D_F64> points = randPointOnPlane(input, 10);

    for (Point3D_F64 p : points) {
      double found = UtilPlane3D_F64.evaluate(input, p);
      assertEquals(0, found, GrlConstants.DOUBLE_TEST_TOL);
    }
  }