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 )
{
// Colour 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 );
}