Importing Quake files and previewing levels using Irrlicht (Part 2)

The LoadQuake Command

Firstly there is the command itself, which is not that much. This just has an execute method and of course it is registered in the main.cpp file.

class LoadQuakeCommandData : public CommandData
{
public:
   virtual Bool Execute(BaseDocument* doc);
};
Bool RegisterQuakeLoaderCommandData()
{
//Note that I should really get another ID from the maxon plugin cafe here rather than go + 1
  return RegisterCommandPlugin(ID_RUNGAME_CMD+1, "LoadQuake", 0, "", "LoadQuake", gNew LoadQuakeCommandData());
}

The Execute method is where all the exciting stuff happens. Firstly this creates an IrrlichtDevice, sets it up loads in the Quake pk3 file from the media directory located in the irrlichtviewers plugin folder.

Bool LoadQuakeCommandData::Execute(BaseDocument* doc)
{
IrrlichtDevice *device = createDevice( video::EDT_NULL, dimension2d(640, 480), 16, false, false, false);


if (!device) return FALSE;


IVideoDriver* driver = device->getVideoDriver();
ISceneManager* smgr = device->getSceneManager();
gui::IGUIEnvironment* gui = device->getGUIEnvironment();


//add our private media directory to the file system


Filename fileName = GeGetPluginPath() + Filename("media");


char path[1024];
fileName.GetString().GetCString(path,1024);
device->getFileSystem()->addFolderFileArchive(path);


/*
To display the Quake 3 map, we first need to load it. Quake 3 maps
are packed into .pk3 files, which are nothing other than .zip files.
So we add the .pk3 file to our FileSystem. After it was added,
we are able to read from the files in that archive as they would
directly be stored on disk.
*/
device->getFileSystem()->addZipFileArchive ( "map-20kdm2.pk3" );
scene::IQ3LevelMesh* mesh = (scene::IQ3LevelMesh*) smgr->getMesh("maps/20kdm2.bsp");
Now for each piece of geometry in the mesh we create a new polygonal object in Cinema4D and create a normals tag for it.

scene::ISceneNode* node = 0;
if ( mesh )
{
scene::IMesh *geometry = mesh->getMesh(quake3::E_Q3_MESH_GEOMETRY);


u32 geom;
for(geom=0;geomgetMeshBufferCount();geom++)
{
IMeshBuffer* buffer = geometry->getMeshBuffer(geom);


u32 vertcount = buffer->getVertexCount();
u32 indexCount = buffer->getIndexCount();


//Find the polygon count
u32 polyCount = indexCount / 3;


//Create the polygon object
PolygonObject *newPolyObj = (PolygonObject *)BaseObject::Alloc(Opolygon);
newPolyObj->ResizeObject(vertcount,polyCount);


Vector *newPoints = newPolyObj->GetPointW();
CPolygon *newPolys = newPolyObj->GetPolygonW();


//Create a normals tag
VariableTag *nrmtag = NULL;
SWORD *normalData = NULL;
nrmtag = newPolyObj->MakeVariableTag(Tnormal,polyCount);
if(nrmtag)
{
normalData = (SWORD*)nrmtag->GetDataAddressW();
}
Now we load in all the materials and textures into Cinema4D. One thing to note here is that all the textures are created in memory. Nothing is ever saved to disk during the entire Loading of the Quake level to Running it in the IrrlichtViewer.

        //Create materials and textures
if(buffer)
{
UVWTag *uvwA = NULL;
UVWTag *uvwB = NULL;


Vector textureTrans;
Vector textureRot;
Vector textureScale;


video::SMaterial &material = buffer->getMaterial();
LONG matCount=0;
for(matCount=MATERIAL_MAX_TEXTURES-1;matCount>=0;matCount--)
{
video::ITexture *pTexture = material.getTexture(matCount);


if(pTexture)
{
    //Create a new material for each texture since they have different UVs. Mix the materials together using the "Mix Texture" parameter on a TextureTag
Material *newMat = Material::Alloc();
doc->InsertMaterial(newMat);


if(matCount==0)
{
//The first material will use the UVs from the the first UVTag.
uvwA = (UVWTag*)newPolyObj->MakeVariableTag(Tuvw,polyCount);
}
else if(matCount == 1 && buffer->getVertexType() == EVT_2TCOORDS)
{
//The second material will use the UVs from the second UVTag
uvwB = (UVWTag*)newPolyObj->MakeVariableTag(Tuvw,polyCount);
}


dimension2d size = pTexture->getSize();
const stringc &name = pTexture->getName();


//Set the data for the texture
BaseContainer bc;
bc.SetLong(TEXTURE_FILEFORMAT,FILTER_BMP);
bc.SetLong(TEXTURE_WIDTH, size.Width);
bc.SetLong(TEXTURE_HEIGHT, size.Height);
bc.SetLong(TEXTURE_MODE, MODE_RGB);


//Create a new material in Cinema4D
newMat->SetChannelState(CHANNEL_COLOR,TRUE);
newMat->SetName(name.c_str());


//Get the color channel for the new material
BaseChannel* colorChannel = newMat->GetChannel(CHANNEL_COLOR);


//Assign the texture name to the material so the material can reference the image.
BaseContainer bla;
bla.SetString(BASECHANNEL_TEXTURE,name.c_str());
colorChannel->SetData(bla);


//See if we have already loaded this texture
PaintTexture* newTexture = NULL;
GeListHead *head = PaintTexture::GetPaintTextureHead();
if(head)
{
PaintTexture *headTexture = (PaintTexture *)head->GetFirst();
while(headTexture)
{
if(headTexture->GetFilename() == Filename(name.c_str()))
{
newTexture = headTexture;
break;
}
headTexture = (PaintTexture *)headTexture->GetNext();
}
}


//If no texture was previously loaded then create a new one
if(!newTexture)
{
newTexture = PaintTexture::CreateNewTexture(name.c_str(), bc);


core::matrix4& textMatrix = material.getTextureMatrix(matCount);
vector3df trans = textMatrix.getTranslation();
vector3df rot = textMatrix.getRotationDegrees();
vector3df scale = textMatrix.getScale();


textureTrans.x = trans.X;
textureTrans.y = trans.Y;
textureTrans.z = trans.Z;


textureRot.x = Rad(Real(rot.X));
textureRot.y = Rad(Real(rot.Y));
textureRot.z = Rad(Real(rot.Z));


textureScale.x = scale.X;
textureScale.y = scale.Y;
textureScale.z = scale.Z;


u32 bytesPerRow = pTexture->getPitch();
ECOLOR_FORMAT format = pTexture->getColorFormat();


UCHAR *pixelData = (UCHAR *)pTexture->lock(true);


    IImage* tempImage = driver->createImageFromData(format,size,pixelData,true,false); //Use the pointer for the data but don't delete it when the image is dropped


//Get the first layer for the new image
PaintLayerBmp *paintBmp  = NULL;
PaintLayer *layer = newTexture->GetFirstLayer();
paintBmp = (PaintLayerBmp*)layer;
paintBmp->SetName(name.c_str());


//Write the pixels from the Irrlicht image to the C4D PaintLayerBmp;
u32 x,y;
for(x=0;x<(u32)size.Width;x++)
{
for(y=0;y<(u32)size.Height;y++)
{
video::SColor col = tempImage->getPixel(x,y);


PIX buffer[3];
buffer[0] = (UCHAR)col.getRed();
buffer[1] = (UCHAR)col.getGreen();
buffer[2] = (UCHAR)col.getBlue();


paintBmp->SetPixelCnt(x,y,1,buffer,3,MODE_RGB,0);
}
}


pTexture->unlock();
}

This is where the Mix Flag is set for the texture Tag so that the light maps display correctly when the image is rendered.

//Create the texture tag and add it to the polygon object.
//Tell the texture to MIX the color if it has a lightmap (which is the secound material in our case)
{
TextureTag *texTag = (TextureTag *)newPolyObj->MakeTag(Ttexture);
texTag->SetMaterial(newMat);


//Have a look in the file ttexture.h for a list of other flags
//C:\Program Files\MAXON\CINEMA 4D R11.5\resource\res\description\ttexture.h
BaseContainer texTagData;
texTagData.SetReal(TEXTURETAG_SIDE,TEXTURETAG_SIDE_FRONTANDBACK);
texTagData.SetBool(TEXTURETAG_TILE,TRUE);
texTagData.SetVector(TEXTURETAG_POSITION,textureTrans);
texTagData.SetVector(TEXTURETAG_SIZE,textureScale);
texTagData.SetVector(TEXTURETAG_ROTATION,textureRot);
texTagData.SetLong(TEXTURETAG_PROJECTION, TEXTURETAG_PROJECTION_UVW);


if(matCount==1)
{
texTagData.SetBool(TEXTURETAG_MIX,TRUE);
}
texTag->SetData(texTagData);
}


//Tell the material to update everything
newMat->Message(MSG_UPDATE);
newMat->Update(TRUE, TRUE);
}
}

After that we load in all the Vertices for the geometry and copy then over to the Cinema4D polygonal Object

            //Copy across the vertex data to the polygon object.
if(buffer->getVertexType()==EVT_STANDARD)
{
S3DVertex *vertices = (S3DVertex*)buffer->getVertices();
u32 vertIndex;
for(vertIndex=0;vertIndex
{
const S3DVertex &theVert = vertices[vertIndex];
newPoints[vertIndex].x = theVert.Pos.X;
newPoints[vertIndex].y = theVert.Pos.Y;
newPoints[vertIndex].z = theVert.Pos.Z;
}
}
else if(buffer->getVertexType()==EVT_2TCOORDS)
{
S3DVertex2TCoords *vertices = (S3DVertex2TCoords*)buffer->getVertices();
u32 vertIndex;
for(vertIndex=0;vertIndex
{
const S3DVertex2TCoords &theVert = vertices[vertIndex];
newPoints[vertIndex].x = theVert.Pos.X;
newPoints[vertIndex].y = theVert.Pos.Y;
newPoints[vertIndex].z = theVert.Pos.Z;
}
}
else if(buffer->getVertexType()==EVT_TANGENTS)
{
S3DVertexTangents *vertices = (S3DVertexTangents*)buffer->getVertices();
u32 vertIndex;
for(vertIndex=0;vertIndex
{
const S3DVertexTangents &theVert = vertices[vertIndex];
newPoints[vertIndex].x = theVert.Pos.X;
newPoints[vertIndex].y = theVert.Pos.Y;
newPoints[vertIndex].z = theVert.Pos.Z;
}
}

And lastly we create the UVs and Normals for the polygonal objects

           //Set the normals and UVs for the polygon object
switch(buffer->getIndexType())
{
case video::EIT_16BIT:
{
u16 *indices = (u16 *)buffer->getIndices();
u32 index;
u32 polyCount = 0;
for(index=0;index
{
newPolys[polyCount].a = indices[index];
newPolys[polyCount].b = indices[index+1];
newPolys[polyCount].d = newPolys[polyCount].c = indices[index+2];


//If its the standard type then we need to cast to S3DVertex
if(buffer->getVertexType()== EVT_STANDARD && uvwA)
{
S3DVertex *vertices = (S3DVertex*)buffer->getVertices();


UVWStruct uv;


const S3DVertex &theVertA = vertices[indices[index]];
const S3DVertex &theVertB = vertices[indices[index+1]];
const S3DVertex &theVertC = vertices[indices[index+2]];


//Set the UVs for the first UV set (the diffuse color)
uv.a.x = theVertA.TCoords.X;
uv.a.y = theVertA.TCoords.Y;


uv.b.x = theVertB.TCoords.X;
uv.b.y = theVertB.TCoords.Y;


uv.c.x = theVertC.TCoords.X;
uv.c.y = theVertC.TCoords.Y;


uvwA->SetSlow(polyCount,uv);


//Set the normals
if(normalData)
{
    StoreNormal(normalData+12*polyCount+0,Vector(theVertA.Normal.X,theVertA.Normal.Y,theVertA.Normal.Z));
    StoreNormal(normalData+12*polyCount+3,Vector(theVertB.Normal.X,theVertB.Normal.Y,theVertB.Normal.Z));
    StoreNormal(normalData+12*polyCount+6,Vector(theVertC.Normal.X,theVertC.Normal.Y,theVertC.Normal.Z));
    StoreNormal(normalData+12*polyCount+9,Vector(theVertC.Normal.X,theVertC.Normal.Y,theVertC.Normal.Z));
}
}


//If its the standard type then we need to cast to S3DVertex
else if(buffer->getVertexType()==EVT_2TCOORDS)
{


UVWStruct uv;
S3DVertex2TCoords *vertices = (S3DVertex2TCoords*)buffer->getVertices();
const S3DVertex2TCoords &theVertA = vertices[indices[index]];
const S3DVertex2TCoords &theVertB = vertices[indices[index+1]];
const S3DVertex2TCoords &theVertC = vertices[indices[index+2]];


//UV A (used by the Diffuse Color texture)
if(uvwA)
{
uv.a.x = theVertA.TCoords.X;
uv.a.y = theVertA.TCoords.Y;


uv.b.x = theVertB.TCoords.X;
uv.b.y = theVertB.TCoords.Y;


uv.c.x = theVertC.TCoords.X;
uv.c.y = theVertC.TCoords.Y;


uvwA->SetSlow(polyCount,uv);
}


//UV B (used byt the Light map texture)
if(uvwB)
{
uv.a.x = theVertA.TCoords2.X;
uv.a.y = theVertA.TCoords2.Y;


uv.b.x = theVertB.TCoords2.X;
uv.b.y = theVertB.TCoords2.Y;


uv.c.x = theVertC.TCoords2.X;
uv.c.y = theVertC.TCoords2.Y;


uvwB->SetSlow(polyCount,uv);
}


//Set the normals
if(normalData)
{
    StoreNormal(normalData+12*polyCount+0,Vector(theVertA.Normal.X,theVertA.Normal.Y,theVertA.Normal.Z));
    StoreNormal(normalData+12*polyCount+3,Vector(theVertB.Normal.X,theVertB.Normal.Y,theVertB.Normal.Z));
    StoreNormal(normalData+12*polyCount+6,Vector(theVertC.Normal.X,theVertC.Normal.Y,theVertC.Normal.Z));
    StoreNormal(normalData+12*polyCount+9,Vector(theVertC.Normal.X,theVertC.Normal.Y,theVertC.Normal.Z));
}
}


polyCount++;
}
}
break;
case EIT_32BIT:
{
u32 *indices = (u32*)buffer->getIndices();
}
break;
default:
break;
}
doc->InsertObject(newPolyObj,0,0);
newPolyObj->Message(MSG_UPDATE);
}
}
}


device->drop();


EventAdd();


return TRUE;
}


In Part 3 we will look at the Irrlicht Viewer code...

Comments

Popular posts from this blog

Creating a renderer using a video post plugin

Selecting a Game Engine

C++ Code Generation using T4 Templates