Package soc.qase.file.bsp

Source Code of soc.qase.file.bsp.BSPParser

//---------------------------------------------------------------------
// Name:      BSPParser.java
// Author:      Bernard.Gorman@computing.dcu.ie
//---------------------------------------------------------------------

package soc.qase.file.bsp;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.StringTokenizer;
import java.util.Vector;

import soc.qase.file.pak.PAKParser;
import soc.qase.tools.Utils;
import soc.qase.tools.vecmath.Vector3f;

/*-------------------------------------------------------------------*/
/**  A class which enables QASE to parse BSP files, which are used by
*  Quake 2 to store the geometry of its various maps. By constructing
*  an internal representation of the geometry from this data, the
*  BSPParser can perform collision detection on the environment, 
*  detecting and reporting the location of or distance to the nearest
*  solid obstacle in a given direction from a specified starting point. */
/*-------------------------------------------------------------------*/
public class BSPParser
{
  private byte[] lumpData = null;
  private byte[] headerData = null;
  private byte[] pakBSPFileData = null;

  private String fName = null;
  private File bspFile = null;

  private BufferedInputStream bufIn = null;
  private ByteArrayInputStream byteIn = null;

  private boolean mapRead = false;
  private boolean fileOpen = false;
  private boolean inPAKFile = false;

  private BSPLump[] bspLumps = null;
  private BSPHeader bspHeader = null;

  public BSPPlaneLump planeLump = null;
  public BSPVertexLump vertexLump = null;
  public BSPNodeLump nodeLump = null;
  public BSPLeafLump leafLump = null;
  public BSPLeafFaceLump leafFaceLump = null;
  public BSPFaceEdgeLump faceEdgeLump = null;
  public BSPEdgeLump edgeLump = null;
  public BSPModelLump modelLump = null;
  public BSPBrushLump brushLump = null;
  public BSPBrushSideLump brushSideLump = null;
  public BSPLeafBrushLump leafBrushLump = null;

  public BSPEntityLump entitiesLump = null;

  public static final float EPSILON = 0.03125f;
  public static final int TRACE_LINE = 0, TRACE_SPHERE = 1, TRACE_BOX = 2;

/*-------------------------------------------------------------------*/
/**  Default constructor. */
/*-------------------------------------------------------------------*/
  public BSPParser()
  {  }

/*-------------------------------------------------------------------*/
/**  Constructor. Allows a BSP file to be specified.
@param filename the filename of the BSP file from which to read */
/*-------------------------------------------------------------------*/
  public BSPParser(String filename)
  {
    load(filename);
  }

/*-------------------------------------------------------------------*/
/**  Load the specified BSP file.
@param filename the filename of the BSP file from which to read.
*  If the file is within a PAK archive, it can be loaded by using the
*  hash character to combine the path to the PAK file and the path to
*  the BSP file within it, as in C:/archive1.pak#maps/mymap.bsp
@return true if the load was successful, false otherwise */
/*-------------------------------------------------------------------*/
  public boolean load(String filename)
  {
    if(mapRead) // if the BSPParser already contains BSP data, reset it and load new data
      reset();

    // # in filename means PAK archive
    if(filename.indexOf('#') != -1)
    {
      StringTokenizer st = new StringTokenizer(filename, "#");
      pakBSPFileData = PAKParser.getFileFromPAK(st.nextToken(), st.nextToken());

      if(pakBSPFileData != null)
      {
        byteIn = new ByteArrayInputStream(pakBSPFileData);
        bufIn = new BufferedInputStream(byteIn);

        fName = filename;
        fileOpen = inPAKFile = true;
      }     
    }
    else
    {
      try
      {
        bspFile = new File(filename);
        bufIn = new BufferedInputStream(new FileInputStream(bspFile));

        fName = filename;
        fileOpen = true;
      }
      catch(IOException ioe)
      {  }
    }

    // parse the data and build BSP structures
    mapRead = (fileOpen && readBSPData());

    // close file handles and reset internal variables
    try { bufIn.close(); byteIn.close(); } catch(Exception ioe){}

    bufIn = null;
    byteIn = null;
    bspFile = null;

    fileOpen = inPAKFile = false;
    headerData = lumpData = pakBSPFileData = null;

    if(!mapRead) // if map reading was unsuccessful, reset all class variables
      reset();

    return mapRead;
  }

/*-------------------------------------------------------------------*/
/**  Check whether the specified BSP file has been loaded.
@return true if the map is loaded, false otherwise */
/*-------------------------------------------------------------------*/
  public boolean isMapLoaded()
  {
    return mapRead;
  }

/*-------------------------------------------------------------------*/
/**  Read the BSP data from the open file.
@return true if the data was successfully parsed, false otherwise*/
/*-------------------------------------------------------------------*/
  private boolean readBSPData()
  {
    return readBSPHeader() && readBSPLumps();
  }

/*-------------------------------------------------------------------*/
/**  Load the specified BSP file's header, which contains the full
*  directory listing of all lumps.
@return true if the header is read successfully, false otherwise */
/*-------------------------------------------------------------------*/
  private boolean readBSPHeader()
  {
    headerData = new byte[4 * 40]// 40 integer values
   
    try
    {
      bufIn.read(headerData);
      bspHeader = new BSPHeader(headerData);
    }
    catch(IOException ioe)
    {
      return false;
    }

    return bspHeader.validateBSP();
  }

/*-------------------------------------------------------------------*/
/**  Read and store the various lumps from the BSP file.
@return true if the lumps are read correctly, false otherwise */
/*-------------------------------------------------------------------*/
  private boolean readBSPLumps()
  {
    if(inPAKFile)
      lumpData = new byte[pakBSPFileData.length - headerData.length];
    else
      lumpData = new byte[(int)bspFile.length() - headerData.length];

    try
    {
      bufIn.read(lumpData);
    }
    catch(IOException ioe)
    {
      return false;
    }

    bspLumps = new BSPLump[19];

    bspLumps[BSPHeader.PLANES] = planeLump = new BSPPlaneLump(lumpData, bspHeader.getOffset(BSPHeader.PLANES), bspHeader.getLength(BSPHeader.PLANES));
    bspLumps[BSPHeader.VERTICES] = vertexLump = new BSPVertexLump(lumpData, bspHeader.getOffset(BSPHeader.VERTICES), bspHeader.getLength(BSPHeader.VERTICES));
    bspLumps[BSPHeader.NODES] = nodeLump = new BSPNodeLump(lumpData, bspHeader.getOffset(BSPHeader.NODES), bspHeader.getLength(BSPHeader.NODES));
    bspLumps[BSPHeader.LEAVES] = leafLump = new BSPLeafLump(lumpData, bspHeader.getOffset(BSPHeader.LEAVES), bspHeader.getLength(BSPHeader.LEAVES));
    bspLumps[BSPHeader.LEAF_FACE_TABLE] = leafFaceLump = new BSPLeafFaceLump(lumpData, bspHeader.getOffset(BSPHeader.LEAF_FACE_TABLE), bspHeader.getLength(BSPHeader.LEAF_FACE_TABLE));
    bspLumps[BSPHeader.FACE_EDGE_TABLE] = faceEdgeLump = new BSPFaceEdgeLump(lumpData, bspHeader.getOffset(BSPHeader.FACE_EDGE_TABLE), bspHeader.getLength(BSPHeader.FACE_EDGE_TABLE));
    bspLumps[BSPHeader.EDGES] = edgeLump = new BSPEdgeLump(lumpData, bspHeader.getOffset(BSPHeader.EDGES), bspHeader.getLength(BSPHeader.EDGES));
    bspLumps[BSPHeader.MODELS] = modelLump = new BSPModelLump(lumpData, bspHeader.getOffset(BSPHeader.MODELS), bspHeader.getLength(BSPHeader.MODELS));
    bspLumps[BSPHeader.BRUSHES] = brushLump = new BSPBrushLump(lumpData, bspHeader.getOffset(BSPHeader.BRUSHES), bspHeader.getLength(BSPHeader.BRUSHES));
    bspLumps[BSPHeader.BRUSH_SIDES] = brushSideLump = new BSPBrushSideLump(lumpData, bspHeader.getOffset(BSPHeader.BRUSH_SIDES), bspHeader.getLength(BSPHeader.BRUSH_SIDES));
    bspLumps[BSPHeader.LEAF_BRUSH_TABLE] = leafBrushLump = new BSPLeafBrushLump(lumpData, bspHeader.getOffset(BSPHeader.LEAF_BRUSH_TABLE), bspHeader.getLength(BSPHeader.LEAF_BRUSH_TABLE));

    bspLumps[BSPHeader.ENTITIES] = entitiesLump = new BSPEntityLump(lumpData, bspHeader.getOffset(BSPHeader.ENTITIES), bspHeader.getLength(BSPHeader.ENTITIES), modelLump.models);

    return true;
  }

/*-------------------------------------------------------------------*/
/**  A convenience method, which determines whether a given BSP file
*  contains the specified map name. Generally called by the readMap
*  method of BasicBot, when attempting to locate and load the map
*  being used in the current game session.
@param pathAndFileName the full path to the BSP file
@param mapName the name of the map to look for in the file
@return true if the map name was found, false otherwise
@see soc.qase.bot.BasicBot#readMap */
/*-------------------------------------------------------------------*/
  public static boolean isMapNameInFile(String pathAndFileName, String mapName)
  {
    File bspMapCheck = new File(pathAndFileName);

    if(bspMapCheck.exists())
    {
      byte[] entityBlockOffset = new byte[4];
      byte[] searchBytes = mapName.getBytes();
      String currentLine = null, entityBlock = "";

      try
      {
        RandomAccessFile mapReader = new RandomAccessFile(bspMapCheck, "r");

        mapReader.seek(8); // skip ID & version info
        mapReader.read(entityBlockOffset);
        mapReader.seek(Utils.intValue(entityBlockOffset, 0));

        while((currentLine = mapReader.readLine()) != null)
        {
          entityBlock += currentLine;

          if(currentLine.indexOf('}') != -1 && entityBlock.indexOf("worldspawn") != -1)
            break;
        }

        mapReader.close();
      }
      catch(IOException ioe)
      {  }

      return (entityBlock.indexOf(mapName) > -1);
    }

    return false;
  }

/*-------------------------------------------------------------------*/
/**  Returns the name of the current map (not necessarily the same as
*  the BSP file name), or null if the map does not have a name or no
*  map is loaded.
@return the name of the currently-loaded map, or null if no such
*  map/name. */
/*-------------------------------------------------------------------*/
  public String getMapName()
  {
    if(!mapRead)
      return null;

    for(int i = 0; i < entitiesLump.entities.length; i++)
    {
      if(entitiesLump.entities[i].isWorldSpawn)
        return entitiesLump.entities[i].getAttribute("message");
    }

    return null;
  }

/*-------------------------------------------------------------------*/
/**  Returns the path and name of the .bsp file associated with the BSPParser.
@return the name of the current .bsp file. */
/*-------------------------------------------------------------------*/
  public String getFileName()
  {
    return fName;
  }

/*-------------------------------------------------------------------*/
/**  Return a specified lump object.
@param lumpNum the index of the lump (using constants listed above)
@return the BSPLump object at that index */
/*-------------------------------------------------------------------*/
  public BSPLump getLump(int lumpNum)
  {
    return bspLumps[lumpNum];
  }

/*-------------------------------------------------------------------*/
/** Returns all Item entities.
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getItems(Vector vect)
  {  return getEntityType(vect, BSPEntity.BSP_ITEM)}

/*-------------------------------------------------------------------*/
/** Returns all entities which possess in-game models.
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getModels(Vector vect)
  {  return getEntityType(vect, BSPEntity.BSP_MODEL)}

/*-------------------------------------------------------------------*/
/** Returns all Weapon entities.
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getWeapons(Vector vect)
  {  return getEntityType(vect, BSPEntity.BSP_WEAPON)}

/*-------------------------------------------------------------------*/
/** Returns all Monster entities.
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getMonsters(Vector vect)
  {  return getEntityType(vect, BSPEntity.BSP_MONSTER)}

/*-------------------------------------------------------------------*/
/** Returns all Door entities.
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getDoors(Vector vect)
  {  return getEntityType(vect, BSPEntity.BSP_DOOR)}

/*-------------------------------------------------------------------*/
/** Returns all Lift entities.
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getLifts(Vector vect)
  {  return getEntityType(vect, BSPEntity.BSP_LIFT)}

/*-------------------------------------------------------------------*/
/** Returns all Button entities.
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getButtons(Vector vect)
  {  return getEntityType(vect, BSPEntity.BSP_BUTTON)}

/*-------------------------------------------------------------------*/
/** Returns all Illusory entities (i.e. can be seen but not interactive).
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getIllusion(Vector vect)
  {  return getEntityType(vect, BSPEntity.BSP_ILLUSION)}

/*-------------------------------------------------------------------*/
/** Returns all Conveyor belt and train entities.
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getConveyors(Vector vect)
  {  return getEntityType(vect, BSPEntity.BSP_CONVEYOR)}

/*-------------------------------------------------------------------*/
/** Returns all Teleporter entities, both single-player and DeathMatch.
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getTeleports(Vector vect)
  {  return getDMTeleports(getNormalTeleports(vect))}

/*-------------------------------------------------------------------*/
/** Returns all DeathMatch teleporter entities.
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getDMTeleports(Vector vect)
  {  return getEntityType(vect, BSPEntity.BSP_DM_TELEPORT)}

/*-------------------------------------------------------------------*/
/** Returns all single-player teleporter entities.
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getNormalTeleports(Vector vect)
  {  return getEntityType(vect, BSPEntity.BSP_NORMAL_TELEPORT)}

/*-------------------------------------------------------------------*/
/** Returns all secret door entities.
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getSecretDoors(Vector vect)
  {  return getEntityType(vect, BSPEntity.BSP_SECRET_DOOR)}

/*-------------------------------------------------------------------*/
/** Returns all path corner entities.
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getPathCorners(Vector vect)
  {  return getEntityType(vect, BSPEntity.BSP_CORNER)}

/*-------------------------------------------------------------------*/
/** Returns all walkover-button entities.
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getWalkovers(Vector vect)
  {  return getEntityType(vect, BSPEntity.BSP_WALKOVER)}

/*-------------------------------------------------------------------*/
/** Returns all Teleport destination entities.
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getTeleportDestinations(Vector vect)
  {  return getEntityType(vect, BSPEntity.BSP_DESTINATION)}

/*-------------------------------------------------------------------*/
/** Returns all miscellaneous object entities (exploding barrels, etc).
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getMiscObjects(Vector vect)
  {  return getEntityType(vect, BSPEntity.BSP_MISC_OBJECT)}

/*-------------------------------------------------------------------*/
/** Returns all spawn points, regardless of single or multi-player.
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getStartPositions(Vector vect)
  {  return getDMStartPositions(getPlayerStartPositions(vect))}

/*-------------------------------------------------------------------*/
/** Returns all single-player spawn points.
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getPlayerStartPositions(Vector vect)
  {  return getEntityType(vect, BSPEntity.BSP_PLAYER_START)}

/*-------------------------------------------------------------------*/
/** Returns all DeathMatch spawn points.
@param vect the Vector into which the entities will be added
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getDMStartPositions(Vector vect)
  {  return getEntityType(vect, BSPEntity.BSP_DM_START)}

/*-------------------------------------------------------------------*/
/** Returns all entities of the specified type. The supplied entity ID
*  should match one of the integer constants found in BSPEntity.
@param vect the Vector into which the entities will be added
@param entID the type of entity to find and return; should be one
*  of the integer constants from BSPEntity
@return a reference to the newly-populated vect for convenience */
/*-------------------------------------------------------------------*/
  public Vector getEntityType(Vector vect, int entID)
  {
    for(int i = 0; i < entitiesLump.entities.length; i++)
    {
      if((entID == BSPEntity.BSP_MODEL ? entitiesLump.entities[i].isModel : entitiesLump.entities[i].entityType == entID))
        vect.add(entitiesLump.entities[i]);
    }

    return vect;
  }

/*-------------------------------------------------------------------*/
/**  Perform a line trace from one point to another to determine if the
*  endpoint is visible from the start point.
@return true if nothing obscures the view, false otherwise */
/*-------------------------------------------------------------------*/
  public boolean isVisible(Vector3f start, Vector3f end)
  {
    return (traceLine(start, end)).equals(end);
  }

/*-------------------------------------------------------------------*/
/**  Projects a line through the game world in a given direction from a
*  given start point, and returns the location of the first collision
*  with solid geometry.
@param start start point for line trace
@param dir direction in which to trace
@param maxDist maximum distance across which to sweep
@return the location of the first collision */
/*-------------------------------------------------------------------*/
  public Vector3f getObstacleLocation(Vector3f start, Vector3f dir, float maxDist)
  {
    return traceLine(start, getEndpoint(start, dir, maxDist));
  }

/*-------------------------------------------------------------------*/
/**  Projects a sphere through the game world in a given direction from a
*  given start point, and returns the location of the first collision
*  with solid geometry.
@param start start point for trace
@param dir direction in which to trace
@param sphereRadius the radius of the sphere to be traced
@param maxDist maximum distance across which to sweep
@return the location of the first collision */
/*-------------------------------------------------------------------*/
  public Vector3f getObstacleLocation(Vector3f start, Vector3f dir, float sphereRadius, float maxDist)
  {
    return traceSphere(start, getEndpoint(start, dir, maxDist), sphereRadius);
  }

/*-------------------------------------------------------------------*/
/**  Projects a bounding box through the game world in a given direction
*  from a given start point, and returns the location of the first
*  collision with solid geometry.
@param start start point for trace
@param dir direction in which to trace
@param boundingBoxMins the lower reference point of the bounding box
@param boundingBoxMaxs the upper reference point of the bounding box
@param maxDist maximum distance across which to sweep
@return the location of the first collision */
/*-------------------------------------------------------------------*/
  public Vector3f getObstacleLocation(Vector3f start, Vector3f dir, Vector3f boundingBoxMins, Vector3f boundingBoxMaxs, float maxDist)
  {
    return traceBox(start, getEndpoint(start, dir, maxDist), boundingBoxMins, boundingBoxMaxs);
  }

/*-------------------------------------------------------------------*/
/**  Projects a line through the game world in a given direction from a
*  given start point, and returns the distance to the first collision
*  with solid geometry.
@param start start point for line trace
@param dir direction in which to trace
@param maxDist maximum distance across which to sweep
@return the distance to the first collision */
/*-------------------------------------------------------------------*/
  public float getObstacleDistance(Vector3f start, Vector3f dir, float maxDist)
  {
    if(!mapRead)
      return Float.NaN;

    Vector3f end = getObstacleLocation(start, dir, maxDist);

    end.sub(start);
    return end.length();
  }

/*-------------------------------------------------------------------*/
/**  Projects a sphere through the game world in a given direction from a
*  given start point, and returns the distance to the first collision
*  with solid geometry.
@param start start point for trace
@param dir direction in which to trace
@param sphereRadius the radius of the sphere to be traced
@param maxDist maximum distance across which to sweep
@return the distance to the first collision */
/*-------------------------------------------------------------------*/
  public float getObstacleDistance(Vector3f start, Vector3f dir, float sphereRadius, float maxDist)
  {
    if(!mapRead)
      return Float.NaN;

    Vector3f end = getObstacleLocation(start, dir, sphereRadius, maxDist);

    end.sub(start);
    return end.length();
  }

/*-------------------------------------------------------------------*/
/**  Projects a bounding box through the game world in a given direction
*  from a given start point, and returns the location of the first
*  collision with solid geometry.
@param start start point for trace
@param dir direction in which to trace
@param boundingBoxMins the lower reference point of the bounding box
@param boundingBoxMaxs the upper reference point of the bounding box
@param maxDist maximum distance across which to sweep
@return the location of the first collision */
/*-------------------------------------------------------------------*/
  public float getObstacleDistance(Vector3f start, Vector3f dir, Vector3f boundingBoxMins, Vector3f boundingBoxMaxs, float maxDist)
  {
    if(!mapRead)
      return Float.NaN;

    Vector3f end = getObstacleLocation(start, dir, boundingBoxMins, boundingBoxMaxs, maxDist);

    end.sub(start);
    return end.length();
  }

/*-------------------------------------------------------------------*/
/*-------------------------------------------------------------------*/
  private Vector3f getEndpoint(Vector3f start, Vector3f dir, float maxDist)
  {
    Vector3f end = new Vector3f(dir);

    end.normalize();
    end.scale(maxDist);
    end.add(start);

    return end;
  }

  private int TRACE_TYPE;
  private int BRUSH_BITS = BSPBrush.CONTENTS_SOLID;

  private float traceRadius;
  private float outputFraction;

  private boolean outputAllSolid;
  private boolean outputStartsOut;

  private Vector3f outputEnd = new Vector3f(0, 0, 0);
  private Vector3f inputStart = new Vector3f(0, 0, 0);
  private Vector3f inputEnd = new Vector3f(0, 0, 0);

  private Vector3f traceMins = null;
  private Vector3f traceMaxs = null;
  private Vector3f traceExtents = new Vector3f(0, 0, 0);

/*-------------------------------------------------------------------*/
/**  Set the type of brush to check for when sweeping, specified by
*  the constants found in BSPBrush. Allows the agent to check for
*  different types of terrain.
@param brushType the type of brush to check against
@see BSPBrush */
/*-------------------------------------------------------------------*/
  public void setBrushType(int brushType)
  {
    BRUSH_BITS = brushType;
  }

/*-------------------------------------------------------------------*/
/**  Trace a line between two points, and return the point at which a
*  collision first occurs.
@param start the start point of the trace
@param end the endpoint of the trace
@return a Vector3f indicating the location at which the first
*  collision occurred */
/*-------------------------------------------------------------------*/
  public Vector3f traceLine(Vector3f start, Vector3f end)
  {
    TRACE_TYPE = TRACE_LINE;
    return trace(start, end);
  }

/*-------------------------------------------------------------------*/
/**  Trace a sphere between two points, and return the point at which a
*  collision first occurs.
@param start the start point of the trace
@param end the endpoint of the trace
@param radius the radius of the sphere to sweep
@return a Vector3f indicating the location at which the first
*  collision occurred */
/*-------------------------------------------------------------------*/
  public Vector3f traceSphere(Vector3f start, Vector3f end, float radius)
  {
    traceRadius = radius;
    TRACE_TYPE = TRACE_SPHERE;
    return trace(start, end);
  }

/*-------------------------------------------------------------------*/
/**  Trace a bounding box between two points, and return the point at
*  which a collision first occurs.
@param start the start point of the trace
@param end the endpoint of the trace
@param mins the lower reference point of the bounding box
@param maxs the upper reference point of the bounding box
@return a Vector3f indicating the location at which the first
*  collision occurred */
/*-------------------------------------------------------------------*/
  public Vector3f traceBox(Vector3f start, Vector3f end, Vector3f mins, Vector3f maxs)
  {
    if(mins.length() == 0 && maxs.length() == 0)
      return traceLine(start, end);

    TRACE_TYPE = TRACE_BOX;

    traceMins = new Vector3f(mins);
    traceMaxs = new Vector3f(maxs);

    for(int i = 0; i < 3; i++)
      traceExtents.set(i, Math.max(-traceMins.get(i), traceMaxs.get(i)))

    return trace(start, end);
  }

  private Vector3f trace(Vector3f start, Vector3f end)
  {
    if(!mapRead)
      return null;

    outputStartsOut = true;
    outputAllSolid = false;
    outputFraction = 1.0f;

    inputEnd.set(end);
    inputStart.set(start);

    // traverse the BSP tree
    checkNode( 0, 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.set(i, inputStart.get(i) + outputFraction * (inputEnd.get(i) - inputStart.get(i)));
      }
    }

    return new Vector3f(outputEnd);
  }

  private void checkNode( int nodeIndex, float startFraction, float endFraction, Vector3f start, Vector3f end )
  {
    if (nodeIndex < 0)
    {  // this is a leaf
      BSPLeaf leaf = leafLump.leaves[-(nodeIndex + 1)];

      for (int i = 0; i < leaf.numLeafBrushes; i++)
      {
        BSPBrush brush = brushLump.brushes[leafBrushLump.leafBrushTable[leaf.firstLeafBrush + i]];

        if (brush.numSides > 0 && brush.checkContents(BRUSH_BITS))
          checkBrush(brush);
      }

      // don't have to do anything else for leaves
      return;
    }
 
    // this is a node

    BSPNode node = nodeLump.nodes[nodeIndex];
    BSPPlane plane = planeLump.planes[(int)node.plane];

    float offset = 0;
    float startDistance = start.dot(plane.normal) - plane.distance;
    float endDistance = end.dot(plane.normal) - plane.distance;

    if(TRACE_TYPE == TRACE_LINE)
      offset = 0;
    else if(TRACE_TYPE == TRACE_SPHERE)
      offset = traceRadius;
    else if(TRACE_TYPE == TRACE_BOX)
    {
      for(int i = 0; i < 3; i++)
        offset += Math.abs(traceExtents.get(i) * plane.normal.get(i));
    }

    if (startDistance >= offset && endDistance >= offset)
    {  // both points are in front of the plane
      // so check the front child
      checkNode(node.frontChild, startFraction, endFraction, start, end );
    }
    else if (startDistance < -offset && endDistance < -offset)
    {  // both points are behind the plane
      // so check the back child
      checkNode(node.backChild, startFraction, endFraction, start, end );
    }
    else
    // the line spans the splitting plane
      int side;
      float fraction1, fraction2, middleFraction;
      Vector3f middle = new Vector3f(0, 0, 0);

      // split the segment into two
      if (startDistance < endDistance)
      {
        side = 1; // back
        float inverseDistance = 1.0f / (startDistance - endDistance);
        fraction1 = (startDistance - offset + EPSILON) * inverseDistance;
        fraction2 = (startDistance + offset + EPSILON) * inverseDistance;
      }
      else if (endDistance < startDistance)
      {
        side = 0; // front
        float 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.set(i, start.get(i) + fraction1 * (end.get(i) - start.get(i)));

      // check the first side
      checkNode(node.children[side], 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.set(i, start.get(i) + fraction2 * (end.get(i) - start.get(i)));

      // check the second side
      checkNode(node.children[(side + 1) % 2], middleFraction, endFraction, middle, end );
    }
  }

  private void checkBrush(BSPBrush brush)
  {
    float startFraction = -1.0f;
    float endFraction = 1.0f;
    boolean startsOut = false;
    boolean endsOut = false;
 
    for (int i = 0; i < brush.numSides; i++)
    {
      BSPBrushSide brushSide = brushSideLump.brushSides[(int)brush.firstSide + i];
      BSPPlane plane = planeLump.planes[brushSide.planeNum];

      float startDistance = 0, endDistance = 0;

      if(TRACE_TYPE == TRACE_LINE)
      {
        startDistance = inputStart.dot(plane.normal) - plane.distance;
        endDistance = inputEnd.dot(plane.normal) - plane.distance;
      }
      if(TRACE_TYPE == TRACE_SPHERE)
      {
        startDistance = inputStart.dot(plane.normal) - (plane.distance + traceRadius);
        endDistance = inputEnd.dot(plane.normal) - (plane.distance + traceRadius);
      }
      else if(TRACE_TYPE == TRACE_BOX)
      {
        for(int j = 0; j < 3; j++)
        {
          if(plane.normal.get(j) < 0)
          {
            startDistance += ((inputStart.get(j) + traceMaxs.get(j)) * plane.normal.get(j))
            endDistance += ((inputEnd.get(j) + traceMaxs.get(j)) * plane.normal.get(j))
          }
          else
          {
            startDistance += ((inputStart.get(j) + traceMins.get(j)) * plane.normal.get(j))
            endDistance += ((inputEnd.get(j) + traceMins.get(j)) * plane.normal.get(j))
          }
        }

        startDistance -= plane.distance;
        endDistance -= plane.distance;
      }

      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
        float fraction = (startDistance - EPSILON) / (startDistance - endDistance);
        if (fraction > startFraction)
          startFraction = fraction;
      }
      else
      {   // line is leaving the brush
        float fraction = (startDistance + EPSILON) / (startDistance - endDistance);
        if (fraction < endFraction)
          endFraction = fraction;
      }
    }
 
    if (!startsOut)
    {
      outputStartsOut = false;

      if(!endsOut)
        outputAllSolid = true;

      return;
    }
 
    if (startFraction < endFraction)
    {
      if (startFraction > -1 && startFraction < outputFraction)
      {
        if (startFraction < 0)
          startFraction = 0;
        outputFraction = startFraction;
      }
    }
  }

/*-------------------------------------------------------------------*/
/**  Resets all contents of the BSPParser object. Called if a new BSP
*  file is loaded while a previous file is active, or by the user
*  for any arbitraty purpose. */
/*-------------------------------------------------------------------*/
  public void reset()
  {
    // reset BSP structures
    lumpData = null;
    headerData = null;
    pakBSPFileData = null;

    fName = null;
    bspFile = null;

    bufIn = null;
    byteIn = null;

    mapRead = false;
    fileOpen = false;
    inPAKFile = false;

    bspLumps = null;
    bspHeader = null;

    planeLump = null;
    vertexLump = null;
    nodeLump = null;
    leafLump = null;
    leafFaceLump = null;
    faceEdgeLump = null;
    edgeLump = null;
    modelLump = null;
    brushLump = null;
    brushSideLump = null;
    leafBrushLump = null;

    entitiesLump = null;

    // reset private trace variables
    TRACE_TYPE = 0;
    BRUSH_BITS = BSPBrush.CONTENTS_SOLID;

    traceRadius = 0.0f;
    outputFraction = 0.0f;

    outputAllSolid = false;
    outputStartsOut = false;

    outputEnd = new Vector3f(0, 0, 0);
    inputStart = new Vector3f(0, 0, 0);
    inputEnd = new Vector3f(0, 0, 0);

    traceMins = null;
    traceMaxs = null;
    traceExtents = new Vector3f(0, 0, 0);
  }
}
TOP

Related Classes of soc.qase.file.bsp.BSPParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.