All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.lacuna.artifex.Curve2 Maven / Gradle / Ivy

package io.lacuna.artifex;

import io.lacuna.artifex.utils.Intersections;

import java.util.Arrays;

import static io.lacuna.artifex.Box.box;
import static io.lacuna.artifex.Vec2.cross;
import static io.lacuna.artifex.utils.Scalars.EPSILON;
import static java.lang.Math.abs;

/**
 * @author ztellman
 */
public interface Curve2 {

  /**
   * @param t a parametric point on the curve, not necessarily within [0, 1]
   * @return the interpolated position on the curve
   */
  Vec2 position(double t);

  /**
   * Given a ring of curves, the sum of area() will be the are enclosed by that ring. For clockwise rings, the sum will
   * be negative, for counter-clockwise rings it will be positive.
   *
   * @return the signed area of the curve
   */
  double signedArea();

  double length();

  default Vec2 start() {
    return position(0);
  }

  default Vec2 end() {
    return position(1);
  }

  /**
   * @return an updated curve with the specified endpoints.
   */
  Curve2 endpoints(Vec2 start, Vec2 end);

  /**
   * @param t a parametric point on the curve, not necessarily within [0, 1]
   * @return the tangent at the interpolated position on the curve, which is not normalized
   */
  Vec2 direction(double t);

  /**
   * @param t a parametric point within the curve, which must be within (0, 1)
   * @return an array representing the lower and upper regions of the curve, split at {@code t}
   */
  Curve2[] split(double t);

  boolean isFlat(double epsilon);

  /**
   * @param interval the parametric range
   * @return the curve within [interval.lo, interval.hi]
   */
  default Curve2 range(Interval interval) {
    return range(interval.lo, interval.hi);
  }

  /**
   * @param tMin the lower parametric bound
   * @param tMax the upper parametric bound
   * @return the curve within [tMin, tMax]
   */
  default Curve2 range(double tMin, double tMax) {
    if (tMin == tMax) {
      throw new IllegalArgumentException("range must be non-zero");
    } else if (tMax < tMin) {
      throw new IllegalArgumentException("tMin must be less than tMax");
    }

    Curve2 c;
    if (tMin == 0 && tMax == 1) {
      return this;
    } else if (tMin == 0) {
      return split(tMax)[0];
    } else if (tMax == 1) {
      return split(tMin)[1];
    } else {
      return split(tMin)[1].split((tMax - tMin) / (1 - tMin))[0].endpoints(position(tMin), position(tMax));
    }
  }

  /**
   * @param unsafeTs an array of parametric split points
   * @return an array of curves, split at the specified points.
   */
  default Curve2[] split(double[] unsafeTs) {

    double previous = Double.POSITIVE_INFINITY;
    int tCount = 0;
    for (int i = 0; i < unsafeTs.length; ++i) {
      if (unsafeTs[i] != previous) {
        tCount++;
      }
      previous = unsafeTs[i];
    }
    double[] ts = new double[tCount];
    tCount = 0;
    previous = Double.POSITIVE_INFINITY;
    for (int i = 0; i < unsafeTs.length; ++i) {
      if (unsafeTs[i] != previous) {
        ts[tCount] = unsafeTs[i];
        tCount++;
      }
      previous = unsafeTs[i];
    }

    if (ts.length == 0) {
      return new Curve2[] {this};
    }

    ts = ts.clone();
    Arrays.sort(ts);

    int offset = ts[0] == 0 ? 1 : 0;
    int len = (ts.length - offset) - (ts[ts.length - 1] == 1 ? 1 : 0);
    System.arraycopy(ts, offset, ts, 0, len);

    if (len == 0) {
      return new Curve2[] {this};
    } else if (len == 1) {
      return split(ts[0]);
    }

    Curve2[] result = new Curve2[len + 1];

    result[0] = range(0, ts[0]);
    for (int i = 0; i < len - 1; i++) {
      result[i + 1] = range(ts[i], ts[i + 1]);
    }
    result[len] = range(ts[len - 1], 1);

    return result;
  }

  /**
   * @param p a point in 2D space
   * @return the {@code t} parameter representing the closest point on the curve, not necessarily within [0,1]
   */
  double nearestPoint(Vec2 p);

  default Box2 bounds() {
    Box2 bounds = box(start(), end());
    for (double t : inflections()) {
      bounds = bounds.union(position(t));
    }
    return bounds;
  }

  Vec2[] subdivide(double error);

  Curve2 transform(Matrix3 m);

  Curve2 reverse();

  double[] inflections();

  default Vec2[] intersections(Curve2 c) {
    return Intersections.intersections(this, c);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy