Example #1
0
 public Winding(List<Vector3f> verts) {
   this.verts = Collections.unmodifiableList(verts);
 }
Example #2
0
/**
 * Winding utility class.
 *
 * <p><i>"Not wind like the air, but wind like a watch"</i>
 *
 * <p>Original class name: unmap.Wind Original author: Rof
 *
 * @author Nico Bergemann <barracuda415 at yahoo.de>
 */
public class Winding implements List<Vector3f> {

  private static final Winding EMPTY =
      new Winding(Collections.unmodifiableList(new ArrayList<Vector3f>()));

  public static final int MAX_LEN = 56756; // sqrt(3)*32768
  public static final int MAX_COORD = 32768;
  public static final int SIDE_FRONT = 0;
  public static final int SIDE_BACK = 1;
  public static final int SIDE_ON = 2;

  // epsilon values
  public static final float EPS_SPLIT = 0.01f;
  public static final float EPS_COMP = 0.5f;
  public static final float EPS_DEGEN = 0.1f;

  // list of vectors to vertex points
  private final List<Vector3f> verts;

  public Winding(Winding that) {
    this.verts = that.verts;
  }

  public Winding(List<Vector3f> verts) {
    this.verts = Collections.unmodifiableList(verts);
  }

  /**
   * Clips this winding to a plane defined by a normal and distance, removing all vertices in front
   * or behind it.
   *
   * <p>Equals ClipWindingEpsilon() in polylib.cpp
   *
   * @param normal plane normal
   * @param dist plane distance to origin
   * @param eps clipping epsilon
   * @param back keep vertices behind the plane?
   */
  public Winding clipEpsilon(Vector3f normal, float dist, float eps, boolean back) {
    // counts number of front, back and on vertices
    int[] counts = new int[] {0, 0, 0};
    final int size = verts.size();
    float[] dists = new float[size + 1];
    int[] sides = new int[size + 1];

    // determine sides for each point
    for (int i = 0; i < size; i++) {
      // distance along norm-dirn from origin to vertex
      float dot = verts.get(i).dot(normal);

      // distance along norm-dirn from clip plane to vertex
      dot -= dist;

      // store it
      dists[i] = dot;

      if (dot > eps) {
        // vertex in front of plane
        sides[i] = SIDE_FRONT;
      } else if (dot < -eps) {
        // vertex behind plane
        sides[i] = SIDE_BACK;
      } else {
        // vertex on plane (within epsilon)
        sides[i] = SIDE_ON;
      }

      // count relative vertex positions
      counts[sides[i]]++;
    }

    sides[size] = sides[0]; // loop around to 0'th
    dists[size] = dists[0];

    if (counts[SIDE_FRONT] == 0) {
      // no vertices in front - all behind clip plane
      if (!back) {
        return EMPTY;
      } else {
        return this;
      }
    }
    if (counts[SIDE_BACK] == 0) {
      // no vertices in back - all in front of clip plane
      if (back) {
        return EMPTY;
      } else {
        return this;
      }
    }

    List<Vector3f> vertsNew = new ArrayList<Vector3f>();

    for (int i = 0; i < size; i++) {
      // get i'th vertex
      Vector3f p1 = verts.get(i);

      if (sides[i] == SIDE_ON) {
        vertsNew.add(p1);
        continue;
      }

      if (sides[i] == SIDE_FRONT && !back) {
        // add copy the current vertex
        vertsNew.add(p1);
      }

      if (sides[i] == SIDE_BACK && back) {
        // add copy the current vertex
        vertsNew.add(p1);
      }

      if (sides[i + 1] == SIDE_ON) {
        // next vertex is on the plane, so go to next vertex stat
        continue;
      }

      if (sides[i + 1] == sides[i]) {
        // next vertex does not change side, so go to next vertex stat
        continue;
      }

      // otherwise, we are crossing the clip plane between this vertex and the next
      // so generate a split point

      // will contain the next vertex position
      Vector3f p2;

      if (i == size - 1) {
        // we're the last vertex in the winding
        // next vertex is the 0'th one
        p2 = verts.get(0);
      } else {
        // else get the next vertex
        p2 = verts.get(i + 1);
      }

      // dot is fractional position of clip plane between
      // this vertex and the next
      float dot = dists[i] / (dists[i] - dists[i + 1]);

      // vector of the split vertex
      Vector3f mv = Vector3f.NULL;

      for (int j = 0; j < normal.size; j++) {
        // avoid round off error when possible
        if (normal.get(j) == 1) {
          mv = mv.set(j, dist);
        } else if (normal.get(j) == -1) {
          mv = mv.set(j, -dist);
        } else {
          // check it! MSH
          mv = mv.set(j, p1.get(j) + dot * (p2.get(j) - p1.get(j)));
        }
      }

      // write the output vertex
      vertsNew.add(mv);
    }

    return new Winding(vertsNew);
  }

  /**
   * Clips this winding to a plane and removes all vertices behind or in front of it.
   *
   * @param pl plane to clip to
   * @param back keep vertices behind the plane?
   */
  public Winding clipPlane(DPlane pl, boolean back) {
    return clipEpsilon(pl.normal, pl.dist, EPS_SPLIT, back);
  }

  /**
   * Removes degenerated vertices from this winding. A vertex is degenerated when its distance to
   * the previous vertex is smaller than {@link EPS_DEGEN}.
   *
   * @return number of removed vertices
   */
  public Winding removeDegenerated() {
    if (verts.isEmpty()) {
      return this;
    }

    ArrayList<Vector3f> vertsNew = new ArrayList<>();

    final int size = verts.size();

    for (int i = 0; i < size; i++) {
      int j = (i + 1) % size;
      Vector3f v1 = verts.get(i);
      Vector3f v2 = verts.get(j);

      if (v1.sub(v2).length() > EPS_DEGEN) {
        vertsNew.add(v1);
      }
    }

    return new Winding(vertsNew);
  }

  /**
   * Removes collinear vertices from this winding.
   *
   * @return number of removed vertices
   */
  public Winding removeCollinear() {
    if (verts.isEmpty()) {
      return this;
    }

    ArrayList<Vector3f> vertsNew = new ArrayList<>();

    final int size = verts.size();

    for (int i = 0; i < size; i++) {
      int j = (i + 1) % size;
      int k = (i + size - 1) % size;
      Vector3f v1 = verts.get(j).sub(verts.get(i)).normalize();
      Vector3f v2 = verts.get(i).sub(verts.get(k)).normalize();

      if (v1.dot(v2) < 0.999) {
        vertsNew.add(verts.get(i));
      }
    }

    return new Winding(vertsNew);
  }

  /**
   * Rotates all vertices in this winding by the given euler angles.
   *
   * @param angles rotation angles
   */
  public Winding rotate(Vector3f angles) {
    if (verts.isEmpty()) {
      return this;
    }

    ArrayList<Vector3f> vertsNew = new ArrayList<>();

    for (Vector3f vert : verts) {
      vertsNew.add(vert.rotate(angles));
    }

    return new Winding(vertsNew);
  }

  public Winding translate(Vector3f offset) {
    if (verts.isEmpty()) {
      return this;
    }

    ArrayList<Vector3f> vertsNew = new ArrayList<>();

    for (Vector3f vert : verts) {
      vertsNew.add(vert.add(offset));
    }

    return new Winding(vertsNew);
  }

  public Winding addBackface() {
    if (verts.isEmpty()) {
      return this;
    }

    List<Vector3f> vertsNew = new ArrayList<>();

    final int size = verts.size();

    for (int i = 0; i < size; i++) {
      if (i != 0) {
        vertsNew.add(verts.get(i));
      }
      if (i != size) {
        vertsNew.add(verts.get(i));
      }
    }

    return new Winding(vertsNew);
  }

  /**
   * Returns true if the winding still has one of the points from basewinding for plane.
   *
   * <p>Equals WindingIsHuge() from brushbsp.cpp
   *
   * @return true if winding is huge
   */
  public boolean isHuge() {
    for (Vector3f point : this) {
      for (float value : point) {
        if (Math.abs(value) > MAX_COORD) {
          return true;
        }
      }
    }

    return false;
  }

  /**
   * Compare two windings, taking into account that start points may not match
   *
   * @param that other winding
   * @return true if it matches this winding
   */
  public boolean matches(Winding that) {
    final int size = verts.size();

    // if windings have different number of points, trivially fail
    if (size != that.verts.size()) {
      return false;
    }

    // minimum match distance
    float min = 1e6f;

    for (int i = 0; i < size; i++) {
      float mdist = 0;

      // get the aggregate distance at offset i
      for (int j = 0; j < size; j++) {
        // wrap index if greater than size
        int k = (j + i) % size;

        // distance between vertex j of this and k of that
        mdist += verts.get(j).sub(that.verts.get(k)).length();
      }

      // update minimum match distance
      min = Math.min(min, mdist);
    }

    // check if match was close enough
    return min < EPS_COMP;
  }

  /**
   * Checks if a point is inside this winding.
   *
   * @param pt point to test
   * @return true if the point lies inside this winding
   */
  public boolean isInside(Vector3f pt) {
    if (isEmpty() || size() < 2) {
      // "Is not possible!"
      return false;
    }

    // get the first normal to test
    Vector3f toPt = pt.sub(get(0));
    Vector3f edge = get(1).sub(get(0));
    Vector3f testCross = edge.cross(toPt).normalize();
    Vector3f cross;

    int size = size();

    for (int i = 1; i < size; i++) {
      toPt = pt.sub(get(i));
      edge = get((i + 1) % size).sub(get(i));
      cross = edge.cross(toPt).normalize();

      if (cross.dot(testCross) < 0) {
        return false;
      }
    }

    return true;
  }

  public AABB getBounds() {
    Vector3f mins = Vector3f.MAX_VALUE;
    Vector3f maxs = Vector3f.MIN_VALUE;

    for (Vector3f vert : verts) {
      mins = mins.min(vert);
      maxs = maxs.max(vert);
    }

    return new AABB(mins, maxs);
  }

  /**
   * Returns the center point (barycenter) of this winding.
   *
   * <p>Equals WindingCenter() in polylib.cpp
   *
   * @return
   */
  public Vector3f getCenter() {
    Vector3f sum = Vector3f.NULL;

    // add all verts
    for (Vector3f vert : verts) {
      sum = sum.add(vert);
    }

    // average vertex position
    return sum.scalar(1f / verts.size());
  }

  /**
   * Returns the plane points of this winding in form of a triangle.
   *
   * @return Vector3f array with three points of the triangle
   */
  public Vector3f[] buildPlane() {
    Vector3f[] vertsNew = new Vector3f[verts.size()];
    Vector3f[] plane = new Vector3f[3];

    // 1st vert is always base vertex
    plane[0] = get(0);

    // build vector list
    for (int i = 0; i < vertsNew.length; i++) {
      // the vector from start vertex to i'th
      vertsNew[i] = get(i).sub(plane[0]);
    }

    // the largest modulus of cross product found between ixj
    float maxmcp = -1;
    // the i index of largest cp
    int imax = -1;
    // the j index of largest cp
    int jmax = -1;

    // loop through all i x j combinations
    for (int i = 1; i < vertsNew.length; i++) {
      // ensures j>i
      for (int j = i + 1; j < vertsNew.length; j++) {
        float mcp = vertsNew[i].cross(vertsNew[j]).length();
        if (mcp > maxmcp) {
          maxmcp = mcp;
          imax = i;
          jmax = j;
        }
      }
    }

    // choose other two such that cross product is maximum
    plane[1] = get(imax);
    plane[2] = get(jmax);

    return plane;
  }

  /**
   * Checks if this winding contains any duplicate vertices.
   *
   * @return true if this winding contains duplicate vertices
   */
  public boolean hasDuplicates() {
    final int size = verts.size();

    for (int i = 0; i < size; i++) {
      for (int j = 0; j < size; j++) {
        if (i == j) {
          continue;
        }

        Vector3f v1 = verts.get(i);
        Vector3f v2 = verts.get(j);

        if (v1.equals(v2)) {
          return true;
        }
      }
    }

    return false;
  }

  /**
   * Returns the total area of this winding.
   *
   * @return total area
   */
  public float getArea() {
    float total = 0;
    final int size = verts.size();

    for (int i = 2; i < size; i++) {
      Vector3f v1 = verts.get(i - 1).sub(verts.get(0));
      Vector3f v2 = verts.get(i).sub(verts.get(0));
      total += v1.cross(v2).length();
    }

    return total * 0.5f;
  }

  @Override
  public int size() {
    return verts.size();
  }

  @Override
  public boolean isEmpty() {
    return verts.isEmpty();
  }

  @Override
  public boolean contains(Object o) {
    return verts.contains(o);
  }

  @Override
  public Iterator<Vector3f> iterator() {
    return verts.iterator();
  }

  @Override
  public Object[] toArray() {
    return verts.toArray();
  }

  @Override
  public <T> T[] toArray(T[] a) {
    return verts.toArray(a);
  }

  @Override
  public boolean add(Vector3f e) {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean remove(Object o) {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean containsAll(Collection<?> c) {
    return verts.containsAll(c);
  }

  @Override
  public boolean addAll(Collection<? extends Vector3f> c) {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean addAll(int index, Collection<? extends Vector3f> c) {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean removeAll(Collection<?> c) {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean retainAll(Collection<?> c) {
    throw new UnsupportedOperationException();
  }

  @Override
  public void clear() {
    throw new UnsupportedOperationException();
  }

  @Override
  public Vector3f get(int index) {
    return verts.get(index);
  }

  @Override
  public Vector3f set(int index, Vector3f element) {
    throw new UnsupportedOperationException();
  }

  @Override
  public void add(int index, Vector3f element) {
    throw new UnsupportedOperationException();
  }

  @Override
  public Vector3f remove(int index) {
    throw new UnsupportedOperationException();
  }

  @Override
  public int indexOf(Object o) {
    return verts.indexOf(o);
  }

  @Override
  public int lastIndexOf(Object o) {
    return verts.lastIndexOf(o);
  }

  @Override
  public ListIterator<Vector3f> listIterator() {
    return verts.listIterator();
  }

  @Override
  public ListIterator<Vector3f> listIterator(int index) {
    return verts.listIterator(index);
  }

  @Override
  public List<Vector3f> subList(int fromIndex, int toIndex) {
    return verts.subList(fromIndex, toIndex);
  }

  @Override
  public String toString() {
    return verts.toString();
  }

  @Override
  public boolean equals(Object obj) {
    if (obj == null) {
      return false;
    }
    if (getClass() != obj.getClass()) {
      return false;
    }
    final Winding other = (Winding) obj;
    if (this.verts != other.verts && (this.verts == null || !this.verts.equals(other.verts))) {
      return false;
    }
    return true;
  }

  @Override
  public int hashCode() {
    int hash = 5;
    hash = 37 * hash + (this.verts != null ? this.verts.hashCode() : 0);
    return hash;
  }
}