#include "IrrCompileConfig.h"
#ifdef _IRR_COMPILE_WITH_BSP_LOADER_
#include "CQ3LevelMesh.h"
#include "ISceneManager.h"
#include "os.h"
#include "SMeshBufferLightMap.h"
#include "irrString.h"
#include "ILightSceneNode.h"
#include "IQ3Shader.h"
#include "IFileList.h"
namespace irr
{
namespace scene
{
using namespace quake3;
CQ3LevelMesh::CQ3LevelMesh(io::IFileSystem* fs, scene::ISceneManager* smgr,
const Q3LevelLoadParameter &loadParam)
: LoadParam(loadParam), Textures(0), NumTextures(0), LightMaps(0), NumLightMaps(0),
Vertices(0), NumVertices(0), Faces(0), NumFaces(0), Planes(0), NumPlanes(0),
Nodes(0), NumNodes(0), Leafs(0), NumLeafs(0), LeafFaces(0), NumLeafFaces(0),
MeshVerts(0), NumMeshVerts(0), Brushes(0), NumBrushes(0), FileSystem(fs), SceneManager(smgr)
{
#ifdef _DEBUG
IReferenceCounted::setDebugName("CQ3LevelMesh");
#endif
for ( s32 i = 0; i!= E_Q3_MESH_SIZE; ++i )
{
Mesh[i] = 0;
}
Driver = smgr ? smgr->getVideoDriver() : 0;
if (Driver)
Driver->grab();
if (FileSystem)
FileSystem->grab();
InitShader();
}
CQ3LevelMesh::~CQ3LevelMesh()
{
cleanLoader ();
if (Driver)
Driver->drop();
if (FileSystem)
FileSystem->drop();
for ( s32 i = 0; i!= E_Q3_MESH_SIZE; ++i )
{
if ( Mesh[i] )
{
Mesh[i]->drop();
Mesh[i] = 0;
}
}
ReleaseShader();
ReleaseEntity();
}
bool CQ3LevelMesh::loadFile(io::IReadFile* file)
{
if (!file)
return false;
LevelName = file->getFileName();
file->read(&header, sizeof(tBSPHeader));
#ifdef __BIG_ENDIAN__
header.strID = os::Byteswap::byteswap(header.strID);
header.version = os::Byteswap::byteswap(header.version);
#endif
if ( (header.strID != 0x50534249 ||
( header.version != 0x2e
&& header.version != 0x2f
)
)
&&
( header.strID != 0x50534252 || header.version != 1 )
)
{
os::Printer::log("Could not load .bsp file, unknown header.", file->getFileName(), ELL_ERROR);
return false;
}
#if 0
if ( header.strID == 0x50534252 )
{
LoadParam.swapHeader = 1;
}
#endif
file->read(&Lumps[0], sizeof(tBSPLump)*kMaxLumps);
s32 i;
if ( LoadParam.swapHeader )
{
for ( i=0; i< kMaxLumps;++i)
{
Lumps[i].offset = os::Byteswap::byteswap(Lumps[i].offset);
Lumps[i].length = os::Byteswap::byteswap(Lumps[i].length);
}
}
for ( i = 0; i!= E_Q3_MESH_SIZE; ++i )
{
Mesh[i] = new SMesh();
}
ReleaseEntity();
loadEntities(&Lumps[kEntities], file);
loadTextures(&Lumps[kShaders], file);
loadLightmaps(&Lumps[kLightmaps], file);
loadVerts(&Lumps[kVertices], file);
loadFaces(&Lumps[kFaces], file);
loadPlanes(&Lumps[kPlanes], file);
loadNodes(&Lumps[kNodes], file);
loadLeafs(&Lumps[kLeafs], file);
loadLeafFaces(&Lumps[kLeafFaces], file);
loadVisData(&Lumps[kVisData], file);
loadModels(&Lumps[kModels], file);
loadMeshVerts(&Lumps[kMeshVerts], file);
loadBrushes(&Lumps[kBrushes], file);
loadBrushSides(&Lumps[kBrushSides], file);
loadLeafBrushes(&Lumps[kLeafBrushes], file);
loadFogs(&Lumps[kFogs], file );
loadTextures();
constructMesh();
solveTJunction();
cleanMeshes();
calcBoundingBoxes();
cleanLoader();
return true;
}
void CQ3LevelMesh::cleanLoader ()
{
delete [] Textures; Textures = 0;
delete [] LightMaps; LightMaps = 0;
delete [] Vertices; Vertices = 0;
delete [] Faces; Faces = 0;
delete [] Planes; Planes = 0;
delete [] Nodes; Nodes = 0;
delete [] Leafs; Leafs = 0;
delete [] LeafFaces; LeafFaces = 0;
delete [] MeshVerts; MeshVerts = 0;
delete [] Brushes; Brushes = 0;
Lightmap.clear();
Tex.clear();
}
u32 CQ3LevelMesh::getFrameCount() const
{
return 1;
}
IMesh* CQ3LevelMesh::getMesh(s32 frameInMs, s32 detailLevel, s32 startFrameLoop, s32 endFrameLoop)
{
return Mesh[frameInMs];
}
void CQ3LevelMesh::loadTextures(tBSPLump* l, io::IReadFile* file)
{
NumTextures = l->length / sizeof(tBSPTexture);
if ( !NumTextures )
return;
Textures = new tBSPTexture[NumTextures];
file->seek(l->offset);
file->read(Textures, l->length);
if ( LoadParam.swapHeader )
{
for (s32 i=0;i<NumTextures;++i)
{
Textures[i].flags = os::Byteswap::byteswap(Textures[i].flags);
Textures[i].contents = os::Byteswap::byteswap(Textures[i].contents);
}
}
}
void CQ3LevelMesh::loadLightmaps(tBSPLump* l, io::IReadFile* file)
{
NumLightMaps = l->length / sizeof(tBSPLightmap);
if ( !NumLightMaps )
return;
LightMaps = new tBSPLightmap[NumLightMaps];
file->seek(l->offset);
file->read(LightMaps, l->length);
}
void CQ3LevelMesh::loadVerts(tBSPLump* l, io::IReadFile* file)
{
NumVertices = l->length / sizeof(tBSPVertex);
if ( !NumVertices )
return;
Vertices = new tBSPVertex[NumVertices];
file->seek(l->offset);
file->read(Vertices, l->length);
if ( LoadParam.swapHeader )
for (s32 i=0;i<NumVertices;i++)
{
Vertices[i].vPosition[0] = os::Byteswap::byteswap(Vertices[i].vPosition[0]);
Vertices[i].vPosition[1] = os::Byteswap::byteswap(Vertices[i].vPosition[1]);
Vertices[i].vPosition[2] = os::Byteswap::byteswap(Vertices[i].vPosition[2]);
Vertices[i].vTextureCoord[0] = os::Byteswap::byteswap(Vertices[i].vTextureCoord[0]);
Vertices[i].vTextureCoord[1] = os::Byteswap::byteswap(Vertices[i].vTextureCoord[1]);
Vertices[i].vLightmapCoord[0] = os::Byteswap::byteswap(Vertices[i].vLightmapCoord[0]);
Vertices[i].vLightmapCoord[1] = os::Byteswap::byteswap(Vertices[i].vLightmapCoord[1]);
Vertices[i].vNormal[0] = os::Byteswap::byteswap(Vertices[i].vNormal[0]);
Vertices[i].vNormal[1] = os::Byteswap::byteswap(Vertices[i].vNormal[1]);
Vertices[i].vNormal[2] = os::Byteswap::byteswap(Vertices[i].vNormal[2]);
}
}
void CQ3LevelMesh::loadFaces(tBSPLump* l, io::IReadFile* file)
{
NumFaces = l->length / sizeof(tBSPFace);
if (!NumFaces)
return;
Faces = new tBSPFace[NumFaces];
file->seek(l->offset);
file->read(Faces, l->length);
if ( LoadParam.swapHeader )
{
for ( s32 i=0;i<NumFaces;i++)
{
Faces[i].textureID = os::Byteswap::byteswap(Faces[i].textureID);
Faces[i].fogNum = os::Byteswap::byteswap(Faces[i].fogNum);
Faces[i].type = os::Byteswap::byteswap(Faces[i].type);
Faces[i].vertexIndex = os::Byteswap::byteswap(Faces[i].vertexIndex);
Faces[i].numOfVerts = os::Byteswap::byteswap(Faces[i].numOfVerts);
Faces[i].meshVertIndex = os::Byteswap::byteswap(Faces[i].meshVertIndex);
Faces[i].numMeshVerts = os::Byteswap::byteswap(Faces[i].numMeshVerts);
Faces[i].lightmapID = os::Byteswap::byteswap(Faces[i].lightmapID);
Faces[i].lMapCorner[0] = os::Byteswap::byteswap(Faces[i].lMapCorner[0]);
Faces[i].lMapCorner[1] = os::Byteswap::byteswap(Faces[i].lMapCorner[1]);
Faces[i].lMapSize[0] = os::Byteswap::byteswap(Faces[i].lMapSize[0]);
Faces[i].lMapSize[1] = os::Byteswap::byteswap(Faces[i].lMapSize[1]);
Faces[i].lMapPos[0] = os::Byteswap::byteswap(Faces[i].lMapPos[0]);
Faces[i].lMapPos[1] = os::Byteswap::byteswap(Faces[i].lMapPos[1]);
Faces[i].lMapPos[2] = os::Byteswap::byteswap(Faces[i].lMapPos[2]);
Faces[i].lMapBitsets[0][0] = os::Byteswap::byteswap(Faces[i].lMapBitsets[0][0]);
Faces[i].lMapBitsets[0][1] = os::Byteswap::byteswap(Faces[i].lMapBitsets[0][1]);
Faces[i].lMapBitsets[0][2] = os::Byteswap::byteswap(Faces[i].lMapBitsets[0][2]);
Faces[i].lMapBitsets[1][0] = os::Byteswap::byteswap(Faces[i].lMapBitsets[1][0]);
Faces[i].lMapBitsets[1][1] = os::Byteswap::byteswap(Faces[i].lMapBitsets[1][1]);
Faces[i].lMapBitsets[1][2] = os::Byteswap::byteswap(Faces[i].lMapBitsets[1][2]);
Faces[i].vNormal[0] = os::Byteswap::byteswap(Faces[i].vNormal[0]);
Faces[i].vNormal[1] = os::Byteswap::byteswap(Faces[i].vNormal[1]);
Faces[i].vNormal[2] = os::Byteswap::byteswap(Faces[i].vNormal[2]);
Faces[i].size[0] = os::Byteswap::byteswap(Faces[i].size[0]);
Faces[i].size[1] = os::Byteswap::byteswap(Faces[i].size[1]);
}
}
}
void CQ3LevelMesh::loadPlanes(tBSPLump* l, io::IReadFile* file)
{
}
void CQ3LevelMesh::loadNodes(tBSPLump* l, io::IReadFile* file)
{
}
void CQ3LevelMesh::loadLeafs(tBSPLump* l, io::IReadFile* file)
{
}
void CQ3LevelMesh::loadLeafFaces(tBSPLump* l, io::IReadFile* file)
{
}
void CQ3LevelMesh::loadVisData(tBSPLump* l, io::IReadFile* file)
{
}
void CQ3LevelMesh::loadEntities(tBSPLump* l, io::IReadFile* file)
{
core::array<u8> entity;
entity.set_used( l->length + 2 );
entity[l->length + 1 ] = 0;
file->seek(l->offset);
file->read( entity.pointer(), l->length);
parser_parse( entity.pointer(), l->length, &CQ3LevelMesh::scriptcallback_entity );
}
void CQ3LevelMesh::loadFogs(tBSPLump* l, io::IReadFile* file)
{
u32 files = l->length / sizeof(tBSPFog);
file->seek( l->offset );
tBSPFog fog;
const IShader *shader;
STexShader t;
for ( u32 i = 0; i!= files; ++i )
{
file->read( &fog, sizeof( fog ) );
shader = getShader( fog.shader );
t.Texture = 0;
t.ShaderID = shader ? shader->ID : -1;
FogMap.push_back ( t );
}
}
void CQ3LevelMesh::loadModels(tBSPLump* l, io::IReadFile* file)
{
u32 files = l->length / sizeof(tBSPModel);
file->seek( l->offset );
tBSPModel def;
for ( u32 i = 0; i!= files; ++i )
{
file->read( &def, sizeof( def ) );
}
}
void CQ3LevelMesh::loadMeshVerts(tBSPLump* l, io::IReadFile* file)
{
NumMeshVerts = l->length / sizeof(s32);
if (!NumMeshVerts)
return;
MeshVerts = new s32[NumMeshVerts];
file->seek(l->offset);
file->read(MeshVerts, l->length);
if ( LoadParam.swapHeader )
{
for (int i=0;i<NumMeshVerts;i++)
MeshVerts[i] = os::Byteswap::byteswap(MeshVerts[i]);
}
}
void CQ3LevelMesh::loadBrushes(tBSPLump* l, io::IReadFile* file)
{
}
void CQ3LevelMesh::loadBrushSides(tBSPLump* l, io::IReadFile* file)
{
}
void CQ3LevelMesh::loadLeafBrushes(tBSPLump* l, io::IReadFile* file)
{
}
inline bool isQ3WhiteSpace( const u8 symbol )
{
return symbol == ' ' || symbol == '\t' || symbol == '\r';
}
inline bool isQ3ValidName( const u8 symbol )
{
return (symbol >= 'a' && symbol <= 'z' ) ||
(symbol >= 'A' && symbol <= 'Z' ) ||
(symbol >= '0' && symbol <= '9' ) ||
(symbol == '/' || symbol == '_' || symbol == '.' );
}
void CQ3LevelMesh::parser_nextToken()
{
u8 symbol;
Parser.token = "";
Parser.tokenresult = Q3_TOKEN_UNRESOLVED;
do
{
if ( Parser.index >= Parser.sourcesize )
{
Parser.tokenresult = Q3_TOKEN_EOF;
return;
}
symbol = Parser.source [ Parser.index ];
Parser.index += 1;
} while ( isQ3WhiteSpace( symbol ) );
switch ( symbol )
{
case 0:
Parser.tokenresult = Q3_TOKEN_EOF;
return;
case '/':
if ( Parser.index >= Parser.sourcesize )
{
Parser.tokenresult = Q3_TOKEN_EOF;
return;
}
symbol = Parser.source [ Parser.index ];
Parser.index += 1;
if ( isQ3WhiteSpace( symbol ) )
{
Parser.tokenresult = Q3_TOKEN_MATH_DIVIDE;
return;
}
else
if ( symbol == '*' )
{
}
else
if ( symbol == '/' )
{
do
{
if ( Parser.index >= Parser.sourcesize )
{
Parser.tokenresult = Q3_TOKEN_EOF;
return;
}
symbol = Parser.source [ Parser.index ];
Parser.index += 1;
} while ( symbol != '\n' );
Parser.tokenresult = Q3_TOKEN_COMMENT;
return;
}
break;
case '\n':
Parser.tokenresult = Q3_TOKEN_EOL;
return;
case '{':
Parser.tokenresult = Q3_TOKEN_START_LIST;
return;
case '}':
Parser.tokenresult = Q3_TOKEN_END_LIST;
return;
case '"':
do
{
if ( Parser.index >= Parser.sourcesize )
{
Parser.tokenresult = Q3_TOKEN_EOF;
return;
}
symbol = Parser.source [ Parser.index ];
Parser.index += 1;
if ( symbol != '"' )
Parser.token.append( symbol );
} while ( symbol != '"' );
Parser.tokenresult = Q3_TOKEN_ENTITY;
return;
}
Parser.token.append( symbol );
bool validName = true;
do
{
if ( Parser.index >= Parser.sourcesize )
{
Parser.tokenresult = Q3_TOKEN_EOF;
return;
}
symbol = Parser.source [ Parser.index ];
validName = isQ3ValidName( symbol );
if ( validName )
{
Parser.token.append( symbol );
Parser.index += 1;
}
} while ( validName );
Parser.tokenresult = Q3_TOKEN_TOKEN;
return;
}
void CQ3LevelMesh::parser_parse( const void * data, const u32 size, CQ3LevelMesh::tParserCallback callback )
{
Parser.source = static_cast<const c8*>(data);
Parser.sourcesize = size;
Parser.index = 0;
SVarGroupList *groupList;
s32 active;
s32 last;
SVariable entity ( "" );
groupList = new SVarGroupList();
groupList->VariableGroup.push_back( SVarGroup() );
active = last = 0;
do
{
parser_nextToken();
switch ( Parser.tokenresult )
{
case Q3_TOKEN_START_LIST:
{
groupList->VariableGroup.push_back( SVarGroup() );
last = active;
active = groupList->VariableGroup.size() - 1;
entity.clear();
} break;
case Q3_TOKEN_EOL:
{
if ( entity.isValid() )
{
groupList->VariableGroup[active].Variable.push_back( entity );
entity.clear();
}
} break;
case Q3_TOKEN_TOKEN:
case Q3_TOKEN_ENTITY:
{
Parser.token.make_lower();
if ( 0 == entity.isValid() )
{
entity.name = Parser.token;
entity.content = "";
}
else
{
if ( entity.content.size() )
{
entity.content += " ";
}
entity.content += Parser.token;
}
} break;
case Q3_TOKEN_END_LIST:
{
if ( active == 1 )
{
(this->*callback)( groupList, Q3_TOKEN_END_LIST );
groupList->drop();
groupList = new SVarGroupList();
groupList->VariableGroup.push_back( SVarGroup() );
last = 0;
}
active = last;
entity.clear();
} break;
default:
break;
}
} while ( Parser.tokenresult != Q3_TOKEN_EOF );
(this->*callback)( groupList, Q3_TOKEN_EOF );
groupList->drop();
}
s32 CQ3LevelMesh::setShaderFogMaterial( video::SMaterial &material, const tBSPFace * face ) const
{
material.MaterialType = video::EMT_SOLID;
material.Wireframe = false;
material.Lighting = false;
material.BackfaceCulling = false;
material.setTexture(0, 0);
material.setTexture(1, 0);
material.setTexture(2, 0);
material.setTexture(3, 0);
material.ZBuffer = video::ECFN_LESSEQUAL;
material.ZWriteEnable = false;
material.MaterialTypeParam = 0.f;
s32 shaderState = -1;
if ( (u32) face->fogNum < FogMap.size() )
{
material.setTexture(0, FogMap [ face->fogNum ].Texture);
shaderState = FogMap [ face->fogNum ].ShaderID;
}
return shaderState;
}
s32 CQ3LevelMesh::setShaderMaterial( video::SMaterial &material, const tBSPFace * face ) const
{
material.MaterialType = video::EMT_SOLID;
material.Wireframe = false;
material.Lighting = false;
material.BackfaceCulling = true;
material.setTexture(0, 0);
material.setTexture(1, 0);
material.setTexture(2, 0);
material.setTexture(3, 0);
material.ZBuffer = video::ECFN_LESSEQUAL;
material.ZWriteEnable = true;
material.MaterialTypeParam = 0.f;
s32 shaderState = -1;
if ( face->textureID >= 0 && face->textureID < (s32)Tex.size() )
{
material.setTexture(0, Tex [ face->textureID ].Texture);
shaderState = Tex [ face->textureID ].ShaderID;
}
if ( face->lightmapID >= 0 && face->lightmapID < (s32)Lightmap.size() )
{
material.setTexture(1, Lightmap [ face->lightmapID ]);
material.MaterialType = LoadParam.defaultLightMapMaterial;
}
material.MaterialTypeParam2 = (f32) shaderState;
const IShader *shader = getShader(shaderState);
if ( 0 == shader )
return shaderState;
return shaderState;
#if 0
const SVarGroup *group;
group = shader->getGroup( 1 );
if ( group )
{
material.BackfaceCulling = getCullingFunction( group->get( "cull" ) );
if ( group->isDefined( "surfaceparm", "nolightmap" ) )
{
material.MaterialType = video::EMT_SOLID;
material.setTexture(1, 0);
}
}
u32 startPos;
for ( s32 g = 2; g <= 3; ++g )
{
group = shader->getGroup( g );
if ( 0 == group )
continue;
startPos = 0;
if ( group->isDefined( "depthwrite" ) )
{
material.ZWriteEnable = true;
}
SBlendFunc blendfunc ( LoadParam.defaultModulate );
getBlendFunc( group->get( "blendfunc" ), blendfunc );
getBlendFunc( group->get( "alphafunc" ), blendfunc );
if ( 0 == LoadParam.alpharef &&
( blendfunc.type == video::EMT_TRANSPARENT_ALPHA_CHANNEL ||
blendfunc.type == video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF
)
)
{
blendfunc.type = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
blendfunc.param0 = 0.f;
}
material.MaterialType = blendfunc.type;
material.MaterialTypeParam = blendfunc.param0;
shaderState |= (material.MaterialType == video::EMT_SOLID ) ? 0x00020000 : 0;
}
if ( shader->VarGroup->VariableGroup.size() <= 4 )
{
shaderState |= 0x00010000;
}
material.MaterialTypeParam2 = (f32) shaderState;
return shaderState;
#endif
}
void CQ3LevelMesh::solveTJunction()
{
}
void CQ3LevelMesh::constructMesh()
{
if ( LoadParam.verbose > 0 )
{
LoadParam.startTime = os::Timer::getRealTime();
if ( LoadParam.verbose > 1 )
{
snprintf( buf, sizeof ( buf ),
"quake3::constructMesh start to create %d faces, %d vertices,%d mesh vertices",
NumFaces,
NumVertices,
NumMeshVerts
);
os::Printer::log(buf, ELL_INFORMATION);
}
}
s32 i, j, k,s;
s32 *index;
video::S3DVertex2TCoords temp[3];
video::SMaterial material;
video::SMaterial material2;
SToBuffer item [ E_Q3_MESH_SIZE ];
u32 itemSize;
for ( i=0; i < NumFaces; ++i)
{
const tBSPFace * face = Faces + i;
s32 shaderState = setShaderMaterial( material, face );
itemSize = 0;
const IShader *shader = getShader(shaderState);
if ( face->fogNum >= 0 )
{
setShaderFogMaterial ( material2, face );
item[itemSize].index = E_Q3_MESH_FOG;
item[itemSize].takeVertexColor = 1;
itemSize += 1;
}
switch( face->type )
{
case 1:
case 2:
case 3:
{
if ( 0 == shader )
{
if ( LoadParam.cleanUnResolvedMeshes || material.getTexture(0) )
{
item[itemSize].takeVertexColor = 1;
item[itemSize].index = E_Q3_MESH_GEOMETRY;
itemSize += 1;
}
else
{
item[itemSize].takeVertexColor = 1;
item[itemSize].index = E_Q3_MESH_UNRESOLVED;
itemSize += 1;
}
}
else
{
item[itemSize].takeVertexColor = 1;
item[itemSize].index = E_Q3_MESH_ITEMS;
itemSize += 1;
}
} break;
case 4:
break;
}
for ( u32 g = 0; g != itemSize; ++g )
{
scene::SMeshBufferLightMap* buffer = 0;
if ( item[g].index == E_Q3_MESH_GEOMETRY )
{
if ( 0 == item[g].takeVertexColor )
{
item[g].takeVertexColor = material.getTexture(0) == 0 || material.getTexture(1) == 0;
}
if (Faces[i].lightmapID < -1 || Faces[i].lightmapID > NumLightMaps-1)
{
Faces[i].lightmapID = -1;
}
#if 0
const s32 tmp_index = ((Faces[i].lightmapID+1) * (NumTextures+1)) + (Faces[i].textureID+1);
buffer = (SMeshBufferLightMap*) Mesh[E_Q3_MESH_GEOMETRY]->getMeshBuffer(tmp_index);
buffer->setHardwareMappingHint ( EHM_STATIC );
buffer->getMaterial() = material;
#endif
}
if ( 0 == buffer )
{
if ( LoadParam.mergeShaderBuffer == 1 )
{
buffer = (SMeshBufferLightMap*) Mesh[ item[g].index ]->getMeshBuffer(
item[g].index != E_Q3_MESH_FOG ? material : material2 );
}
if ( 0 == buffer )
{
buffer = new scene::SMeshBufferLightMap();
Mesh[ item[g].index ]->addMeshBuffer( buffer );
buffer->drop();
buffer->getMaterial() = item[g].index != E_Q3_MESH_FOG ? material : material2;
if ( item[g].index == E_Q3_MESH_GEOMETRY )
buffer->setHardwareMappingHint ( EHM_STATIC );
}
}
switch(Faces[i].type)
{
case 4:
break;
case 2:
createCurvedSurface_bezier( buffer, i,
LoadParam.patchTesselation,
item[g].takeVertexColor
);
break;
case 1:
case 3:
index = MeshVerts + face->meshVertIndex;
k = buffer->getVertexCount();
s = buffer->getIndexCount()+face->numMeshVerts;
if ( buffer->Indices.allocated_size () < (u32) s )
{
if ( buffer->Indices.allocated_size () > 0 &&
face->numMeshVerts < 20 && NumFaces > 1000
)
{
s = buffer->getIndexCount() + (NumFaces >> 3 * face->numMeshVerts );
}
buffer->Indices.reallocate( s);
}
for ( j = 0; j < face->numMeshVerts; ++j )
{
buffer->Indices.push_back( k + index [j] );
}
s = k+face->numOfVerts;
if ( buffer->Vertices.allocated_size () < (u32) s )
{
if ( buffer->Indices.allocated_size () > 0 &&
face->numOfVerts < 20 && NumFaces > 1000
)
{
s = buffer->getIndexCount() + (NumFaces >> 3 * face->numOfVerts );
}
buffer->Vertices.reallocate( s);
}
for ( j = 0; j != face->numOfVerts; ++j )
{
copy( &temp[0], &Vertices[ j + face->vertexIndex ], item[g].takeVertexColor );
buffer->Vertices.push_back( temp[0] );
}
break;
}
}
}
if ( LoadParam.verbose > 0 )
{
LoadParam.endTime = os::Timer::getRealTime();
snprintf( buf, sizeof ( buf ),
"quake3::constructMesh needed %04d ms to create %d faces, %d vertices,%d mesh vertices",
LoadParam.endTime - LoadParam.startTime,
NumFaces,
NumVertices,
NumMeshVerts
);
os::Printer::log(buf, ELL_INFORMATION);
}
}
void CQ3LevelMesh::S3DVertex2TCoords_64::copy( video::S3DVertex2TCoords &dest ) const
{
#if defined (TJUNCTION_SOLVER_ROUND)
dest.Pos.X = core::round_( (f32) Pos.X );
dest.Pos.Y = core::round_( (f32) Pos.Y );
dest.Pos.Z = core::round_( (f32) Pos.Z );
#elif defined (TJUNCTION_SOLVER_0125)
dest.Pos.X = (f32) ( floor ( Pos.X * 8.f + 0.5 ) * 0.125 );
dest.Pos.Y = (f32) ( floor ( Pos.Y * 8.f + 0.5 ) * 0.125 );
dest.Pos.Z = (f32) ( floor ( Pos.Z * 8.f + 0.5 ) * 0.125 );
#else
dest.Pos.X = (f32) Pos.X;
dest.Pos.Y = (f32) Pos.Y;
dest.Pos.Z = (f32) Pos.Z;
#endif
dest.Normal.X = (f32) Normal.X;
dest.Normal.Y = (f32) Normal.Y;
dest.Normal.Z = (f32) Normal.Z;
dest.Normal.normalize();
dest.Color = Color.toSColor();
dest.TCoords.X = (f32) TCoords.X;
dest.TCoords.Y = (f32) TCoords.Y;
dest.TCoords2.X = (f32) TCoords2.X;
dest.TCoords2.Y = (f32) TCoords2.Y;
}
void CQ3LevelMesh::copy( S3DVertex2TCoords_64 * dest, const tBSPVertex * source, s32 vertexcolor ) const
{
#if defined (TJUNCTION_SOLVER_ROUND)
dest->Pos.X = core::round_( source->vPosition[0] );
dest->Pos.Y = core::round_( source->vPosition[2] );
dest->Pos.Z = core::round_( source->vPosition[1] );
#elif defined (TJUNCTION_SOLVER_0125)
dest->Pos.X = (f32) ( floor ( source->vPosition[0] * 8.f + 0.5 ) * 0.125 );
dest->Pos.Y = (f32) ( floor ( source->vPosition[2] * 8.f + 0.5 ) * 0.125 );
dest->Pos.Z = (f32) ( floor ( source->vPosition[1] * 8.f + 0.5 ) * 0.125 );
#else
dest->Pos.X = source->vPosition[0];
dest->Pos.Y = source->vPosition[2];
dest->Pos.Z = source->vPosition[1];
#endif
dest->Normal.X = source->vNormal[0];
dest->Normal.Y = source->vNormal[2];
dest->Normal.Z = source->vNormal[1];
dest->Normal.normalize();
dest->TCoords.X = source->vTextureCoord[0];
dest->TCoords.Y = source->vTextureCoord[1];
dest->TCoords2.X = source->vLightmapCoord[0];
dest->TCoords2.Y = source->vLightmapCoord[1];
if ( vertexcolor )
{
u32 a = source->color[3];
u32 r = core::s32_min( source->color[0] * LoadParam.defaultModulate, 255 );
u32 g = core::s32_min( source->color[1] * LoadParam.defaultModulate, 255 );
u32 b = core::s32_min( source->color[2] * LoadParam.defaultModulate, 255 );
dest->Color.set(a * 1.f/255.f, r * 1.f/255.f,
g * 1.f/255.f, b * 1.f/255.f);
}
else
{
dest->Color.set( 1.f, 1.f, 1.f, 1.f );
}
}
inline void CQ3LevelMesh::copy( video::S3DVertex2TCoords * dest, const tBSPVertex * source, s32 vertexcolor ) const
{
#if defined (TJUNCTION_SOLVER_ROUND)
dest->Pos.X = core::round_( source->vPosition[0] );
dest->Pos.Y = core::round_( source->vPosition[2] );
dest->Pos.Z = core::round_( source->vPosition[1] );
#elif defined (TJUNCTION_SOLVER_0125)
dest->Pos.X = (f32) ( floor ( source->vPosition[0] * 8.f + 0.5 ) * 0.125 );
dest->Pos.Y = (f32) ( floor ( source->vPosition[2] * 8.f + 0.5 ) * 0.125 );
dest->Pos.Z = (f32) ( floor ( source->vPosition[1] * 8.f + 0.5 ) * 0.125 );
#else
dest->Pos.X = source->vPosition[0];
dest->Pos.Y = source->vPosition[2];
dest->Pos.Z = source->vPosition[1];
#endif
dest->Normal.X = source->vNormal[0];
dest->Normal.Y = source->vNormal[2];
dest->Normal.Z = source->vNormal[1];
dest->Normal.normalize();
dest->TCoords.X = source->vTextureCoord[0];
dest->TCoords.Y = source->vTextureCoord[1];
dest->TCoords2.X = source->vLightmapCoord[0];
dest->TCoords2.Y = source->vLightmapCoord[1];
if ( vertexcolor )
{
u32 a = source->color[3];
u32 r = core::s32_min( source->color[0] * LoadParam.defaultModulate, 255 );
u32 g = core::s32_min( source->color[1] * LoadParam.defaultModulate, 255 );
u32 b = core::s32_min( source->color[2] * LoadParam.defaultModulate, 255 );
dest->Color.set(a << 24 | r << 16 | g << 8 | b);
}
else
{
dest->Color.set(0xFFFFFFFF);
}
}
void CQ3LevelMesh::SBezier::tesselate( s32 level )
{
s32 j, k;
column[0].set_used( level + 1 );
column[1].set_used( level + 1 );
column[2].set_used( level + 1 );
const f64 w = 0.0 + (1.0 / (f64) level );
for( j = 0; j <= level; ++j)
{
const f64 f = w * (f64) j;
column[0][j] = control[0].getInterpolated_quadratic(control[3], control[6], f );
column[1][j] = control[1].getInterpolated_quadratic(control[4], control[7], f );
column[2][j] = control[2].getInterpolated_quadratic(control[5], control[8], f );
}
const u32 idx = Patch->Vertices.size();
Patch->Vertices.reallocate(idx+level*level);
video::S3DVertex2TCoords v;
S3DVertex2TCoords_64 f;
for( j = 0; j <= level; ++j)
{
for( k = 0; k <= level; ++k)
{
f = column[0][j].getInterpolated_quadratic(column[1][j], column[2][j], w * (f64) k);
f.copy( v );
Patch->Vertices.push_back( v );
}
}
Patch->Indices.reallocate(Patch->Indices.size()+6*level*level);
for( j = 0; j < level; ++j)
{
for( k = 0; k < level; ++k)
{
const s32 inx = idx + ( k * ( level + 1 ) ) + j;
Patch->Indices.push_back( inx + 0 );
Patch->Indices.push_back( inx + (level + 1 ) + 0 );
Patch->Indices.push_back( inx + (level + 1 ) + 1 );
Patch->Indices.push_back( inx + 0 );
Patch->Indices.push_back( inx + (level + 1 ) + 1 );
Patch->Indices.push_back( inx + 1 );
}
}
}
void CQ3LevelMesh::createCurvedSurface_nosubdivision(SMeshBufferLightMap* meshBuffer,
s32 faceIndex,
s32 patchTesselation,
s32 storevertexcolor)
{
tBSPFace * face = &Faces[faceIndex];
u32 j,k,m;
const u32 controlWidth = face->size[0];
const u32 controlHeight = face->size[1];
if ( 0 == controlWidth || 0 == controlHeight )
return;
video::S3DVertex2TCoords v;
m = meshBuffer->Vertices.size();
meshBuffer->Vertices.reallocate(m+controlHeight * controlWidth);
for ( j = 0; j!= controlHeight * controlWidth; ++j )
{
copy( &v, &Vertices [ face->vertexIndex + j ], storevertexcolor );
meshBuffer->Vertices.push_back( v );
}
meshBuffer->Indices.reallocate(meshBuffer->Indices.size()+6*(controlHeight-1) * (controlWidth-1));
for ( j = 0; j!= controlHeight - 1; ++j )
{
for ( k = 0; k!= controlWidth - 1; ++k )
{
meshBuffer->Indices.push_back( m + k + 0 );
meshBuffer->Indices.push_back( m + k + controlWidth + 0 );
meshBuffer->Indices.push_back( m + k + controlWidth + 1 );
meshBuffer->Indices.push_back( m + k + 0 );
meshBuffer->Indices.push_back( m + k + controlWidth + 1 );
meshBuffer->Indices.push_back( m + k + 1 );
}
m += controlWidth;
}
}
void CQ3LevelMesh::createCurvedSurface_bezier(SMeshBufferLightMap* meshBuffer,
s32 faceIndex,
s32 patchTesselation,
s32 storevertexcolor)
{
tBSPFace * face = &Faces[faceIndex];
u32 j,k;
const u32 controlWidth = face->size[0];
const u32 controlHeight = face->size[1];
if ( 0 == controlWidth || 0 == controlHeight )
return;
const u32 biquadWidth = (controlWidth - 1)/2;
const u32 biquadHeight = (controlHeight -1)/2;
if ( LoadParam.verbose > 1 )
{
LoadParam.startTime = os::Timer::getRealTime();
}
core::array<S3DVertex2TCoords_64> controlPoint;
controlPoint.set_used( controlWidth * controlHeight );
for( j = 0; j < controlPoint.size(); ++j)
{
copy( &controlPoint[j], &Vertices [ face->vertexIndex + j ], storevertexcolor );
}
Bezier.Patch = new scene::SMeshBufferLightMap();
for( j = 0; j < biquadHeight; ++j)
{
for( k = 0; k < biquadWidth; ++k)
{
const s32 inx = j*controlWidth*2 + k*2;
Bezier.control[0] = controlPoint[ inx + 0];
Bezier.control[1] = controlPoint[ inx + 1];
Bezier.control[2] = controlPoint[ inx + 2];
Bezier.control[3] = controlPoint[ inx + controlWidth + 0 ];
Bezier.control[4] = controlPoint[ inx + controlWidth + 1 ];
Bezier.control[5] = controlPoint[ inx + controlWidth + 2 ];
Bezier.control[6] = controlPoint[ inx + controlWidth * 2 + 0];
Bezier.control[7] = controlPoint[ inx + controlWidth * 2 + 1];
Bezier.control[8] = controlPoint[ inx + controlWidth * 2 + 2];
Bezier.tesselate( patchTesselation );
}
}
const u32 bsize = Bezier.Patch->getVertexCount();
const u32 msize = meshBuffer->getVertexCount();
meshBuffer->Vertices.reallocate(msize+bsize);
for ( j = 0; j!= bsize; ++j )
{
meshBuffer->Vertices.push_back( Bezier.Patch->Vertices[j] );
}
meshBuffer->Indices.reallocate(meshBuffer->getIndexCount()+Bezier.Patch->getIndexCount());
for ( j = 0; j!= Bezier.Patch->getIndexCount(); ++j )
{
meshBuffer->Indices.push_back( msize + Bezier.Patch->Indices[j] );
}
delete Bezier.Patch;
if ( LoadParam.verbose > 1 )
{
LoadParam.endTime = os::Timer::getRealTime();
snprintf( buf, sizeof ( buf ),
"quake3::createCurvedSurface_bezier needed %04d ms to create bezier patch.(%dx%d)",
LoadParam.endTime - LoadParam.startTime,
biquadWidth,
biquadHeight
);
os::Printer::log(buf, ELL_INFORMATION);
}
}
void CQ3LevelMesh::getConfiguration( io::IReadFile* file )
{
tBSPLump l;
l.offset = file->getPos();
l.length = file->getSize ();
core::array<u8> entity;
entity.set_used( l.length + 2 );
entity[l.length + 1 ] = 0;
file->seek(l.offset);
file->read( entity.pointer(), l.length);
parser_parse( entity.pointer(), l.length, &CQ3LevelMesh::scriptcallback_config );
if ( Entity.size () )
Entity.getLast().name = file->getFileName();
}
tQ3EntityList & CQ3LevelMesh::getEntityList()
{
return Entity;
}
const IShader * CQ3LevelMesh::getShader(u32 index) const
{
index &= 0xFFFF;
if ( index < Shader.size() )
{
return &Shader[index];
}
return 0;
}
const IShader* CQ3LevelMesh::getShader( const c8 * filename, bool fileNameIsValid )
{
core::stringc searchName ( filename );
IShader search;
search.name = searchName;
search.name.replace( '\\', '/' );
search.name.make_lower();
core::stringc message;
s32 index;
index = Shader.linear_search( search );
if ( index >= 0 )
{
if ( LoadParam.verbose > 1 )
{
message = searchName + " found " + Shader[index].name;
os::Printer::log("quake3:getShader", message.c_str(), ELL_INFORMATION);
}
return &Shader[index];
}
io::path loadFile;
if ( !fileNameIsValid )
{
core::stringc cut( search.name );
s32 end = cut.findLast( '/' );
s32 start = cut.findLast( '/', end - 1 );
loadFile = LoadParam.scriptDir;
loadFile.append( cut.subString( start, end - start ) );
loadFile.append( ".shader" );
}
else
{
loadFile = search.name;
}
index = ShaderFile.binary_search( loadFile );
if ( index >= 0 )
return 0;
ShaderFile.push_back( loadFile );
if ( !FileSystem->existFile( loadFile.c_str() ) )
{
if ( LoadParam.verbose > 1 )
{
message = loadFile + " for " + searchName + " failed ";
os::Printer::log("quake3:getShader", message.c_str(), ELL_INFORMATION);
}
return 0;
}
if ( LoadParam.verbose )
{
message = loadFile + " for " + searchName;
os::Printer::log("quake3:getShader Load shader", message.c_str(), ELL_INFORMATION);
}
io::IReadFile *file = FileSystem->createAndOpenFile( loadFile.c_str() );
if ( file )
{
getShader ( file );
file->drop ();
}
index = Shader.linear_search( search );
return index >= 0 ? &Shader[index] : 0;
}
void CQ3LevelMesh::getShader( io::IReadFile* file )
{
if ( 0 == file )
return;
core::array<u8> script;
const long len = file->getSize();
script.set_used( len + 2 );
file->seek( 0 );
file->read( script.pointer(), len );
script[ len + 1 ] = 0;
parser_parse( script.pointer(), len, &CQ3LevelMesh::scriptcallback_shader );
}
void CQ3LevelMesh::InitShader()
{
ReleaseShader();
IShader element;
SVarGroup group;
SVariable variable ( "noshader" );
group.Variable.push_back( variable );
element.VarGroup = new SVarGroupList();
element.VarGroup->VariableGroup.push_back( group );
element.VarGroup->VariableGroup.push_back( SVarGroup() );
element.name = element.VarGroup->VariableGroup[0].Variable[0].name;
element.ID = Shader.size();
Shader.push_back( element );
if ( LoadParam.loadAllShaders )
{
io::EFileSystemType current = FileSystem->setFileListSystem ( io::FILESYSTEM_VIRTUAL );
io::path save = FileSystem->getWorkingDirectory();
io::path newDir;
newDir = "/";
newDir += LoadParam.scriptDir;
newDir += "/";
FileSystem->changeWorkingDirectoryTo ( newDir.c_str() );
core::stringc s;
io::IFileList *fileList = FileSystem->createFileList ();
for (u32 i=0; i< fileList->getFileCount(); ++i)
{
s = fileList->getFullFileName(i);
if ( s.find ( ".shader" ) >= 0 )
{
if ( 0 == LoadParam.loadSkyShader && s.find ( "sky.shader" ) >= 0 )
{
}
else
{
getShader ( s.c_str () );
}
}
}
fileList->drop ();
FileSystem->changeWorkingDirectoryTo ( save );
FileSystem->setFileListSystem ( current );
}
}
void CQ3LevelMesh::ReleaseShader()
{
for ( u32 i = 0; i!= Shader.size(); ++i )
{
Shader[i].VarGroup->drop();
}
Shader.clear();
ShaderFile.clear();
}
void CQ3LevelMesh::ReleaseEntity()
{
for ( u32 i = 0; i!= Entity.size(); ++i )
{
Entity[i].VarGroup->drop();
}
Entity.clear();
}
void CQ3LevelMesh::scriptcallback_config( SVarGroupList *& grouplist, eToken token )
{
IShader element;
if ( token == Q3_TOKEN_END_LIST )
{
if ( 0 == grouplist->VariableGroup[0].Variable.size() )
return;
element.name = grouplist->VariableGroup[0].Variable[0].name;
}
else
{
if ( grouplist->VariableGroup.size() != 2 )
return;
element.name = "configuration";
}
grouplist->grab();
element.VarGroup = grouplist;
element.ID = Entity.size();
Entity.push_back( element );
}
void CQ3LevelMesh::scriptcallback_entity( SVarGroupList *& grouplist, eToken token )
{
if ( token != Q3_TOKEN_END_LIST || grouplist->VariableGroup.size() != 2 )
return;
grouplist->grab();
IEntity element;
element.VarGroup = grouplist;
element.ID = Entity.size();
element.name = grouplist->VariableGroup[1].get( "classname" );
Entity.push_back( element );
}
void CQ3LevelMesh::scriptcallback_shader( SVarGroupList *& grouplist,eToken token )
{
if ( token != Q3_TOKEN_END_LIST || grouplist->VariableGroup[0].Variable.size()==0)
return;
IShader element;
grouplist->grab();
element.VarGroup = grouplist;
element.name = element.VarGroup->VariableGroup[0].Variable[0].name;
element.ID = Shader.size();
Shader.push_back( element );
}
void CQ3LevelMesh::cleanMeshes()
{
if ( 0 == LoadParam.cleanUnResolvedMeshes )
return;
u32 run = 0;
u32 remove = 0;
irr::scene::SMesh *m;
IMeshBuffer *b;
for ( u32 g = 0; g < E_Q3_MESH_SIZE; ++g )
{
bool texture0important = ( g == 0 );
run = 0;
remove = 0;
m = Mesh[g];
if ( LoadParam.verbose > 0 )
{
LoadParam.startTime = os::Timer::getRealTime();
if ( LoadParam.verbose > 1 )
{
snprintf( buf, sizeof ( buf ),
"quake3::cleanMeshes%d start for %d meshes",
g,
m->MeshBuffers.size()
);
os::Printer::log(buf, ELL_INFORMATION);
}
}
u32 i = 0;
s32 blockstart = -1;
s32 blockcount;
while( i < m->MeshBuffers.size())
{
run += 1;
b = m->MeshBuffers[i];
if ( b->getVertexCount() == 0 || b->getIndexCount() == 0 ||
( texture0important && b->getMaterial().getTexture(0) == 0 )
)
{
if ( blockstart < 0 )
{
blockstart = i;
blockcount = 0;
}
blockcount += 1;
i += 1;
i -= 1;
remove += 1;
b->drop();
m->MeshBuffers.erase(i);
}
else
{
if ( blockstart >= 0 )
{
if ( LoadParam.verbose > 1 )
{
snprintf( buf, sizeof ( buf ),
"quake3::cleanMeshes%d cleaning mesh %d %d size",
g,
blockstart,
blockcount
);
os::Printer::log(buf, ELL_INFORMATION);
}
blockstart = -1;
}
i += 1;
}
}
if ( LoadParam.verbose > 0 )
{
LoadParam.endTime = os::Timer::getRealTime();
snprintf( buf, sizeof ( buf ),
"quake3::cleanMeshes%d needed %04d ms to clean %d of %d meshes",
g,
LoadParam.endTime - LoadParam.startTime,
remove,
run
);
os::Printer::log(buf, ELL_INFORMATION);
}
}
}
void CQ3LevelMesh::calcBoundingBoxes()
{
if ( LoadParam.verbose > 0 )
{
LoadParam.startTime = os::Timer::getRealTime();
if ( LoadParam.verbose > 1 )
{
snprintf( buf, sizeof ( buf ),
"quake3::calcBoundingBoxes start create %d textures and %d lightmaps",
NumTextures,
NumLightMaps
);
os::Printer::log(buf, ELL_INFORMATION);
}
}
for ( u32 g = 0; g != E_Q3_MESH_SIZE; ++g )
{
for ( u32 j=0; j < Mesh[g]->MeshBuffers.size(); ++j)
{
((SMeshBufferLightMap*)Mesh[g]->MeshBuffers[j])->recalculateBoundingBox();
}
Mesh[g]->recalculateBoundingBox();
if (g!=0)
Mesh[0]->BoundingBox.addInternalBox(Mesh[g]->getBoundingBox());
}
if ( LoadParam.verbose > 0 )
{
LoadParam.endTime = os::Timer::getRealTime();
snprintf( buf, sizeof ( buf ),
"quake3::calcBoundingBoxes needed %04d ms to create %d textures and %d lightmaps",
LoadParam.endTime - LoadParam.startTime,
NumTextures,
NumLightMaps
);
os::Printer::log( buf, ELL_INFORMATION);
}
}
void CQ3LevelMesh::loadTextures()
{
if (!Driver)
return;
if ( LoadParam.verbose > 0 )
{
LoadParam.startTime = os::Timer::getRealTime();
if ( LoadParam.verbose > 1 )
{
snprintf( buf, sizeof ( buf ),
"quake3::loadTextures start create %d textures and %d lightmaps",
NumTextures,
NumLightMaps
);
os::Printer::log( buf, ELL_INFORMATION);
}
}
c8 lightmapname[255];
s32 t;
Lightmap.set_used(NumLightMaps);
core::dimension2d<u32> lmapsize(128,128);
video::IImage* lmapImg;
for ( t = 0; t < NumLightMaps ; ++t)
{
sprintf(lightmapname, "%s.lightmap.%d", LevelName.c_str(), t);
lmapImg = Driver->createImageFromData(
video::ECF_R8G8B8, lmapsize,
LightMaps[t].imageBits, false, true );
Lightmap[t] = Driver->addTexture( lightmapname, lmapImg );
lmapImg->drop();
}
Tex.set_used( NumTextures );
const IShader* shader;
core::stringc list;
io::path check;
tTexArray textureArray;
for ( t=0; t< NumTextures; ++t)
{
shader = getShader(Textures[t].strName, false);
}
for ( t=0; t< NumTextures; ++t)
{
Tex[t].ShaderID = -1;
Tex[t].Texture = 0;
list = "";
shader = getShader( Textures[t].strName, false);
if ( shader )
{
Tex[t].ShaderID = shader->ID;
const SVarGroup * group;
group = shader->getGroup( 2 );
if ( group )
{
if ( core::cutFilenameExtension( check, group->get( "map" ) ) == Textures[t].strName )
{
list += check;
}
else
if ( check == "$lightmap" )
{
group = shader->getGroup( 3 );
if ( group )
list += group->get( "map" );
}
}
}
else
{
list += Textures[t].strName;
}
u32 pos = 0;
getTextures( textureArray, list, pos, FileSystem, Driver );
Tex[t].Texture = textureArray[0];
}
if ( LoadParam.verbose > 0 )
{
LoadParam.endTime = os::Timer::getRealTime();
snprintf( buf, sizeof ( buf ),
"quake3::loadTextures needed %04d ms to create %d textures and %d lightmaps",
LoadParam.endTime - LoadParam.startTime,
NumTextures,
NumLightMaps
);
os::Printer::log( buf, ELL_INFORMATION);
}
}
const core::aabbox3d<f32>& CQ3LevelMesh::getBoundingBox() const
{
return Mesh[0]->getBoundingBox();
}
void CQ3LevelMesh::setBoundingBox(const core::aabbox3df& box)
{
Mesh[0]->setBoundingBox(box);
}
E_ANIMATED_MESH_TYPE CQ3LevelMesh::getMeshType() const
{
return scene::EAMT_BSP;
}
}
}
#endif