/**
   * draws a circle
   *
   * @param center
   * @param v1
   * @param v2
   * @param radius
   */
  public void circle(GgbVector center, GgbVector v1, GgbVector v2, double radius) {

    length = (float) (2 * Math.PI * radius); // TODO use integer to avoid bad dash cycle connection

    int longitude = 60;

    GgbVector vn1;
    GgbVector vn2 = v1.crossProduct(v2);

    float dt = (float) 1 / longitude;
    float da = (float) (2 * Math.PI * dt);
    float u = 0, v = 1;

    setTextureX(0);
    vn1 = (GgbVector) v1.mul(u).add(v2.mul(v));
    down((GgbVector) center.add(vn1.mul(radius)), vn1, vn2);

    for (int i = 1; i <= longitude; i++) {
      u = (float) Math.sin(i * da);
      v = (float) Math.cos(i * da);

      setTextureX(i * dt);
      vn1 = (GgbVector) v1.mul(u).add(v2.mul(v));
      moveTo((GgbVector) center.add(vn1.mul(radius)), vn1, vn2);
    }
  }
  /**
   * returns the point at position lambda on the coord sys in the dimension given
   *
   * @param dimension
   * @param lambda
   * @return the point at position lambda on the coord sys
   */
  public GgbVector getPointInD(int dimension, double lambda) {

    GgbVector v = getPoint(lambda);
    switch (dimension) {
      case 3:
        return v;
      case 2:
        return new GgbVector(v.getX(), v.getY(), v.getW());
      default:
        return null;
    }
  }
  public GgbVector evaluatePoint(double u, double v) {

    GeoPointND point = (GeoPointND) getGeoElement();

    double r = point.getPointSize() / getView3D().getScale() * 1.5;
    GgbVector n =
        new GgbVector(
            new double[] {
              Math.cos(u) * Math.cos(v) * r, Math.sin(u) * Math.cos(v) * r, Math.sin(v) * r
            });

    return (GgbVector) n.add(point.getInhomCoordsInD(3));
  }
  /**
   * adds the point with the specified position and tangent to the curve currently being drawn.
   *
   * @param p the point's position vector
   * @param t the tangent at the point
   */
  public void addPointToCurve3D(GgbVector3D p, GgbVector3D t) {
    GgbVector position = new GgbVector(p.getX(), p.getY(), p.getZ(), 0);
    GgbVector tangent = new GgbVector(t.getX(), t.getY(), t.getZ(), 0);
    if (firstCurvePoint) {
      end = new PlotterBrushSection(position, tangent, thickness);
      firstCurvePoint = false;
    } else {
      if (discontinuityPassed(position)) {
        startDrawingCurve(); // start drawing a new segment
        addPointToCurve(position, tangent);
        return;
      } else {
        start = end;
        end = new PlotterBrushSection(start, position, tangent, thickness);

        addCurvePos((float) position.sub(start.getCenter()).norm());
        join();
      }
    }
    previousPosition = position;
    previousTangent = tangent;
  }
  /**
   * segment curve
   *
   * @param p1
   * @param p2
   */
  public void segment(GgbVector p1, GgbVector p2) {

    length = (float) p1.distance(p2);
    if (Kernel.isEqual(length, 0, Kernel.STANDARD_PRECISION)) return;

    down(p1);

    switch (arrowType) {
      case ARROW_TYPE_NONE:
      default:
        setTextureX(0, 1);
        moveTo(p2);
        break;
      case ARROW_TYPE_SIMPLE:
        float factor = (12 + lineThickness) * LINE3D_THICKNESS / scale;
        float arrowPos = ARROW_LENGTH / length * factor;
        GgbVector arrowBase = (GgbVector) start.getCenter().mul(arrowPos).add(p2.mul(1 - arrowPos));

        setTextureX(0);
        if (hasTicks()) {
          GgbVector d = p2.sub(p1).normalized();
          float thickness = this.thickness;

          float i =
              ticksOffset * length - ((int) (ticksOffset * length / ticksDistance)) * ticksDistance;
          float ticksDelta = thickness;
          float ticksThickness = 4 * thickness;
          if (i <= ticksDelta) i += ticksDistance;

          for (; i <= length * (1 - arrowPos); i += ticksDistance) {

            GgbVector p1b = (GgbVector) p1.add(d.mul(i - ticksDelta));
            GgbVector p2b = (GgbVector) p1.add(d.mul(i + ticksDelta));

            setTextureType(TEXTURE_AFFINE);
            setTextureX(i / length);
            moveTo(p1b);
            setThickness(ticksThickness);
            setTextureType(TEXTURE_CONSTANT_0);
            moveTo(p1b);
            moveTo(p2b);
            setThickness(thickness);
            moveTo(p2b);
          }
        }

        setTextureType(TEXTURE_AFFINE);
        setTextureX(1 - arrowPos);
        moveTo(arrowBase);

        textureTypeX = TEXTURE_ID;
        setTextureX(0, 0);
        setThickness(factor * ARROW_WIDTH);
        moveTo(arrowBase);
        setThickness(0);
        moveTo(p2);
        break;
    }
  }
  public GgbVector getCartesianEquationVector(GgbMatrix m) {
    GgbVector origin = getCoordSys().getOrigin();
    GgbVector direction = getCoordSys().getVx();

    // TODO generalize it to other planes than xOy

    // if lines is not in the plane, return null
    if (!Kernel.isZero(origin.getZ()) || !Kernel.isZero(direction.getZ())) return null;

    double x = -direction.getY();
    double y = direction.getX();
    double z = -x * origin.getX() - y * origin.getY();

    return new GgbVector(x, y, z);
  }
 /** set the matrix to [V O] */
 public void setCoordFromPoints(GgbVector a_O, GgbVector a_I) {
   setCoord(a_O, a_I.sub(a_O));
 }
  public void pointChanged(GeoPointND P) {

    boolean done = false;

    // project P on line
    double t = 0;
    if (((GeoElement) P).isGeoElement3D()) {
      if (((GeoPoint3D) P).getWillingCoords() != null) {
        if (((GeoPoint3D) P).getWillingDirection() != null) {
          // project willing location using willing direction
          // GgbVector[] project = coordsys.getProjection(P.getWillingCoords(),
          // P.getWillingDirection());

          GgbVector[] project =
              ((GeoPoint3D) P)
                  .getWillingCoords()
                  .projectOnLineWithDirection(
                      coordsys.getOrigin(),
                      coordsys.getVx(),
                      ((GeoPoint3D) P).getWillingDirection());

          t = project[1].get(1);
          done = true;
        } else {
          // project current point coordinates
          // Application.debug("ici\n getWillingCoords=\n"+P.getWillingCoords()+"\n
          // matrix=\n"+getMatrix().toString());
          GgbVector preDirection =
              ((GeoPoint3D) P)
                  .getWillingCoords()
                  .sub(coordsys.getOrigin())
                  .crossProduct(coordsys.getVx());
          if (preDirection.equalsForKernel(0, Kernel.STANDARD_PRECISION))
            preDirection = coordsys.getVy();

          GgbVector[] project =
              ((GeoPoint3D) P)
                  .getWillingCoords()
                  .projectOnLineWithDirection(
                      coordsys.getOrigin(),
                      coordsys.getVx(),
                      preDirection.crossProduct(coordsys.getVx()));

          t = project[1].get(1);
          done = true;
        }
      }
    }

    if (!done) {
      // project current point coordinates
      // Application.debug("project current point coordinates");
      GgbVector preDirection =
          P.getCoordsInD(3).sub(coordsys.getOrigin()).crossProduct(coordsys.getVx());
      if (preDirection.equalsForKernel(0, Kernel.STANDARD_PRECISION))
        preDirection = coordsys.getVy();

      GgbVector[] project =
          P.getCoordsInD(3)
              .projectOnLineWithDirection(
                  coordsys.getOrigin(),
                  coordsys.getVx(),
                  preDirection.crossProduct(coordsys.getVx()));

      t = project[1].get(1);
    }

    if (t < getMinParameter()) t = getMinParameter();
    else if (t > getMaxParameter()) t = getMaxParameter();

    // set path parameter
    PathParameter pp = P.getPathParameter();

    pp.setT(t);

    // udpate point using pathChanged
    pathChanged(P);
  }
  /**
   * A test used to judge if the curve has passed over a discontinuity since the last point was
   * added.
   *
   * @param position the position of the new point (pos2)
   * @return true iff (pos2-pos1)/||pos2-pos1|| . tangent1 < CurveTree.discontinuityThreshold
   */
  private boolean discontinuityPassed(GgbVector position) {
    GgbVector dir = position.sub(previousPosition).normalized();

    if (dir.dotproduct(previousTangent) < CurveTree.discontinuityThreshold) return true;
    return false;
  }