/// <summary> /// Renderizar escenario BSP utilizando matriz PVS para descartar clusters no visibles /// </summary> /// <param name="camPos">Posición actual de la camara</param> public void render(Vector3 camPos) { Device device = GuiController.Instance.D3dDevice; float elapsedTime = GuiController.Instance.ElapsedTime; time += elapsedTime; //Obtener leaf actual int actualLeaf = FindLeaf(camPos); //Obtener clusters visibles segun PVS int actualCluster = data.leafs[actualLeaf].cluster; if (actualCluster != antCluster) { antCluster = actualCluster; clusterVis.Clear(); for (int i = 0; i < data.leafs.Length; i++) { if (isClusterVisible(actualCluster, data.leafs[i].cluster)) { clusterVis.Add(i); } } } //Actualizar volumen del Frustum con nuevos valores de camara TgcFrustum frustum = GuiController.Instance.Frustum; frustum.updateVolume(device.Transform.View, device.Transform.Projection); foreach (int nleaf in clusterVis) { //Frustum Culling con el AABB del cluster TgcCollisionUtils.FrustumResult result = TgcCollisionUtils.classifyFrustumAABB(frustum, data.leafs[nleaf].boundingBox); if (result == TgcCollisionUtils.FrustumResult.OUTSIDE) { continue; } //Habilitar meshes de este leaf QLeaf currentLeaf = data.leafs[nleaf]; for (int i = currentLeaf.firstLeafSurface; i < currentLeaf.firstLeafSurface + currentLeaf.numLeafSurfaces; i++) { int iMesh = data.leafSurfaces[i]; TgcMesh mesh = meshes[iMesh]; if (mesh != null) { meshes[iMesh].Enabled = true; } } } //Renderizar meshes visibles mViewProj = device.Transform.View * device.Transform.Projection; for (int i = 0; i < meshes.Count; i++) { //Ignonar si no está habilitada TgcMesh mesh = meshes[i]; if (mesh == null || !mesh.Enabled) { continue; } QShaderData shader = data.shaderXSurface[i]; //Renderizado con shaders //TODO: Mejorar el renderizado de shaders. Por ahora esta deshabilitado if (shader != null && shader.Stages.Count > 0) { renderShaderMesh(mesh, shader); } //Renderizado normal else { //render mesh.render(); //deshabilitar para la proxima vuelta mesh.Enabled = false; } } }
/// <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); } } }