/** * 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; }
/** * 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; }
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); }
/** * 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); }
/** * 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; }
/** * 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); }
/** * 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); }
@Override public Vector3f get(int index) { return verts.get(index); }