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 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; }
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); }
/** * 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); }
/** * 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; }
/** * 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); }
/** * 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); }
@Override public void write(DataWriter out) throws IOException { Vector3f.write(out, startPos); out.writeInt(dispVertStart); out.writeInt(dispTriStart); out.writeInt(power); out.writeFloat(smoothingAngle); out.writeInt(unknown); out.writeInt(contents); out.writeUnsignedShort(mapFace); out.writeInt(lightmapAlphaStart); out.writeInt(lightmapSamplePositionStart); out.writeBytes(neighborsVin); for (int i = 0; i < allowedVerts.length; i++) { out.writeInt(allowedVerts[i]); } }
@Override public void read(DataReader in) throws IOException { startPos = Vector3f.read(in); dispVertStart = in.readInt(); dispTriStart = in.readInt(); power = in.readInt(); smoothingAngle = in.readFloat(); unknown = in.readInt(); contents = in.readInt(); mapFace = in.readUnsignedShort(); lightmapAlphaStart = in.readInt(); lightmapSamplePositionStart = in.readInt(); in.readBytes(neighborsVin); for (int i = 0; i < allowedVerts.length; i++) { allowedVerts[i] = in.readInt(); } }
/** * 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); }