/* ** 2013 June 28 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. */ package info.ata4.bspsrc.util; import info.ata4.bsplib.struct.BspData; import info.ata4.bsplib.struct.DAreaportal; import info.ata4.bsplib.struct.DBrush; import info.ata4.bsplib.struct.DBrushSide; import info.ata4.bsplib.struct.DFace; import info.ata4.bsplib.struct.DOccluderPolyData; import info.ata4.bsplib.struct.DPlane; import info.ata4.bsplib.vector.Vector3f; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Factory methods for winding objects. * * @author Nico Bergemann <barracuda415 at yahoo.de> */ public class WindingFactory { private static Map<DFace, Winding> faceCache = new HashMap<>(); private static Map<Integer, Winding> brushSideCache = new HashMap<>(); private static Map<DAreaportal, Winding> areaportalCache = new HashMap<>(); private static Map<DOccluderPolyData, Winding> occluderCache = new HashMap<>(); private static Map<DPlane, Winding> planeCache = new HashMap<>(); private WindingFactory() { } public static void clearCache() { faceCache.clear(); brushSideCache.clear(); areaportalCache.clear(); occluderCache.clear(); planeCache.clear(); } /** * Constructs a winding from face vertices * * @param bsp BSP data * @param face Face * @param both if true, wind in both directions * @return Winding for the face */ public static Winding fromFace(BspData bsp, DFace face) { if (faceCache.containsKey(face)) { return faceCache.get(face); } List<Vector3f> verts = new ArrayList<>(); for (int i = 0; i < face.numedge; i++) { int v; int sedge = bsp.surfEdges.get(face.fstedge + i); if (sedge < 0) { // backwards wound edge v = bsp.edges.get(-sedge).v[1]; } else { // forwards wound edge v = bsp.edges.get(sedge).v[0]; } verts.add(bsp.verts.get(v).point); } Winding w = new Winding(verts); faceCache.put(face, w); return w; } /** * Constructs a winding from a brush, for a brush side * * Equals the brush side part of CreateBrushWindings() in brushbsp.cpp * * @param bsp BSP data * @param brush Brush * @param bside Brush side * @return Winding for the brush side */ public static Winding fromSide(BspData bsp, DBrush brush, DBrushSide bside) { int cacheHash = 5; cacheHash = cacheHash * 14 + brush.hashCode(); cacheHash = cacheHash * 8 + bside.hashCode(); if (brushSideCache.containsKey(cacheHash)) { return brushSideCache.get(cacheHash); } int iplane = bside.pnum; boolean hasSide = false; Winding w = fromPlane(bsp.planes.get(iplane)); // clip to all other planes for (int i = 0; i < brush.numside; i++) { int ibside2 = brush.fstside + i; DBrushSide bside2 = bsp.brushSides.get(ibside2); // don't clip plane to itself if (bside2 == bside) { hasSide = true; continue; } // don't clip to bevel planes if (bside2.bevel) { continue; } // remove everything behind the plane int iplane2 = bside2.pnum; DPlane plane = bsp.planes.get(iplane2); DPlane flipPlane = new DPlane(); flipPlane.normal = plane.normal.scalar(-1); flipPlane.dist = -plane.dist; w = w.clipPlane(flipPlane, false); } if (!hasSide) { throw new IllegalArgumentException("Brush side is not part of brush!"); } brushSideCache.put(cacheHash, w); // return the clipped winding return w; } /** * Constructs a winding from a brush, for a brush side * * Equals the brush side part of CreateBrushWindings() in brushbsp.cpp * * @param bsp BSP data * @param brush Brush * @param side Brush side ID * @return Winding for the brush side */ public static Winding fromSide(BspData bsp, DBrush brush, int side) { int ibside = brush.fstside + side; DBrushSide bside = bsp.brushSides.get(ibside); return fromSide(bsp, brush, bside); } public static Winding fromAreaportal(BspData bsp, DAreaportal ap) { if (areaportalCache.containsKey(ap)) { return areaportalCache.get(ap); } List<Vector3f> verts = new ArrayList<>(); for (int i = 0; i < ap.clipPortalVerts; i++) { int pvi = ap.firstClipPortalVert + i; verts.add(bsp.clipPortalVerts.get(pvi).point); } Winding w = new Winding(verts); areaportalCache.put(ap, w); return w; } /** * Constructs a winding from occluder vertices * * @param bsp BSP data * @param opd Occluder polygon data * @return Winding for the occluder */ public static Winding fromOccluder(BspData bsp, DOccluderPolyData opd) { if (occluderCache.containsKey(opd)) { return occluderCache.get(opd); } List<Vector3f> verts = new ArrayList<>(); for (int k = 0; k < opd.vertexcount; k++) { int pvi = bsp.occluderVerts.get(opd.firstvertexindex + k); verts.add(bsp.verts.get(pvi).point); } Winding w = new Winding(verts); occluderCache.put(opd, w); return w; } /** * Constructs a huge square winding from a plane * * Equals BaseWindingForPlane() in polylib.cpp * * @param pl plane */ public static Winding fromPlane(DPlane pl) { if (planeCache.containsKey(pl)) { return planeCache.get(pl); } // find the dominant axis of plane normal float dmax = -1.0F; int idir = -1; // for each axis for (int i = 0; i < pl.normal.size; i++) { float dc = Math.abs(pl.normal.get(i)); // find the biggest component if (dc <= dmax) { continue; } dmax = dc; idir = i; } // didn't find one (null or NaN'ed vector) if (idir == -1) { throw new RuntimeException("Plane " + pl + ": bad normal"); } // this will be the "upwards" pointing vector Vector3f vup = Vector3f.NULL; switch (idir) { case Winding.SIDE_FRONT: case Winding.SIDE_BACK: // use z unit vector vup = new Vector3f(0, 0, 1); break; case Winding.SIDE_ON: // use x unit vector vup = new Vector3f(1, 0, 0); } // remove the component of this vector along the normal float vdot = vup.dot(pl.normal); vup = vup.add(pl.normal.scalar(-vdot)); // make it a unit (perpendicular) vup = vup.normalize(); // the vector from origin perpendicularly touching plane Vector3f org = pl.normal.scalar(pl.dist); // this is the "rightwards" pointing vector Vector3f vrt = vup.cross(pl.normal); vup = vup.scalar(Winding.MAX_LEN); vrt = vrt.scalar(Winding.MAX_LEN); List<Vector3f> verts = new ArrayList<>(); // move diagonally away from org to create the corner verts verts.add(org.sub(vrt).add(vup)); // left up verts.add(org.add(vrt).add(vup)); // right up verts.add(org.add(vrt).sub(vup)); // right down verts.add(org.sub(vrt).sub(vup)); // left down Winding w = new Winding(verts); planeCache.put(pl, w); return w; } }