private void CreateBrushes(Quake3Level q3lvl) { // 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; } }
private void CreateBrushes(Quake3Level q3lvl) { // 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; } }
/// <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 ); }