public Winding(List<Vector3f> verts) { this.verts = Collections.unmodifiableList(verts); }
/** * 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; } }