/* * pMap.java * * (C) 2005, Alex S. */ import java.util.*; /** * general purpose map object (code to build bsp tree, and store map info) */ class pMap { public Vector brushes = new Vector(); public pBSPNode bsp = null; /** * build bsp tree */ public void buildBSP(){ Vector faces = new Vector(); // non-bsp renderer Enumeration enum = brushes.elements(); while (enum.hasMoreElements()) { pBrush brush = (pBrush)enum.nextElement(); for (int f=0;f d) { mindist = d; minindex = i; } } face = (pFace)faces.elementAt(minindex); faces.removeElementAt(minindex); return face; } /** * build the bsp tree */ public static pBSPNode buildBSP(Vector faces){ if (faces.size() == 0) return new pBSPNode(pBSPNode.LEAFNODE); pBSPNode node = new pBSPNode(); Vector front = new Vector(); Vector back = new Vector(); // pick a plane pFace curr = pickFace(faces); pPlane plane = curr.plane; node.plane = plane; node.faces.addElement(curr); Enumeration enum = faces.elements(); while (enum.hasMoreElements()) { pFace face = (pFace)enum.nextElement(); int e = plane.eval(face); if (e == plane.FRONT) { front.addElement(face); } else if (e == plane.BACK) { back.addElement(face); } else if (e == plane.INCID) { node.faces.addElement(face); } else { //cut pFace f = new pFace(); pFace b = new pFace(); if (plane.cut(face,f,b)) { front.addElement(f); back.addElement(b); } } } node.front = buildBSP(front); node.back = buildBSP(back); return node; } /* * the below collision stuff is mostly lifted from * Nathan Ostgard's (nostgard@lvcm.com) wonderful * tutorial ``Quake 3 BSP Collision Detection'' * http://www.devmaster.net/articles/quake3collision/ */ public static final double EPSILON = 1.0 / 32.0; public double outputFraction = 0; public double[] outputEnd = {0,0,0,1}; public boolean outputStartsOut = false; public boolean outputAllSolid = false; public pPlane outputPlane = new pPlane(); public int traceType = 0; public static final int TT_RAY = 0; public static final int TT_SPHERE = 1; public static final int TT_BOX = 2; // params for trace types double traceRadius = 0; double[] traceMins = {0,0,0,1}; double[] traceMaxs = {0,0,0,1}; double[] traceExtents = {0,0,0,1}; public void traceRay(double[] inputStart, double[] inputEnd){ traceType = TT_RAY; trace( inputStart, inputEnd ); } public void traceSphere(double[] inputStart,double[] inputEnd,double inputRadius){ traceType = TT_SPHERE; traceRadius = inputRadius; trace( inputStart, inputEnd ); } public void TraceBox(double[] inputStart,double[] inputEnd,double[] inputMins,double[] inputMaxs){ if (inputMins[0] == 0 && inputMins[1] == 0 && inputMins[2] == 0 && inputMaxs[0] == 0 && inputMaxs[1] == 0 && inputMaxs[2] == 0){ // the user traceBox, but this is actually a ray traceRay( inputStart, inputEnd ); } else { // setup for a box traceType = TT_BOX; traceMins = inputMins; traceMaxs = inputMaxs; traceExtents[0] = -traceMins[0] > traceMaxs[0] ? -traceMins[0] : traceMaxs[0]; traceExtents[1] = -traceMins[1] > traceMaxs[1] ? -traceMins[1] : traceMaxs[1]; traceExtents[2] = -traceMins[2] > traceMaxs[2] ? -traceMins[2] : traceMaxs[2]; trace( inputStart, inputEnd ); } } /** * traces a thing */ public void trace(double[] inputStart, double[] inputEnd ) { outputStartsOut = true; outputAllSolid = false; outputFraction = 1.0f; // walk through the BSP tree checkNode( bsp, 0.0f, 1.0f, inputStart, inputEnd ); if (outputFraction == 1.0f){ // nothing blocked the trace outputEnd = inputEnd; } else { // collided with something for (int i = 0; i < 3; i++) { outputEnd[i] = inputStart[i] + outputFraction * (inputEnd[i] - inputStart[i]); } } } /** * checks a node */ public void checkNode(pBSPNode node, double startFraction, double endFraction,double[] start,double[] end ){ if (node.type == pBSPNode.LEAFNODE){ // this is a leaf for (int i = 0; i < node.brushes.size(); i++){ pBrush brush = (pBrush)node.brushes.elementAt(i); if (brush.type == pBrush.SOLID){ checkBrush( brush, start, end ); } } // don't have to do anything else for leaves return; } // this is a node pPlane plane = node.plane; double startDistance = 0, endDistance = 0, offset = 0; //startDistance = DotProduct( start, plane->normal ) - plane->distance; //endDistance = DotProduct( end, plane->normal ) - plane->distance; startDistance = plane.eval( start ); endDistance = plane.eval( end ); if (traceType == TT_RAY) { offset = 0; } else if (traceType == TT_SPHERE) { offset = traceRadius; } else if (traceType == TT_BOX) { // this is just a dot product, but we want the absolute values offset = (double)(Math.abs( traceExtents[0] * plane.p[0] ) + Math.abs( traceExtents[1] * plane.p[1] ) + Math.abs( traceExtents[2] * plane.p[2] ) ); } if (startDistance >= offset && endDistance >= offset){ // both points are in front of the plane // so check the front child checkNode( node.front, startFraction, endFraction, start, end ); } else if (startDistance < -offset && endDistance < -offset) { // both points are behind the plane // so check the back child checkNode( node.back, startFraction, endFraction, start, end ); } else { // the line spans the splitting plane int side; double fraction1, fraction2, middleFraction; double[] middle = {0,0,0,1}; // split the segment into two if (startDistance < endDistance) { side = 1; // back double inverseDistance = 1.0f / (startDistance - endDistance); fraction1 = (startDistance - offset + EPSILON) * inverseDistance; fraction2 = (startDistance + offset + EPSILON) * inverseDistance; } else if (endDistance < startDistance) { side = 0; // front double inverseDistance = 1.0f / (startDistance - endDistance); fraction1 = (startDistance + offset + EPSILON) * inverseDistance; fraction2 = (startDistance - offset - EPSILON) * inverseDistance; } else { side = 0; // front fraction1 = 1.0f; fraction2 = 0.0f; } // make sure the numbers are valid if (fraction1 < 0.0f) fraction1 = 0.0f; else if (fraction1 > 1.0f) fraction1 = 1.0f; if (fraction2 < 0.0f) fraction2 = 0.0f; else if (fraction2 > 1.0f) fraction2 = 1.0f; // calculate the middle point for the first side middleFraction = startFraction + (endFraction - startFraction) * fraction1; for (int i = 0; i < 3; i++) middle[i] = start[i] + fraction1 * (end[i] - start[i]); // check the first side if(side == 1){ checkNode( node.back, startFraction, middleFraction, start, middle ); }else if(side == 0){ checkNode( node.front, startFraction, middleFraction, start, middle ); } // calculate the middle point for the second side middleFraction = startFraction + (endFraction - startFraction) * fraction2; for (int i = 0; i < 3; i++) middle[i] = start[i] + fraction2 * (end[i] - start[i]); // check the second side if(side == 1){ checkNode( node.front, middleFraction, endFraction, middle, end ); }else if(side == 0){ checkNode( node.back, middleFraction, endFraction, middle, end ); } } } /** * checks brush */ public void checkBrush(pBrush brush, double[] inputStart, double[] inputEnd ) { double startFraction = -1.0f; double endFraction = 1.0f; boolean startsOut = false; boolean endsOut = false; pPlane hitPlane = outputPlane; for (int i = 0; i < brush.numFaces; i++) { pFace brushside = brush.faces[i]; pPlane plane = brushside.plane; double startDistance = 0, endDistance = 0; if (traceType == TT_RAY) { startDistance = plane.eval(inputStart); endDistance = plane.eval(inputEnd); } else if (traceType == TT_SPHERE) { startDistance = plane.eval( inputStart) - traceRadius; endDistance = plane.eval( inputEnd ) - traceRadius; } else if (traceType == TT_BOX) { double[] offset = {0,0,0,1}; for (int j = 0; j < 3; j++){ if (plane.p[j] < 0) offset[j] = traceMaxs[j]; else offset[j] = traceMins[j]; } startDistance = (inputStart[0] + offset[0]) * plane.p[0] + (inputStart[1] + offset[1]) * plane.p[1] + (inputStart[2] + offset[2]) * plane.p[2] + plane.p[3]; endDistance = (inputEnd[0] + offset[0]) * plane.p[0] + (inputEnd[1] + offset[1]) * plane.p[1] + (inputEnd[2] + offset[2]) * plane.p[2] + plane.p[3]; } if (startDistance > 0) startsOut = true; if (endDistance > 0) endsOut = true; // make sure the trace isn't completely on one side of the brush if (startDistance > 0 && endDistance > 0){ // both are in front of the plane, its outside of this brush return; } if (startDistance <= 0 && endDistance <= 0){ // both are behind this plane, it will get clipped by another one continue; } if (startDistance > endDistance){ // line is entering into the brush double fraction = (startDistance - EPSILON) / (startDistance - endDistance); if (fraction > startFraction){ startFraction = fraction; hitPlane = plane; } } else { // line is leaving the brush double fraction = (startDistance + EPSILON) / (startDistance - endDistance); if (fraction < endFraction){ endFraction = fraction; } } } if (startsOut == false){ outputStartsOut = false; if (endsOut == false) outputAllSolid = true; return; } // was there some collision? if (startFraction < endFraction){ if (startFraction > -1 && startFraction < outputFraction){ if (startFraction < 0) startFraction = 0; outputFraction = startFraction; outputPlane = hitPlane; } } } }