Beispiel #1
0
        /// <summary>
        /// Encontrar la hoja actual del arbol BP
        /// </summary>
        private int FindLeaf(Vector3 pos)
        {
            int index = 0;

            while (index >= 0)
            {
                QNode  node  = data.nodes[index];
                QPlane plane = data.planes[node.planeNum];

                // Distance from point to a plane
                float distance = Vector3.Dot(plane.normal, pos) - plane.dist;

                if (distance >= 0)
                {
                    //front
                    index = node.children[0];
                }
                else
                {
                    //back
                    index = node.children[1];
                }
            }

            //~ => bitwise complement operation
            return(~index);
        }
        /// <summary>
        /// Checks our movement vector against all the planes of the brush
        /// </summary>
        private void CheckBrush(QBrush pBrush, Vector3 vStart, Vector3 vEnd)
        {
            float startRatio = -1.0f;           // Like in BrushCollision.htm, start a ratio at -1
            float endRatio   = 1.0f;            // Set the end ratio to 1
            bool  startsOut  = false;           // This tells us if we starting outside the brush

            // This function actually does the collision detection between our movement
            // vector and the brushes in the world data.  We will go through all of the
            // brush sides and check our start and end ratio against the planes to see if
            // they pass each other.  We start the startRatio at -1 and the endRatio at
            // 1, but as we set the ratios to their intersection points (ratios), then
            // they slowly move toward each other.  If they pass each other, then there
            // is definitely not a collision.

            // Go through all of the brush sides and check collision against each plane
            for (int i = 0; i < pBrush.numSides; i++)
            {
                // Here we grab the current brush side and plane in this brush
                QBrushSide pBrushSide = bspMap.Data.brushSides[pBrush.firstSide + i];
                QPlane     pPlane     = bspMap.Data.planes[pBrushSide.planeNum];

                // Let's store a variable for the offset (like for sphere collision)
                float offset = 0.0f;

                // If we are testing sphere collision we need to add the sphere radius
                if (traceType == TYPE_SPHERE)
                {
                    offset = traceRadius;
                }

                // Test the start and end points against the current plane of the brush side.
                // Notice that we add an offset to the distance from the origin, which makes
                // our sphere collision work.
                float startDistance = Vector3.Dot(vStart, pPlane.normal) - (pPlane.dist + offset);
                float endDistance   = Vector3.Dot(vEnd, pPlane.normal) - (pPlane.dist + offset);



                // This is the last beefy part of code in this tutorial.  In this
                // section we need to do a few special checks to see which extents
                // we should use.  We do this by checking the x,y,z of the normal.
                // If the vNormal.x is less than zero, we want to use the Max.x
                // value, otherwise use the Min.x value.  We do these checks because
                // we want the corner of the bounding box that is closest to the plane to
                // test for collision.  Write it down on paper and see how this works.
                // We want to grab the closest corner (x, y, or z value that is...) so we
                // dont go through the wall.  This works because the bounding box is axis aligned.

                // Store the offset that we will check against the plane


                // If we are using AABB collision
                if (traceType == TYPE_BOX)
                {
                    Vector3 vOffset = new Vector3();
                    // Grab the closest corner (x, y, or z value) that is closest to the plane
                    vOffset.X = (pPlane.normal.X < 0) ? vTraceMaxs.X : vTraceMins.X;
                    vOffset.Y = (pPlane.normal.Y < 0) ? vTraceMaxs.Y : vTraceMins.Y;
                    vOffset.Z = (pPlane.normal.Z < 0) ? vTraceMaxs.Z : vTraceMins.Z;

                    // Use the plane equation to grab the distance our start position is from the plane.
                    // We need to add the offset to this to see if the box collides with the plane,
                    // even if the position doesn't.
                    startDistance = Vector3.Dot(vStart + vOffset, pPlane.normal) - pPlane.dist;

                    // Get the distance our end position is from this current brush plane
                    endDistance = Vector3.Dot(vEnd + vOffset, pPlane.normal) - pPlane.dist;
                }



                // Make sure we start outside of the brush's volume
                if (startDistance > 0)
                {
                    startsOut = true;
                }

                // Stop checking since both the start and end position are in front of the plane
                if (startDistance > 0 && endDistance > 0)
                {
                    return;
                }

                // Continue on to the next brush side if both points are behind or on the plane
                if (startDistance <= 0 && endDistance <= 0)
                {
                    continue;
                }

                // If the distance of the start point is greater than the end point, we have a collision!
                if (startDistance > endDistance)
                {
                    // This gets a ratio from our starting point to the approximate collision spot
                    float Ratio1 = (startDistance - kEpsilon) / (startDistance - endDistance);

                    // If this is the first time coming here, then this will always be true,
                    // since startRatio starts at -1.0f.  We want to find the closest collision,
                    // so we still continue to check all of the brushes before quitting.
                    if (Ratio1 > startRatio)
                    {
                        // Set the startRatio (currently the closest collision distance from start)
                        startRatio       = Ratio1;
                        bCollided        = true;        // Let us know we collided!
                        vCollisionNormal = pPlane.normal;
                        if (vCollisionNormal.Y >= 0.2f)
                        {
                            onGround = true;
                        }


                        // This checks first tests if we actually moved along the x or z-axis,
                        // meaning that we went in a direction somewhere.  The next check makes
                        // sure that we don't always check to step every time we collide.  If
                        // the normal of the plane has a Y value of 1, that means it's just the
                        // flat ground and we don't need to check if we can step over it, it's flat!
                        if ((vStart.X != vEnd.X || vStart.Z != vEnd.Z) && pPlane.normal.Y != 1)
                        {
                            // We can try and step over the wall we collided with
                            bTryStep = true;
                        }
                    }
                }
                else
                {
                    // Get the ratio of the current brush side for the endRatio
                    float Ratio = (startDistance + kEpsilon) / (startDistance - endDistance);

                    // If the ratio is less than the current endRatio, assign a new endRatio.
                    // This will usually always be true when starting out.
                    if (Ratio < endRatio)
                    {
                        endRatio = Ratio;
                    }
                }
            }

            // If we didn't start outside of the brush we don't want to count this collision - return;
            if (startsOut == false)
            {
                return;
            }

            // If our startRatio is less than the endRatio there was a collision!!!
            if (startRatio < endRatio)
            {
                // Make sure the startRatio moved from the start and check if the collision
                // ratio we just got is less than the current ratio stored in m_traceRatio.
                // We want the closest collision to our original starting position.
                if (startRatio > -1 && startRatio < traceRatio)
                {
                    // If the startRatio is less than 0, just set it to 0
                    if (startRatio < 0)
                    {
                        startRatio = 0;
                    }

                    // Store the new ratio in our member variable for later
                    traceRatio = startRatio;
                }
            }
        }
        /// <summary>
        /// Traverses the BSP to find the brushes closest to our position
        /// </summary>
        private void CheckNode(int nodeIndex, float startRatio, float endRatio, Vector3 vStart, Vector3 vEnd)
        {
            // Remember, the nodeIndices are stored as negative numbers when we get to a leaf, so we
            // check if the current node is a leaf, which holds brushes.  If the nodeIndex is negative,
            // the next index is a leaf (note the: nodeIndex + 1)
            if (nodeIndex < 0)
            {
                // If this node in the BSP is a leaf, we need to negate and add 1 to offset
                // the real node index into the m_pLeafs[] array.  You could also do [~nodeIndex].
                QLeaf pLeaf = bspMap.Data.leafs[~nodeIndex];

                // We have a leaf, so let's go through all of the brushes for that leaf
                for (int i = 0; i < pLeaf.numLeafBrushes; i++)
                {
                    // Get the current brush that we going to check
                    QBrush pBrush = bspMap.Data.brushes[bspMap.Data.leafbrushes[pLeaf.firstLeafBrush + i]];

                    // This is kind of an important line.  First, we check if there is actually
                    // and brush sides (which store indices to the normal and plane data for the brush).
                    // If not, then go to the next one.  Otherwise, we also check to see if the brush
                    // is something that we want to collide with.  For instance, there are brushes for
                    // water, lava, bushes, misc. sprites, etc...  We don't want to collide with water
                    // and other things like bushes, so we check the texture type to see if it's a solid.
                    // If the textureType can be binary-anded (&) and still be 1, then it's solid,
                    // otherwise it's something that can be walked through.  That's how Quake chose to
                    // do it.

                    // Check if we have brush sides and the current brush is solid and collidable
                    if ((pBrush.numSides > 0) && (bspMap.Data.shaders[pBrush.shaderNum].contentFlags & 1) > 0)
                    {
                        // Now we delve into the dark depths of the real calculations for collision.
                        // We can now check the movement vector against our brush planes.
                        CheckBrush(pBrush, vStart, vEnd);
                    }
                }

                // Since we found the brushes, we can go back up and stop recursing at this level
                return;
            }

            // If we haven't found a leaf in the node, then we need to keep doing some dirty work
            // until we find the leafs which store the brush information for collision detection.

            // Grad the next node to work with and grab this node's plane data
            QNode  pNode  = bspMap.Data.nodes[nodeIndex];
            QPlane pPlane = bspMap.Data.planes[pNode.planeNum];

            // Now we do some quick tests to see which side we fall on of the node in the BSP

            // Here we use the plane equation to find out where our initial start position is
            // according the the node that we are checking.  We then grab the same info for the end pos.
            float startDistance = Vector3.Dot(vStart, pPlane.normal) - pPlane.dist;
            float endDistance   = Vector3.Dot(vEnd, pPlane.normal) - pPlane.dist;
            float offset        = 0.0f;

            // If we are doing any type of collision detection besides a ray, we need to change
            // the offset for which we are testing collision against the brushes.  If we are testing
            // a sphere against the brushes, we need to add the sphere's offset when we do the plane
            // equation for testing our movement vector (start and end position).  * More Info * For
            // more info on sphere collision, check out our tutorials on this subject.

            // If we are doing sphere collision, include an offset for our collision tests below
            if (traceType == TYPE_SPHERE)
            {
                offset = traceRadius;
            }
            else if (traceType == TYPE_BOX)
            {
                // This equation does a dot product to see how far our
                // AABB is away from the current plane we are checking.
                // Since this is a distance, we need to make it an absolute
                // value, which calls for the fabs() function (abs() for floats).

                // Get the distance our AABB is from the current splitter plane
                offset = Math.Abs(vExtents.X * pPlane.normal.X) +
                         Math.Abs(vExtents.Y * pPlane.normal.Y) +
                         Math.Abs(vExtents.Z * pPlane.normal.Z);
            }

            // Below we just do a basic traversal down the BSP tree.  If the points are in
            // front of the current splitter plane, then only check the nodes in front of
            // that splitter plane.  Otherwise, if both are behind, check the nodes that are
            // behind the current splitter plane.  The next case is that the movement vector
            // is on both sides of the splitter plane, which makes it a bit more tricky because we now
            // need to check both the front and the back and split up the movement vector for both sides.

            // Here we check to see if the start and end point are both in front of the current node.
            // If so, we want to check all of the nodes in front of this current splitter plane.
            if (startDistance >= offset && endDistance >= offset)
            {
                // Traverse the BSP tree on all the nodes in front of this current splitter plane
                CheckNode(pNode.children[0], startDistance, endDistance, vStart, vEnd);
            }
            // If both points are behind the current splitter plane, traverse down the back nodes
            else if (startDistance < -offset && endDistance < -offset)
            {
                // Traverse the BSP tree on all the nodes in back of this current splitter plane
                CheckNode(pNode.children[1], startDistance, endDistance, vStart, vEnd);
            }
            else
            {
                // If we get here, then our ray needs to be split in half to check the nodes
                // on both sides of the current splitter plane.  Thus we create 2 ratios.
                float   Ratio1 = 1.0f, Ratio2 = 0.0f, middleRatio = 0.0f;
                Vector3 vMiddle;        // This stores the middle point for our split ray

                // Start of the side as the front side to check
                int side = pNode.children[0];

                // Here we check to see if the start point is in back of the plane (negative)
                if (startDistance < endDistance)
                {
                    // Since the start position is in back, let's check the back nodes
                    side = pNode.children[1];

                    // Here we create 2 ratios that hold a distance from the start to the
                    // extent closest to the start (take into account a sphere and epsilon).
                    // We use epsilon like Quake does to compensate for float errors.  The second
                    // ratio holds a distance from the other size of the extents on the other side
                    // of the plane.  This essential splits the ray for both sides of the splitter plane.
                    float inverseDistance = 1.0f / (startDistance - endDistance);
                    Ratio1 = (startDistance - offset - kEpsilon) * inverseDistance;
                    Ratio2 = (startDistance + offset + kEpsilon) * inverseDistance;
                }
                // Check if the starting point is greater than the end point (positive)
                else if (startDistance > endDistance)
                {
                    // This means that we are going to recurse down the front nodes first.
                    // We do the same thing as above and get 2 ratios for split ray.
                    // Ratio 1 and 2 are switched in contrast to the last if statement.
                    // This is because the start is starting in the front of the splitter plane.
                    float inverseDistance = 1.0f / (startDistance - endDistance);
                    Ratio1 = (startDistance + offset + kEpsilon) * inverseDistance;
                    Ratio2 = (startDistance - offset - kEpsilon) * inverseDistance;
                }

                // Make sure that we have valid numbers and not some weird float problems.
                // This ensures that we have a value from 0 to 1 as a good ratio should be :)
                if (Ratio1 < 0.0f)
                {
                    Ratio1 = 0.0f;
                }
                else if (Ratio1 > 1.0f)
                {
                    Ratio1 = 1.0f;
                }

                if (Ratio2 < 0.0f)
                {
                    Ratio2 = 0.0f;
                }
                else if (Ratio2 > 1.0f)
                {
                    Ratio2 = 1.0f;
                }

                // Just like we do in the Trace() function, we find the desired middle
                // point on the ray, but instead of a point we get a middleRatio percentage.
                // This isn't the true middle point since we are using offset's and the epsilon value.
                // We also grab the middle point to go with the ratio.
                middleRatio = startRatio + ((endRatio - startRatio) * Ratio1);
                vMiddle     = vStart + ((vEnd - vStart) * Ratio1);

                // Now we recurse on the current side with only the first half of the ray
                CheckNode(side, startRatio, middleRatio, vStart, vMiddle);

                // Now we need to make a middle point and ratio for the other side of the node
                middleRatio = startRatio + ((endRatio - startRatio) * Ratio2);
                vMiddle     = vStart + ((vEnd - vStart) * Ratio2);

                // Depending on which side should go last, traverse the bsp with the
                // other side of the split ray (movement vector).
                if (side == pNode.children[1])
                {
                    CheckNode(pNode.children[0], middleRatio, endRatio, vMiddle, vEnd);
                }
                else
                {
                    CheckNode(pNode.children[1], middleRatio, endRatio, vMiddle, vEnd);
                }
            }
        }