protected void TagNodesWithObject(BspNode node, MovableObject obj, Vector3 pos) { if (node.IsLeaf) { // Add to movable->node map // Insert all the time, will get current if already there objectToNodeMap.Insert(obj, node); node.AddObject(obj); } else { // Find distance to dividing plane float dist = node.GetDistance(pos); //CHECK: treat obj as bounding box? if (MathUtil.Abs(dist) < obj.BoundingRadius) { // Bounding sphere crosses the plane, do both. TagNodesWithObject(node.BackNode, obj, pos); TagNodesWithObject(node.FrontNode, obj, pos); } else if (dist < 0) { // Do back. TagNodesWithObject(node.BackNode, obj, pos); } else { // Do front. TagNodesWithObject(node.FrontNode, obj, pos); } } }
/// <summary> /// Walks the entire BSP tree and returns the leaf which contains the given point. /// </summary>r public BspNode FindLeaf(Vector3 point) { BspNode node = nodes[0]; while (!node.IsLeaf) { node = node.GetNextNode(point); } return(node); }
private void CreateLeaves(Quake3Level q3lvl) { for (int i = 0; i < q3lvl.NumLeaves; ++i) { BspNode node = nodes[i + this.LeafStart]; InternalBspLeaf q3leaf = q3lvl.Leaves[i]; node.IsLeaf = true; node.Owner = this; // Set bounding box node.BoundingBox.Minimum = new Vector3( q3leaf.bbox[0], q3leaf.bbox[1], q3leaf.bbox[2] ); node.BoundingBox.Maximum = new Vector3( q3leaf.bbox[3], q3leaf.bbox[4], q3leaf.bbox[5] ); // Set faces node.FaceGroupStart = q3leaf.faceStart; node.NumFaceGroups = q3leaf.faceCount; node.VisCluster = q3leaf.cluster; // Load Brushes for this leaf int realBrushIdx = 0, solidIdx = 0; int brushCount = q3leaf.brushCount; int brushIdx = q3leaf.brushStart; node.SolidBrushes = new BspBrush[brushCount]; while (brushCount-- > 0) { realBrushIdx = q3lvl.LeafBrushes[brushIdx]; InternalBspBrush q3brush = q3lvl.Brushes[realBrushIdx]; // Only load solid ones, we don't care about any other types // Shader determines this. InternalBspShader brushShader = q3lvl.Shaders[q3brush.shaderIndex]; if ((brushShader.contentFlags & ContentFlags.Solid) == ContentFlags.Solid) { node.SolidBrushes[solidIdx] = brushes[realBrushIdx]; } brushIdx++; solidIdx++; } } }
/// <summary> /// Determines if one leaf node is visible from another. /// </summary> /// <param name="?"></param> /// <returns></returns> public bool IsLeafVisible(BspNode from, BspNode to) { if (to.VisCluster == -1) { return(false); } if (from.VisCluster == -1) { // Camera outside world? return(true); } if (!from.IsLeaf || !to.IsLeaf) { throw new AxiomException("Both nodes must be leaf nodes for visibility testing."); } // Use PVS to determine visibility /* * // In wordier terms, the fairly cryptic (but fast) version is doing this: * // Could make it a macro for even more speed? * * // Row offset = from cluster number * row size * int offset = from->mVisCluster*mVisData.rowLength; * * // Column offset (in bytes) = to cluster number divided by 8 (since 8 bits per bytes) * offset += to->mVisCluster >> 3; * * // Get the right bit within the byte, i.e. bitwise 'and' with bit at remainder position * int result = *(mVisData.tableData + offset) & (1 << (to->mVisCluster & 7)); * * return (result != 0); */ byte visSet = visData.tableData[(from.VisCluster * visData.rowLength) + (to.VisCluster >> 3)]; int result = visSet & (1 << ((to.VisCluster) & 7)); return(result != 0); }
protected virtual void ProcessNode( BspNode node, Ray tracingRay, float maxDistance, float traceDistance ) { // check if ray already encountered a solid brush if ( this.StopRayTracing ) { return; } if ( node.IsLeaf ) { ProcessLeaf( node, tracingRay, maxDistance, traceDistance ); return; } IntersectResult result = tracingRay.Intersects( node.SplittingPlane ); if ( result.Hit ) { if ( result.Distance < maxDistance ) { if ( node.GetSide( tracingRay.Origin ) == PlaneSide.Negative ) { ProcessNode( node.BackNode, tracingRay, result.Distance, traceDistance ); Vector3 splitPoint = tracingRay.Origin + tracingRay.Direction*result.Distance; ProcessNode( node.FrontNode, new Ray( splitPoint, tracingRay.Direction ), maxDistance - result.Distance, traceDistance + result.Distance ); } else { ProcessNode( node.FrontNode, tracingRay, result.Distance, traceDistance ); Vector3 splitPoint = tracingRay.Origin + tracingRay.Direction*result.Distance; ProcessNode( node.BackNode, new Ray( splitPoint, tracingRay.Direction ), maxDistance - result.Distance, traceDistance + result.Distance ); } } else { ProcessNode( node.GetNextNode( tracingRay.Origin ), tracingRay, maxDistance, traceDistance ); } } else { ProcessNode( node.GetNextNode( tracingRay.Origin ), tracingRay, maxDistance, traceDistance ); } }
/// <summary> /// Determines if the passed in node (must also be a leaf) is visible from this leaf. /// </summary> /// <remarks> /// Must only be called on a leaf node, and the parameter must also be a leaf node. If /// this method returns true, then the leaf passed in is visible from this leaf. /// Note that internally this uses the Potentially Visible Set (PVS) which is precalculated /// and stored with the BSP level. /// </remarks> public bool IsLeafVisible(BspNode leaf) { return owner.IsLeafVisible(this, leaf); }
private void CreateNodes(Quake3Level q3lvl) { // Allocate memory for all nodes (leaves and splitters) nodes = new BspNode[q3lvl.NumNodes + q3lvl.NumLeaves]; numLeaves = q3lvl.NumLeaves; leafStart = q3lvl.NumNodes; // Run through and initialize the array so front/back node pointers // aren't null. for(int i = 0; i < nodes.Length; i++) nodes[i] = new BspNode(); // Convert nodes // In our array, first q3lvl.NumNodes are non-leaf, others are leaves for(int i = 0; i < q3lvl.NumNodes; i++) { BspNode node = nodes[i]; InternalBspNode q3node = q3lvl.Nodes[i]; node.IsLeaf = false; node.Owner = this; Plane splitPlane = new Plane(); // Set plane splitPlane.Normal = new Vector3( q3lvl.Planes[q3node.plane].normal[0], q3lvl.Planes[q3node.plane].normal[1], q3lvl.Planes[q3node.plane].normal[2] ); splitPlane.D = -q3lvl.Planes[q3node.plane].distance; node.SplittingPlane = splitPlane; // Set bounding box node.BoundingBox = new AxisAlignedBox( new Vector3( q3node.bbox[0], q3node.bbox[1], q3node.bbox[2] ), new Vector3( q3node.bbox[3], q3node.bbox[4], q3node.bbox[5] ) ); // Set back pointer // Negative indexes in Quake3 mean leaves. if(q3node.back < 0) node.BackNode = nodes[leafStart + (~(q3node.back))]; else node.BackNode = nodes[q3node.back]; // Set front pointer // Negative indexes in Quake3 mean leaves if(q3node.front < 0) node.FrontNode = nodes[leafStart + (~(q3node.front))]; else node.FrontNode = nodes[q3node.front]; } }
protected void TagNodesWithObject(BspNode node, MovableObject obj, Vector3 pos) { if(node.IsLeaf) { // Add to movable->node map // Insert all the time, will get current if already there objectToNodeMap.Insert(obj, node); node.AddObject(obj); } else { // Find distance to dividing plane float dist = node.GetDistance(pos); //CHECK: treat obj as bounding box? if(MathUtil.Abs(dist) < obj.BoundingRadius) { // Bounding sphere crosses the plane, do both. TagNodesWithObject(node.BackNode, obj, pos); TagNodesWithObject(node.FrontNode, obj, pos); } else if(dist < 0) { // Do back. TagNodesWithObject(node.BackNode, obj, pos); } else { // Do front. TagNodesWithObject(node.FrontNode, obj, pos); } } }
/// <summary> /// Determines if one leaf node is visible from another. /// </summary> /// <param name="?"></param> /// <returns></returns> public bool IsLeafVisible(BspNode from, BspNode to) { if(to.VisCluster == -1) return false; if(from.VisCluster == -1) // Camera outside world? return true; if(!from.IsLeaf || !to.IsLeaf) throw new AxiomException("Both nodes must be leaf nodes for visibility testing."); // Use PVS to determine visibility /* // In wordier terms, the fairly cryptic (but fast) version is doing this: // Could make it a macro for even more speed? // Row offset = from cluster number * row size int offset = from->mVisCluster*mVisData.rowLength; // Column offset (in bytes) = to cluster number divided by 8 (since 8 bits per bytes) offset += to->mVisCluster >> 3; // Get the right bit within the byte, i.e. bitwise 'and' with bit at remainder position int result = *(mVisData.tableData + offset) & (1 << (to->mVisCluster & 7)); return (result != 0); */ byte visSet = visData.tableData[(from.VisCluster * visData.rowLength) + (to.VisCluster >> 3)]; int result = visSet & (1 << ((to.VisCluster) & 7)); return (result != 0); }
protected virtual void ProcessLeaf(BspNode leaf) { MovableObjectCollection objects = leaf.Objects; int numObjects = objects.Count; //Check sphere against objects for(int a = 0; a < numObjects; a++) { MovableObject obj = objects[a]; // Skip this object if collision not enabled if((obj.QueryFlags & queryMask) == 0) continue; //Test object as bounding box if(sphere.Intersects(obj.GetWorldBoundingBox())) { if (!foundIntersections.Contains(obj)) { listener.OnQueryResult(obj); foundIntersections.Add(obj); } } } PlaneBoundedVolume boundedVolume = new PlaneBoundedVolume(PlaneSide.Positive); // Check ray against brushes for (int brushPoint=0; brushPoint < leaf.SolidBrushes.Length; brushPoint++) { BspBrush brush = leaf.SolidBrushes[brushPoint]; if (brush == null) continue; boundedVolume.planes = brush.Planes; if(boundedVolume.Intersects(sphere)) { listener.OnQueryResult(brush.Fragment); } } }
protected virtual void ProcessLeaf( BspNode leaf ) { //Check sphere against objects foreach ( MovableObject obj in leaf.Objects.Values ) { // Skip this object if collision not enabled if ( ( obj.QueryFlags & queryMask ) == 0 ) { continue; } //Test object as bounding box if ( sphere.Intersects( obj.GetWorldBoundingBox() ) ) { if ( !this.foundIntersections.Contains( obj ) ) { this.listener.OnQueryResult( obj ); this.foundIntersections.Add( obj ); } } } var boundedVolume = new PlaneBoundedVolume( PlaneSide.Positive ); // Check ray against brushes for ( int brushPoint = 0; brushPoint < leaf.SolidBrushes.Length; brushPoint++ ) { BspBrush brush = leaf.SolidBrushes[ brushPoint ]; if ( brush == null ) { continue; } boundedVolume.planes = brush.Planes; if ( boundedVolume.Intersects( sphere ) ) { this.listener.OnQueryResult( brush.Fragment ); } } }
private void CreateNodes(Quake3Level q3lvl) { // Allocate memory for all nodes (leaves and splitters) nodes = new BspNode[q3lvl.NumNodes + q3lvl.NumLeaves]; numLeaves = q3lvl.NumLeaves; leafStart = q3lvl.NumNodes; // Run through and initialize the array so front/back node pointers // aren't null. for (int i = 0; i < nodes.Length; i++) { nodes[i] = new BspNode(); } // Convert nodes // In our array, first q3lvl.NumNodes are non-leaf, others are leaves for (int i = 0; i < q3lvl.NumNodes; i++) { BspNode node = nodes[i]; InternalBspNode q3node = q3lvl.Nodes[i]; node.IsLeaf = false; node.Owner = this; Plane splitPlane = new Plane(); // Set plane splitPlane.Normal = new Vector3( q3lvl.Planes[q3node.plane].normal[0], q3lvl.Planes[q3node.plane].normal[1], q3lvl.Planes[q3node.plane].normal[2] ); splitPlane.D = -q3lvl.Planes[q3node.plane].distance; node.SplittingPlane = splitPlane; // Set bounding box node.BoundingBox = new AxisAlignedBox( new Vector3( q3node.bbox[0], q3node.bbox[1], q3node.bbox[2] ), new Vector3( q3node.bbox[3], q3node.bbox[4], q3node.bbox[5] ) ); // Set back pointer // Negative indexes in Quake3 mean leaves. if (q3node.back < 0) { node.BackNode = nodes[leafStart + (~(q3node.back))]; } else { node.BackNode = nodes[q3node.back]; } // Set front pointer // Negative indexes in Quake3 mean leaves if (q3node.front < 0) { node.FrontNode = nodes[leafStart + (~(q3node.front))]; } else { node.FrontNode = nodes[q3node.front]; } } }
/// <summary> /// Overriden from SceneManager. /// </summary> /// <param name="position">The position at which to evaluate the list of lights</param> /// <param name="radius">The bounding radius to test</param> /// <param name="destList">List to be populated with ordered set of lights; will be cleared by this method before population.</param> protected override void PopulateLightList(Vector3 position, float radius, LightList destList) { BspNode positionNode = level.FindLeaf(position); BspNode[] lightNodes = new BspNode[lightList.Count]; for (int i = 0; i < lightList.Count; i++) { Light light = lightList[i]; lightNodes[i] = (BspNode) level.objectToNodeMap.FindFirst(light); } // Trawl of the lights that are visible from position, then sort destList.Clear(); float squaredRadius = radius * radius; // loop through the scene lights an add ones in range and visible from positionNode for(int i = 0; i < lightList.Count; i++) { TextureLight light = (TextureLight) lightList[i]; if(light.IsVisible && level.IsLeafVisible(positionNode, lightNodes[i])) { if(light.Type == LightType.Directional) { // no distance light.TempSquaredDist = 0.0f; destList.Add(light); } else { light.TempSquaredDist = (light.DerivedPosition - position).LengthSquared; light.TempSquaredDist -= squaredRadius; // only add in-range lights float range = light.AttenuationRange; if(light.TempSquaredDist <= (range * range)) { destList.Add(light); } } } // if } // for // Sort Destination light list. // TODO: Not needed yet since the current LightList is a sorted list under the hood already //destList.Sort(); }
protected virtual void ProcessLeaf(BspNode leaf, Ray tracingRay, float maxDistance, float traceDistance) { MovableObjectCollection objects = leaf.Objects; int numObjects = objects.Count; //Check ray against objects for(int a = 0; a < numObjects; a++) { MovableObject obj = objects[a]; // Skip this object if collision not enabled if((obj.QueryFlags & queryMask) == 0) continue; //Test object as bounding box IntersectResult result = tracingRay.Intersects(obj.GetWorldBoundingBox()); // if the result came back positive and intersection point is inside // the node, fire the event handler if(result.Hit && result.Distance <= maxDistance) { listener.OnQueryResult(obj, result.Distance + traceDistance); } } PlaneBoundedVolume boundedVolume = new PlaneBoundedVolume(PlaneSide.Positive); BspBrush intersectBrush = null; float intersectBrushDist = float.PositiveInfinity; // Check ray against brushes for (int brushPoint=0; brushPoint < leaf.SolidBrushes.Length; brushPoint++) { BspBrush brush = leaf.SolidBrushes[brushPoint]; if (brush == null) continue; boundedVolume.planes = brush.Planes; IntersectResult result = tracingRay.Intersects(boundedVolume); // if the result came back positive and intersection point is inside // the node, check if this brush is closer if(result.Hit && result.Distance <= maxDistance) { if (result.Distance < intersectBrushDist) { intersectBrushDist = result.Distance; intersectBrush = brush; } } } if (intersectBrush != null) { listener.OnQueryResult(intersectBrush.Fragment, intersectBrushDist + traceDistance); StopRayTracing = true; } }
protected virtual void ProcessLeaf( BspNode leaf, Ray tracingRay, float maxDistance, float traceDistance ) { //Check ray against objects foreach ( MovableObject obj in leaf.Objects.Values ) { // Skip this object if collision not enabled if ( ( obj.QueryFlags & queryMask ) == 0 ) { continue; } //Test object as bounding box IntersectResult result = tracingRay.Intersects( obj.GetWorldBoundingBox() ); // if the result came back positive and intersection point is inside // the node, fire the event handler if ( result.Hit && result.Distance <= maxDistance ) { this.listener.OnQueryResult( obj, result.Distance + traceDistance ); } } var boundedVolume = new PlaneBoundedVolume( PlaneSide.Positive ); BspBrush intersectBrush = null; float intersectBrushDist = float.PositiveInfinity; if ( ( QueryTypeMask & (ulong)SceneQueryTypeMask.WorldGeometry ) != 0 ) { // Check ray against brushes if ( ( QueryTypeMask & (ulong)SceneQueryTypeMask.WorldGeometry ) != 0 ) { for ( int brushPoint = 0; brushPoint < leaf.SolidBrushes.Length; brushPoint++ ) { BspBrush brush = leaf.SolidBrushes[ brushPoint ]; if ( brush == null ) { continue; } boundedVolume.planes = brush.Planes; IntersectResult result = tracingRay.Intersects( boundedVolume ); // if the result came back positive and intersection point is inside // the node, check if this brush is closer if ( result.Hit && result.Distance <= maxDistance ) { if ( result.Distance < intersectBrushDist ) { intersectBrushDist = result.Distance; intersectBrush = brush; } } } if ( intersectBrush != null ) { this.listener.OnQueryResult( intersectBrush.Fragment, intersectBrushDist + traceDistance ); this.StopRayTracing = true; } } } if ( intersectBrush != null ) { this.listener.OnQueryResult( intersectBrush.Fragment, intersectBrushDist + traceDistance ); this.StopRayTracing = true; } }
/// <summary> /// /** Internal utility function for loading data from Quake3. /// </summary> protected void LoadQuake3Level( Quake3Level q3lvl ) { ResourceGroupManager rgm = ResourceGroupManager.Instance; rgm.notifyWorldGeometryStageStarted( "Parsing entities" ); LoadEntities( q3lvl ); rgm.notifyWorldGeometryStageEnded(); rgm.notifyWorldGeometryStageStarted( "Extracting lightmaps" ); q3lvl.ExtractLightmaps(); rgm.notifyWorldGeometryStageEnded(); //----------------------------------------------------------------------- // Vertices //----------------------------------------------------------------------- // Allocate memory for vertices & copy vertexData = new VertexData(); // Create vertex declaration VertexDeclaration decl = vertexData.vertexDeclaration; int offset = 0; int lightTexOffset = 0; decl.AddElement( 0, offset, VertexElementType.Float3, VertexElementSemantic.Position ); offset += VertexElement.GetTypeSize( VertexElementType.Float3 ); decl.AddElement( 0, offset, VertexElementType.Float3, VertexElementSemantic.Normal ); offset += VertexElement.GetTypeSize( VertexElementType.Float3 ); decl.AddElement( 0, offset, VertexElementType.Float2, VertexElementSemantic.TexCoords, 0 ); offset += VertexElement.GetTypeSize( VertexElementType.Float2 ); decl.AddElement( 0, offset, VertexElementType.Float2, VertexElementSemantic.TexCoords, 1 ); // Build initial patches - we need to know how big the vertex buffer needs to be // to accommodate the subdivision // we don't want to include the elements for texture lighting, so we clone it rgm.notifyWorldGeometryStageStarted( "Initializing patches" ); InitQuake3Patches( q3lvl, (VertexDeclaration)decl.Clone() ); rgm.notifyWorldGeometryStageEnded(); // this is for texture lighting color and alpha decl.AddElement( 1, lightTexOffset, VertexElementType.Color, VertexElementSemantic.Diffuse ); lightTexOffset += VertexElement.GetTypeSize( VertexElementType.Color ); // this is for texture lighting coords decl.AddElement( 1, lightTexOffset, VertexElementType.Float2, VertexElementSemantic.TexCoords, 2 ); rgm.notifyWorldGeometryStageStarted( "Setting up vertex data" ); // Create the vertex buffer, allow space for patches HardwareVertexBuffer vbuf = HardwareBufferManager.Instance.CreateVertexBuffer( decl.Clone(0), q3lvl.NumVertices + patchVertexCount, BufferUsage.StaticWriteOnly /* the vertices will be read often for texture lighting, use shadow buffer */, true ); // Create the vertex buffer for texture lighting, allow space for patches HardwareVertexBuffer texLightBuf = HardwareBufferManager.Instance.CreateVertexBuffer( decl.Clone(1), q3lvl.NumVertices + patchVertexCount, BufferUsage.DynamicWriteOnly, false ); // COPY static vertex data - Note that we can't just block-copy the vertex data because we have to reorder // our vertex elements; this is to ensure compatibility with older cards when using // hardware vertex buffers - Direct3D requires that the buffer format maps onto a // FVF in those older drivers. // Lock just the non-patch area for now. unsafe { BspVertex vert = new BspVertex(); TextureLightMap texLightMap = new TextureLightMap(); // Keep another base pointer for use later in patch building for ( int v = 0; v < q3lvl.NumVertices; v++ ) { QuakeVertexToBspVertex( q3lvl.Vertices[ v ], out vert, out texLightMap ); BspVertex* bvptr = | TextureLightMap* tlptr = &texLightMap; vbuf.WriteData( v * sizeof( BspVertex ), sizeof( BspVertex ), (IntPtr)bvptr ); texLightBuf.WriteData( v * sizeof( TextureLightMap ), sizeof( TextureLightMap ), (IntPtr)tlptr ); } } // Setup binding vertexData.vertexBufferBinding.SetBinding( 0, vbuf ); // Setup texture lighting binding vertexData.vertexBufferBinding.SetBinding( 1, texLightBuf ); // Set other data vertexData.vertexStart = 0; vertexData.vertexCount = q3lvl.NumVertices + patchVertexCount; rgm.notifyWorldGeometryStageEnded(); //----------------------------------------------------------------------- // Faces //----------------------------------------------------------------------- rgm.notifyWorldGeometryStageStarted( "Setting up face data" ); leafFaceGroups = new int[ q3lvl.LeafFaces.Length ]; Array.Copy( q3lvl.LeafFaces, 0, leafFaceGroups, 0, leafFaceGroups.Length ); faceGroups = new BspStaticFaceGroup[ q3lvl.Faces.Length ]; // Set up index buffer // NB Quake3 indexes are 32-bit // Copy the indexes into a software area for staging numIndexes = q3lvl.NumElements + patchIndexCount; // Create an index buffer manually in system memory, allow space for patches indexes = HardwareBufferManager.Instance.CreateIndexBuffer( IndexType.Size32, numIndexes, BufferUsage.Dynamic ); // Write main indexes indexes.WriteData( 0, Marshal.SizeOf( typeof( uint ) ) * q3lvl.NumElements, q3lvl.Elements, true ); rgm.notifyWorldGeometryStageEnded(); // now build patch information rgm.notifyWorldGeometryStageStarted( "Building patches" ); BuildQuake3Patches( q3lvl.NumVertices, q3lvl.NumElements ); rgm.notifyWorldGeometryStageEnded(); //----------------------------------------------------------------------- // Create materials for shaders //----------------------------------------------------------------------- // NB this only works for the 'default' shaders for now // i.e. those that don't have a .shader script and thus default // to just texture + lightmap // TODO: pre-parse all .shader files and create lookup for next stage (use ROGL shader_file_t) // Material names are shadername#lightmapnumber // This is because I like to define materials up front completely // rather than combine lightmap and shader dynamically (it's // more generic). It results in more materials, but they're small // beer anyway. Texture duplication is prevented by infrastructure. // To do this I actually need to parse the faces since they have the // shader/lightmap combo (lightmap number is not in the shader since // it can be used with multiple lightmaps) string shaderName; int face = q3lvl.Faces.Length; int progressCountdown = 100; int progressCount = 0; while ( face-- > 0 ) { // Progress reporting if ( progressCountdown == 100 ) { ++progressCount; String str = String.Format( "Loading materials (phase {0})", progressCount ); rgm.notifyWorldGeometryStageStarted( str ); } else if ( progressCountdown == 0 ) { // stage report rgm.notifyWorldGeometryStageEnded(); progressCountdown = 100 + 1; } // Check to see if existing material // Format shader#lightmap int shadIdx = q3lvl.Faces[ face ].shader; shaderName = String.Format( "{0}#{1}", q3lvl.Shaders[ shadIdx ].name, q3lvl.Faces[ face ].lmTexture ); Material shadMat = (Material)MaterialManager.Instance.GetByName( shaderName ); if ( shadMat == null && !bspOptions.useLightmaps ) { // try the no-lightmap material shaderName = String.Format( "{0}#n", q3lvl.Shaders[ shadIdx ].name ); shadMat = (Material)MaterialManager.Instance.GetByName( shaderName ); } if ( shadMat == null ) { // Color layer // NB no extension in Q3A(doh), have to try shader, .jpg, .tga string tryName = q3lvl.Shaders[ shadIdx ].name; // Try shader first Quake3Shader shader = (Quake3Shader)Quake3ShaderManager.Instance.GetByName( tryName ); if ( shader != null ) { shadMat = shader.CreateAsMaterial( q3lvl.Faces[ face ].lmTexture ); } else { // No shader script, try default type texture shadMat = (Material)MaterialManager.Instance.Create( shaderName, rgm.WorldResourceGroupName ); Pass shadPass = shadMat.GetTechnique( 0 ).GetPass( 0 ); // Try jpg TextureUnitState tex = null; if ( ResourceGroupManager.Instance.ResourceExists( rgm.WorldResourceGroupName, tryName + ".jpg" ) ) { tex = shadPass.CreateTextureUnitState( tryName + ".jpg" ); } if ( ResourceGroupManager.Instance.ResourceExists( rgm.WorldResourceGroupName, tryName + ".tga" ) ) { tex = shadPass.CreateTextureUnitState( tryName + ".tga" ); } if ( tex != null ) { // Set replace on all first layer textures for now tex.SetColorOperation( LayerBlendOperation.Replace ); tex.SetTextureAddressingMode( TextureAddressing.Wrap ); // for ambient lighting tex.ColorBlendMode.source2 = LayerBlendSource.Manual; } if ( bspOptions.useLightmaps && q3lvl.Faces[ face ].lmTexture != -1 ) { // Add lightmap, additive blending tex = shadPass.CreateTextureUnitState( String.Format( "@lightmap{0}", q3lvl.Faces[ face ].lmTexture ) ); // Blend tex.SetColorOperation( LayerBlendOperation.Modulate ); // Use 2nd texture co-ordinate set tex.TextureCoordSet = 1; // Clamp tex.SetTextureAddressingMode( TextureAddressing.Clamp ); } shadMat.CullingMode = CullingMode.None; shadMat.Lighting = false; } } shadMat.Load(); // Copy face data BspStaticFaceGroup dest = new BspStaticFaceGroup(); InternalBspFace src = q3lvl.Faces[ face ]; if ( ( q3lvl.Shaders[ src.shader ].surfaceFlags & SurfaceFlags.Sky ) == SurfaceFlags.Sky ) dest.isSky = true; else dest.isSky = false; dest.materialHandle = shadMat.Handle; dest.elementStart = src.elemStart; dest.numElements = src.elemCount; dest.numVertices = src.vertCount; dest.vertexStart = src.vertStart; dest.plane = new Plane(); if ( Quake3ShaderManager.Instance.GetByName( q3lvl.Shaders[ shadIdx ].name ) != null ) { // it's a quake shader dest.isQuakeShader = true; } if ( src.type == BspFaceType.Normal ) { dest.type = FaceGroup.FaceList; // Assign plane dest.plane.Normal = new Vector3( src.normal[ 0 ], src.normal[ 1 ], src.normal[ 2 ] ); dest.plane.D = -dest.plane.Normal.Dot( new Vector3( src.org[ 0 ], src.org[ 1 ], src.org[ 2 ] ) ); // Don't rebase indexes here - Quake3 re-uses some indexes for multiple vertex // groups eg repeating small details have the same relative vertex data but // use the same index data. } else if ( src.type == BspFaceType.Patch ) { // Seems to be some crap in the Q3 level where vertex count = 0 or num control points = 0? if ( ( dest.numVertices == 0 ) || ( src.meshCtrl[ 0 ] == 0 ) ) { dest.type = FaceGroup.Unknown; } else { // Set up patch surface dest.type = FaceGroup.Patch; // Locate the patch we already built if ( !patches.ContainsKey( face ) ) throw new AxiomException( "Patch not found from previous built state." ); dest.patchSurf = (PatchSurface)patches[ face ]; } } else if ( src.type == BspFaceType.Mesh ) { dest.type = FaceGroup.FaceList; // Assign plane dest.plane.Normal = new Vector3( src.normal[ 0 ], src.normal[ 1 ], src.normal[ 2 ] ); dest.plane.D = -dest.plane.Normal.Dot( new Vector3( src.org[ 0 ], src.org[ 1 ], src.org[ 2 ] ) ); } else { LogManager.Instance.Write( "!!! Unknown face type !!!" ); } faceGroups[ face ] = dest; } //----------------------------------------------------------------------- // Nodes //----------------------------------------------------------------------- // Allocate memory for all nodes (leaves and splitters) nodes = new BspNode[ q3lvl.NumNodes + q3lvl.NumLeaves ]; numLeaves = q3lvl.NumLeaves; leafStart = q3lvl.NumNodes; // Run through and initialize the array so front/back node pointers // aren't null. for ( int i = 0; i < nodes.Length; i++ ) nodes[ i ] = new BspNode(); // Convert nodes // In our array, first q3lvl.NumNodes are non-leaf, others are leaves for ( int i = 0; i < q3lvl.NumNodes; i++ ) { BspNode node = nodes[ i ]; InternalBspNode q3node = q3lvl.Nodes[ i ]; node.IsLeaf = false; node.Owner = this; Plane splitPlane = new Plane(); // Set plane splitPlane.Normal = new Vector3( q3lvl.Planes[ q3node.plane ].normal[ 0 ], q3lvl.Planes[ q3node.plane ].normal[ 1 ], q3lvl.Planes[ q3node.plane ].normal[ 2 ] ); splitPlane.D = -q3lvl.Planes[ q3node.plane ].distance; node.SplittingPlane = splitPlane; // Set bounding box node.BoundingBox = new AxisAlignedBox( new Vector3( q3node.bbox[ 0 ], q3node.bbox[ 1 ], q3node.bbox[ 2 ] ), new Vector3( q3node.bbox[ 3 ], q3node.bbox[ 4 ], q3node.bbox[ 5 ] ) ); // Set back pointer // Negative indexes in Quake3 mean leaves. if ( q3node.back < 0 ) node.BackNode = nodes[ leafStart + ( ~( q3node.back ) ) ]; else node.BackNode = nodes[ q3node.back ]; // Set front pointer // Negative indexes in Quake3 mean leaves if ( q3node.front < 0 ) node.FrontNode = nodes[ leafStart + ( ~( q3node.front ) ) ]; else node.FrontNode = nodes[ q3node.front ]; } //----------------------------------------------------------------------- // Brushes //----------------------------------------------------------------------- // Reserve enough memory for all brushes, solid or not (need to maintain indexes) brushes = new BspBrush[ q3lvl.NumBrushes ]; for ( int i = 0; i < q3lvl.NumBrushes; i++ ) { InternalBspBrush q3brush = q3lvl.Brushes[ i ]; // Create a new OGRE brush BspBrush brush = new BspBrush(); int numBrushSides = q3brush.numSides; int brushSideIdx = q3brush.firstSide; // Iterate over the sides and create plane for each while ( numBrushSides-- > 0 ) { InternalBspPlane side = q3lvl.Planes[ q3lvl.BrushSides[ brushSideIdx ].planeNum ]; // Notice how we normally invert Q3A plane distances, but here we do not // Because we want plane normals pointing out of solid brushes, not in. Plane brushSide = new Plane( new Vector3( q3lvl.Planes[ q3lvl.BrushSides[ brushSideIdx ].planeNum ].normal[ 0 ], q3lvl.Planes[ q3lvl.BrushSides[ brushSideIdx ].planeNum ].normal[ 1 ], q3lvl.Planes[ q3lvl.BrushSides[ brushSideIdx ].planeNum ].normal[ 2 ] ), q3lvl.Planes[ q3lvl.BrushSides[ brushSideIdx ].planeNum ].distance ); brush.Planes.Add( brushSide ); brushSideIdx++; } // Build world fragment brush.Fragment.FragmentType = WorldFragmentType.PlaneBoundedRegion; brush.Fragment.Planes = brush.Planes; brushes[ i ] = brush; } //----------------------------------------------------------------------- // Leaves //----------------------------------------------------------------------- for ( int i = 0; i < q3lvl.NumLeaves; ++i ) { BspNode node = nodes[ i + this.LeafStart ]; InternalBspLeaf q3leaf = q3lvl.Leaves[ i ]; node.IsLeaf = true; node.Owner = this; // Set bounding box node.BoundingBox.Minimum = new Vector3( q3leaf.bbox[ 0 ], q3leaf.bbox[ 1 ], q3leaf.bbox[ 2 ] ); node.BoundingBox.Maximum = new Vector3( q3leaf.bbox[ 3 ], q3leaf.bbox[ 4 ], q3leaf.bbox[ 5 ] ); // Set faces node.FaceGroupStart = q3leaf.faceStart; node.NumFaceGroups = q3leaf.faceCount; node.VisCluster = q3leaf.cluster; // Load Brushes for this leaf int realBrushIdx = 0, solidIdx = 0; int brushCount = q3leaf.brushCount; int brushIdx = q3leaf.brushStart; node.SolidBrushes = new BspBrush[ brushCount ]; while ( brushCount-- > 0 ) { realBrushIdx = q3lvl.LeafBrushes[ brushIdx ]; InternalBspBrush q3brush = q3lvl.Brushes[ realBrushIdx ]; // Only load solid ones, we don't care about any other types // Shader determines this. InternalBspShader brushShader = q3lvl.Shaders[ q3brush.shaderIndex ]; if ( ( brushShader.contentFlags & ContentFlags.Solid ) == ContentFlags.Solid ) node.SolidBrushes[ solidIdx ] = brushes[ realBrushIdx ]; brushIdx++; solidIdx++; } } // Vis - just copy visData.numClusters = q3lvl.VisData.clusterCount; visData.rowLength = q3lvl.VisData.rowSize; visData.tableData = new byte[ q3lvl.VisData.rowSize * q3lvl.VisData.clusterCount ]; Array.Copy( q3lvl.VisData.data, 0, visData.tableData, 0, visData.tableData.Length ); }
protected virtual void ProcessNode( BspNode node ) { if ( node.IsLeaf ) { ProcessLeaf( node ); return; } float distance = node.GetDistance( sphere.Center ); if ( Utility.Abs( distance ) < sphere.Radius ) { // Sphere crosses the plane, do both. ProcessNode( node.BackNode ); ProcessNode( node.FrontNode ); } else if ( distance < 0 ) { // Do back. ProcessNode( node.BackNode ); } else { // Do front. ProcessNode( node.FrontNode ); } }
/// <summary> /// Determines if the passed in node (must also be a leaf) is visible from this leaf. /// </summary> /// <remarks> /// Must only be called on a leaf node, and the parameter must also be a leaf node. If /// this method returns true, then the leaf passed in is visible from this leaf. /// Note that internally this uses the Potentially Visible Set (PVS) which is precalculated /// and stored with the BSP level. /// </remarks> public bool IsLeafVisible(BspNode leaf) { return(owner.IsLeafVisible(this, leaf)); }
/// <summary> /// Tags geometry in the leaf specified for later rendering. /// </summary> protected void ProcessVisibleLeaf( BspNode leaf, Camera camera, bool onlyShadowCasters ) { // Skip world geometry if we're only supposed to process shadow casters // World is pre-lit if ( !onlyShadowCasters ) { // Parse the leaf node's faces, add face groups to material map int numGroups = leaf.NumFaceGroups; int idx = leaf.FaceGroupStart; while ( numGroups-- > 0 ) { int realIndex = this.level.LeafFaceGroups[ idx++ ]; // Is it already checked ? if ( this.faceGroupChecked.ContainsKey( realIndex ) && this.faceGroupChecked[ realIndex ] == true ) { continue; } this.faceGroupChecked[ realIndex ] = true; BspStaticFaceGroup faceGroup = this.level.FaceGroups[ realIndex ]; // Get Material reference by handle Material mat = GetMaterial( faceGroup.materialHandle ); // Check normal (manual culling) ManualCullingMode cullMode = mat.GetTechnique( 0 ).GetPass( 0 ).ManualCullingMode; if ( cullMode != ManualCullingMode.None ) { float dist = faceGroup.plane.GetDistance( camera.DerivedPosition ); if ( ( ( dist < 0 ) && ( cullMode == ManualCullingMode.Back ) ) || ( ( dist > 0 ) && ( cullMode == ManualCullingMode.Front ) ) ) { continue; } } // Try to insert, will find existing if already there this.matFaceGroupMap.Add( mat, faceGroup ); } } // Add movables to render queue, provided it hasn't been seen already. foreach ( MovableObject obj in leaf.Objects.Values ) { if ( !this.objectsForRendering.ContainsKey( obj.Name ) ) { if ( obj.IsVisible && ( !onlyShadowCasters || obj.CastShadows ) && camera.IsObjectVisible( obj.GetWorldBoundingBox() ) ) { obj.NotifyCurrentCamera( camera ); obj.UpdateRenderQueue( renderQueue ); // Check if the bounding box should be shown. var node = (SceneNode)obj.ParentNode; if ( node.ShowBoundingBox || showBoundingBoxes ) { node.AddBoundingBoxToQueue( renderQueue ); } this.objectsForRendering.Add( obj ); } } } }
/// <summary> /// Walks the BSP tree looking for the node which the camera is in, and tags any geometry /// which is in a visible leaf for later processing. /// </summary> protected BspNode WalkTree(Camera camera, bool onlyShadowCasters) { // Locate the leaf node where the camera is located BspNode cameraNode = level.FindLeaf(camera.DerivedPosition); matFaceGroupMap.Clear(); faceGroupChecked = new bool[level.FaceGroups.Length]; TextureLight[] lights = new TextureLight[lightList.Count]; BspNode[] lightNodes = new BspNode[lightList.Count]; Sphere[] lightSpheres = new Sphere[lightList.Count]; // The base SceneManager uses this for shadows. // The BspSceneManager uses this for texture lighting as well. if (shadowTechnique == ShadowTechnique.None) { lightsAffectingFrustum.Clear(); lightAddedToFrustum = new bool[lightList.Count]; } for (int lp=0; lp < lightList.Count; lp++) { TextureLight light = (TextureLight) lightList[lp]; lights[lp] = light; lightNodes[lp] = (BspNode) level.objectToNodeMap.FindFirst(light); if (light.Type != LightType.Directional) { // treating spotlight as point for simplicity lightSpheres[lp] = new Sphere(light.DerivedPosition, light.AttenuationRange); } } // Scan through all the other leaf nodes looking for visibles int i = level.NumNodes - level.LeafStart; int p = level.LeafStart; BspNode node; while(i-- > 0) { node = level.Nodes[p]; if(level.IsLeafVisible(cameraNode, node)) { // Visible according to PVS, check bounding box against frustum FrustumPlane plane; if(camera.IsObjectVisible(node.BoundingBox, out plane)) { if (!onlyShadowCasters) { for (int lp=0; lp < lights.Length; lp++) { if (lightAddedToFrustum[lp] || !lights[lp].IsVisible) continue; if (level.IsLeafVisible(lightNodes[lp], node) && (lights[lp].Type == LightType.Directional || lightSpheres[lp].Intersects(node.BoundingBox))) { // This is set so that the lights are rendered with ascending // Priority order. lights[lp].TempSquaredDist = lights[lp].Priority; lightsAffectingFrustum.Add(lights[lp]); lightAddedToFrustum[lp] = true; } } } ProcessVisibleLeaf(node, camera, onlyShadowCasters); if(showNodeAABs) AddBoundingBox(node.BoundingBox, true); } } p++; } return cameraNode; }