/*
  File name: COGLClass.cpp
  Copyright: (C) 2005 - 2013 Tomasz Kazmierczak

  This file is part of Cytadela

 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program. If not, see <http://www.gnu.org/licenses/>.  *

*/

//not on win32!
#if not defined(__WIN32__)
//for compatibility with different OpenGL API's
#define GL_GLEXT_PROTOTYPES
#endif

#include "farread.h"
#include <cstdlib>
#include <algorithm>
#include <math.h>
#include <memory.h>
#include <SDL/SDL_video.h>
#include "COGLClass.h"

//echm... that windows...
#if defined(__WIN32__)
PFNGLACTIVETEXTUREPROC       glActiveTexture = 0;
PFNGLCLIENTACTIVETEXTUREPROC glClientActiveTexture = 0;
#endif

bool COGLClass::initOGL(GLsizei width, GLsizei height)
{
	//check the Open GL version and extensions
	if(not checkOGL())
		return false;

	//z-buffer
	glEnable(GL_DEPTH_TEST);
	//stencil buffer clear value
	glClearStencil(0);
	//we don't need Gouraud shading
	glShadeModel(GL_FLAT);
	//back face culling
	glEnable(GL_CULL_FACE);
	glFrontFace(GL_CCW);
	glCullFace(GL_BACK);
	//no light
	glDisable(GL_LIGHTING);
	//fog
	glEnable(GL_FOG);
	glFogi(GL_FOG_MODE, GL_EXP2);
	glFogf(GL_FOG_DENSITY, 0.0007f);
	float color[] = {0.188f, 0.188f, 0.188f, 1.0f};
	glFogfv(GL_FOG_COLOR, color);
	glHint(GL_FOG_HINT, GL_NICEST);
	//color blending (for transparency)
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	//stream vertex arrays
	glEnableClientState(GL_VERTEX_ARRAY);
	glClientActiveTexture(GL_TEXTURE0);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	//textures - texturing unit 0
	glActiveTexture(GL_TEXTURE0);
	glEnable(GL_TEXTURE_2D);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
	//texturing unit 1
	glActiveTexture(GL_TEXTURE1);
	//combine mode
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
	//alpha blend with texture from unit 0
	glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE);
	glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE);
	glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
	glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PREVIOUS);
	glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
	glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_TEXTURE);
	glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA);
	//add alpha channels
	glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_ADD);
	glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS);
	glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
	glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_TEXTURE);
	glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
	//perspective correction hint
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	//mipmap generation hint
	glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
	//set the OLG's projection matrix
	projectionMatrix(width, height);
	//init OSD interface
	osd.init(width, height);
	return true;
}

//yeah... I know - there is gluCheckExtension, but in GLU v1.3, and guess what... I couldn't find any GLU 1.3
//development files (namely: libglu32.a v1.3) for windows :[ I guess for windows there is only GLU 1.2
bool checkGLExtension(const char *extname, const GLubyte *extlist)
{
	char *ptr = (char *)extlist;
	while(true) {
		//find another occurance of the extname string in the extlist string
		ptr = strstr(ptr, extname);
		//if not found
		if(ptr == 0)
			break;
		//move the pointer to the end of the extension name
		ptr = &ptr[strlen(extname)];
		//if this is for real the end of the extension name
		if(ptr[0] == ' ' or ptr[0] == '\0')
			return true;
		//if not - search on
	}
	return false;
}

bool COGLClass::checkOGL()
{
	//first print the version info to stdout (for information purposes)
	fprintf(stdout, "OpenGL version: %s\n", (char *)glGetString(GL_VERSION));
	fprintf(stdout, "Driver vendor: %s\n", (char *)glGetString(GL_VENDOR));
	fprintf(stdout, "GL renderer: %s\n\n", (char *)glGetString(GL_RENDERER));
	fprintf(stdout, "GLU version: %s\n\n", (char *)gluGetString(GLU_VERSION));

	//check the GLU version (copy only the major version number - first 3 bytes)
	char verNum[] = {'\0', '\0', '\0', '\0'};
	memcpy(verNum, gluGetString(GLU_VERSION), 3 * sizeof(char));
	if(atof(verNum) < 1.1f) {
		fprintf(stderr, "GLU library in version >= 1.1 is required!\n");
		return false;
	}

	//now check the OpenGL version and extensions
	memcpy(verNum, glGetString(GL_VERSION), 3 * sizeof(char));
	float ver = atof(verNum);
	//if less than 1.2 then don't go any further
	if(ver < 1.2f) {
		fprintf(stderr, "NEED AT LEAST OPENGL VERSION 1.2 WITH FOLLOWING EXTENSIONS:\n"
		        "GL_ARB_multitexture, GL_ARB_texture_env_add, GL_ARB_texture_env_combine, "
		        "GL_ARB_texture_border_clamp, GL_ARB_texture_mirrored_repeat\n");
		return false;
	}
	//check for required extensions if necessary
	if(ver < 1.4f and
	   not checkGLExtension("GL_ARB_texture_mirrored_repeat", glGetString(GL_EXTENSIONS))) {
		fprintf(stderr, "The GL_ARB_texture_mirrored_repeat extension is not present\n");
		return false;
	}
	if(ver < 1.3f and
	   (not checkGLExtension("GL_ARB_multitexture", glGetString(GL_EXTENSIONS)) or
	    not checkGLExtension("GL_ARB_texture_env_add", glGetString(GL_EXTENSIONS)) or
	    not checkGLExtension("GL_ARB_texture_env_combine", glGetString(GL_EXTENSIONS)) or
	    not checkGLExtension("GL_ARB_texture_border_clamp", glGetString(GL_EXTENSIONS)))) {
		fprintf(stderr, "At least one of the following needed GL extensions is not present:\n"
		                "GL_ARB_multitexture, GL_ARB_texture_env_add, "
		                "GL_ARB_texture_env_combine, "
		                "GL_ARB_texture_border_clamp\n");
		return false;
	}
	//under windows below functions are treated as extensions of OpenGL API,
	//as the API is only in version 1.2 (ok, maby there is a newer version,
	//but I haven't seen such...), so they must be loaded dynamically in the
	//windows version of Cytadela
#if defined(__WIN32__)
	//we load the functions with the ARB suffix, because some OpenGL drivers in
	//version less than 1.3 (the case when these functions are OGL extensions)
	//contain only the ARB declarations
	glActiveTexture = (PFNGLACTIVETEXTUREPROC)SDL_GL_GetProcAddress("glActiveTextureARB");
	if(not glActiveTexture) {
		fprintf(stderr, "Couldn't load glActiveTexture function\n");
		return false;
	}
	glClientActiveTexture = (PFNGLCLIENTACTIVETEXTUREPROC)SDL_GL_GetProcAddress("glClientActiveTextureARB");
	if(not glClientActiveTexture) {
		fprintf(stderr, "Couldn't load glClientActiveTexture function\n");
		return false;
	}
#endif
	return true;
}

void COGLClass::freeOGL()
{
	osd.deleteFXTextures();
	osd.deleteImages();

	//delete OpenGL's texture objects
	for(GLTextureIDVector::size_type i = 0; i != mTextureIDs.size(); i++)
		glDeleteTextures(1, &mTextureIDs[i]);

	osd.clear();
	//clear all lists
	mTextureIDs.clear();
	mTransparentMeshes.clear();
	mOpaqueMeshes.clear();
}

void COGLClass::preDissolve(float r, float g, float b, DissolveType disType)
{
	switch(disType) {
		case DT_FADEIN: {
			//load color buffer to accumulation buffer
			glAccum(GL_LOAD, 1.0f);
			//clear color buffer to initial color
			glClearColor(r, g, b, 1.0f);
			glClear(GL_COLOR_BUFFER_BIT);
			break;
		}
		case DT_DISSOLVE: {
			//load color buffer to accumulation buffer
			glAccum(GL_LOAD, 1.0f);
			break;
		}
		case DT_FADEOUT: {
			//load color buffer to accumulation buffer
			glAccum(GL_LOAD, 1.0f);
			break;
		}
	}
}

void COGLClass::dissolveFrame(DissolveType disType, float fadeValue)
{
	switch(disType) {
		case DT_FADEIN:
			glAccum(GL_RETURN, fadeValue);
			break;
		case DT_DISSOLVE:
			renderFrame();
			glAccum(GL_MULT, 1.0f - fadeValue);
			glAccum(GL_ACCUM, fadeValue);
			glAccum(GL_RETURN, 1.0f);
			break;
		case DT_FADEOUT:
			glAccum(GL_RETURN, 1.0f - fadeValue);
			break;
	}
}

void COGLClass::projectionMatrix(GLsizei width, GLsizei height)
{
	//set the viewport
	glViewport(0, (GLsizei)(height * 0.2f), width, (GLsizei)(height * 0.8f));

	//set the projection matrix
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	float aspectRatio = (height != 0) ? (float)width / (height * 0.8) : (float)width;
	gluPerspective(45.0f, aspectRatio, 1.0f, 3200.0f);
	//now calculate cosinus of horizontal view angle
	//coshva = 1 / sqrt(l^2 + 1); l = aspectRatio / tan(fovy);
	//fovy = 45deg = pi/2 (vertical view angle); tan(pi/2) = 1
	mCosHVA = (float)(1.0 / sqrt(aspectRatio * aspectRatio + 1.0));
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

void COGLClass::renderFrame()
{
	glClearColor(0.188f, 0.188f, 0.188f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	drawMeshes();

	glFinish();
}

void COGLClass::setCamMatrix(float x, float z, float sin, float cos)
{
	//update some global variables
	mSin = sin;
	mCos = cos;
	mX = x;
	mZ = z;
	//matrix mode is set to modelview matrix so there is no need to set it here;
	//we just make it identity and set the view
	glLoadIdentity();
	gluLookAt(x, 50.0f, z, x + sin, 50.0f, z - cos, 0.0f, 1.0f, 0.0f);
}

bool COGLClass::setGOManager(GameObjectsManager *objects)
{
	mObjects = objects;

	//set the capacity of texture ids' vector and set all it's entries to 0
	//(OpenGL begins numbering the textures from 1)
	mTextureIDs.assign(mObjects->getTexturesQuantity(), 0);
	//create transparent and opaque mesh lists
	createMeshLists();

	for(uint32_t j = 0; j < objects->getTexturesQuantity(); ++j) {
		if(not loadTexture(j)) {
			fprintf(stderr, "COGLClass::set3DgInterface(): !loadTexture(%d)\n", j);
			return false;
		}
	}
	return true;
}

void COGLClass::createMeshLists()
{
	for(MeshMap3Dg::iterator i = mObjects->meshes.begin(); i != mObjects->meshes.end(); i++) {
		if(i->second->getProperty(MRP_TRANSPARENT))
			mTransparentMeshes.push_back(i->second);
		else mOpaqueMeshes.push_back(i->second);
	}
}

void COGLClass::addMeshToRenderList(Mesh3Dg *mesh)
{
	if(mesh->getProperty(MRP_TRANSPARENT))
		mTransparentMeshes.push_back(mesh);
	else mOpaqueMeshes.push_back(mesh);
}

void COGLClass::remMeshFromRenderList(Mesh3DgUID uid)
{
	//find the mesh in transparent meshes list
	Mesh3DgList::iterator i = mTransparentMeshes.begin();
	for(; i != mTransparentMeshes.end(); i++) {
		if((*i)->getUID() == uid)
			break;
	}
	//if the mesh is present in this list
	if(i != mTransparentMeshes.end()) {
		mTransparentMeshes.erase(i);
		return;
	}
	//and opaque meshes
	i = mOpaqueMeshes.begin();
	for(; i != mOpaqueMeshes.end(); i++) {
		if((*i)->getUID() == uid)
			break;
	}
	if(i != mOpaqueMeshes.end()) {
		mOpaqueMeshes.erase(i);
		return;
	}
}

Vector3Dg gPosition;

//transparent objects sorting algorithm
bool transpSort(Mesh3Dg *mesh1, Mesh3Dg *mesh2)
{
	//if one of the meshes is non-renderable or has no base then don't compare
	if(not mesh1->getProperty(MRP_HAS_BASE) or not mesh1->getProperty(MRP_RENDERABLE))
		return false;
	if(not mesh2->getProperty(MRP_HAS_BASE) or not mesh2->getProperty(MRP_RENDERABLE))
		return true;

	//if door then calculate center from it's frame
	GameObject *object = (GameObject *)mesh1->getChild();
	if(object and object->getType() == GOTYPE_DOOR) {
		//get the frame's mesh
		mesh1 = ((GODoor *)object)->getFrameMesh();
		//if second mesh is first mesh's frame then we know which mesh
		//is to be rendered first
		if(mesh1->getUID() == mesh2->getUID())
			return true;
	}
	object = (GameObject *)mesh2->getChild();
	if(object and object->getType() == GOTYPE_DOOR) {
		mesh2 = ((GODoor *)object)->getFrameMesh();
		if(mesh2->getUID() == mesh1->getUID())
			return false;
	}

	//center of the first mesh
	Vector3Dg sCenter = mesh1->geomCenter() - gPosition;
	double f1 = sCenter.lengthPow2();
	//center of the second mesh
	sCenter = mesh2->geomCenter() - gPosition;
	double f2 = sCenter.lengthPow2();

	//compare vector lengths^2 (the same effect as in case of vector lengths,
	//but less calculations - no sqrt() func - and more accurate:)
	if(f1 < f2)
		return false;
	//if equal - check the order in the objects' array
	if(f1 == f2) {
		if(mesh1->getUID() > mesh2->getUID())
			return false;
		return true;
	}
	return true;
}

void COGLClass::drawMeshes()
{
	//floor and celling (render 'em here 'cause they wouldn't pass the visiblity test
	//in some cases and they should be visible always)
	MeshBase3Dg *floorBase = mObjects->meshes[0]->getBase();
	setTextures(mObjects->meshes[0]);
	glVertexPointer(3, GL_FLOAT, 0, floorBase->getVCoordsStream());
	glDrawArrays(GL_TRIANGLES, 0, floorBase->getTriansQuantity() * 3);

	MeshBase3Dg *ceilingBase = mObjects->meshes[1]->getBase();
	setTextures(mObjects->meshes[1]);
	glVertexPointer(3, GL_FLOAT, 0, ceilingBase->getVCoordsStream());
	glDrawArrays(GL_TRIANGLES, 0, ceilingBase->getTriansQuantity() * 3);

	for(Mesh3DgList::iterator i = mOpaqueMeshes.begin(); i != mOpaqueMeshes.end(); i++) {
		Mesh3Dg *mesh = *i;
		MeshBase3Dg *base = mesh->getBase();
		if(base == floorBase or base == ceilingBase)
			continue;
		if(not mesh->getProperty(MRP_HAS_BASE) or not mesh->getProperty(MRP_RENDERABLE))
			continue;
		//visiblity test
		const Point3Dg *firstVertexCoords = base->getVCoordsStream();
		//move back the camera cords a little to avoid culling objects that are
		//near the clipping plane
		float vx = firstVertexCoords->x - (mX - 110.0f * mSin);
		float vz = firstVertexCoords->z - (mZ + 110.0f * mCos);
		float length = sqrtf(vx * vx + vz * vz);
		//visiblity test
		//we need -cosinus, so in dot product we can substract in stead of adding
		if((((vx / length) * mSin) - ((vz / length) * mCos)) < mCosHVA or length > 3200.0f)
			continue;
		//polygon offset
		if(mesh->getProperty(MRP_Z_MINUS_OFFSET)) {
			glPolygonOffset(-1.0f, -15.0f);
			glEnable(GL_POLYGON_OFFSET_FILL);
		}
		setTextures(mesh);
		glVertexPointer(3, GL_FLOAT, 0, base->getVCoordsStream());
		glDrawArrays(GL_TRIANGLES, 0, base->getTriansQuantity() * 3);
		glDisable(GL_POLYGON_OFFSET_FILL);
	}

	//sort transparent objects
	gPosition = Vector3Dg(mX, 0.0f, mZ);
	mTransparentMeshes.sort(transpSort);

	for(Mesh3DgList::iterator i = mTransparentMeshes.begin(); i != mTransparentMeshes.end(); i++) {
		Mesh3Dg *mesh = *i;
		if(not mesh->getProperty(MRP_HAS_BASE) or not mesh->getProperty(MRP_RENDERABLE))
			continue;
		MeshBase3Dg *base = mesh->getBase();
		//visiblity test
		const Point3Dg *firstVertexCoords = base->getVCoordsStream();
		float fVx = mesh->translation.r4c1 + firstVertexCoords->x - (mX - 110.0f * mSin);
		float fVz = mesh->translation.r4c3 + firstVertexCoords->z - (mZ + 110.0f * mCos);
		float length = sqrtf(fVx * fVx + fVz * fVz);
		if((((fVx / length) * mSin) - ((fVz / length) * mCos)) < mCosHVA or length > 3200.0f)
			continue;
		//turn off Z buffer writing (it might have been enabled for previous mesh)
		glDepthMask(GL_FALSE);
		//stencil mask for horizontal door
		GameObject *obj = (GameObject *)mesh->getChild();
		if(obj and obj->getType() == GOTYPE_DOOR) {
			Mesh3Dg *frame = ((GODoor *)obj)->getFrameMesh();
			if(frame->getProperty(MRP_HAS_BASE) and frame->getProperty(MRP_RENDERABLE)) {
				glEnable(GL_STENCIL_TEST);
				drawStencilMask(frame);
			}
		}
		//most of transparent meshes are dynamic, so they must be transformed
		glPushMatrix();
		glMultMatrixf((GLfloat *)mesh->translation.m);
		glMultMatrixf((GLfloat *)mesh->rotation.m);
		glMultMatrixf((GLfloat *)mesh->scaling.m);
		//polygon offset
		if(mesh->getProperty(MRP_Z_PLUS_OFFSET)) {
			glPolygonOffset(1.0f, 1.0f);
			glEnable(GL_POLYGON_OFFSET_FILL);
		}
		//if z-buffer writing has to be enabled for this mesh (for example door frame)
		if(mesh->getProperty(MRP_Z_WRITE))
			glDepthMask(GL_TRUE);
		setTextures(mesh);
		glVertexPointer(3, GL_FLOAT, 0, base->getVCoordsStream());
		glDrawArrays(GL_TRIANGLES, 0, base->getTriansQuantity() * 3);
		glDisable(GL_STENCIL_TEST);
		glDisable(GL_POLYGON_OFFSET_FILL);
		glPopMatrix();
	}
	glDepthMask(GL_TRUE);
}

void COGLClass::drawStencilMask(Mesh3Dg *mesh)
{
	glClear(GL_STENCIL_BUFFER_BIT);
	glStencilFunc(GL_ALWAYS, 1, 1);
	glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
	MeshBase3Dg *base = mesh->getBase();
	glVertexPointer(3, GL_FLOAT, 0, base->getVCoordsStream());
	setTextures(mesh);
	glDrawArrays(GL_TRIANGLES, 0, base->getTriansQuantity() * 3);

	glStencilFunc(GL_EQUAL, 1, 1);
	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
}

void COGLClass::setTextures(Mesh3Dg *mesh)
{
	Material3Dg *mtl = mObjects->materials[mesh->materialNumber];
	MeshBase3Dg *base = mesh->getBase();

	//first texture - make texture unit 0 the active one
	glActiveTexture(GL_TEXTURE0);
	glClientActiveTexture(GL_TEXTURE0);
	glTexCoordPointer(2, GL_FLOAT, 0, base->getTCoordsStream());
	glBindTexture(GL_TEXTURE_2D, mTextureIDs[mtl->getTexture(0)]);

	//if only one texture, then disable the texture unit 1
	if(mtl->texturesQuantity < 2) {
		glActiveTexture(GL_TEXTURE1);
		glDisable(GL_TEXTURE_2D);
		glClientActiveTexture(GL_TEXTURE1);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		return;
	}

	//second texture - make texture unit 1 the active one
	glActiveTexture(GL_TEXTURE1);
	glEnable(GL_TEXTURE_2D);
	glClientActiveTexture(GL_TEXTURE1);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, base->getTCoordsStream());
	glBindTexture(GL_TEXTURE_2D, mTextureIDs[mtl->getTexture(1)]);
}

bool COGLClass::loadTexture(uint32_t texNum)
{
	if(texNum >= mObjects->getTexturesQuantity() or mTextureIDs[texNum] > 0)
		return true;

	ImgDims imgDims;
	Texture3Dg *texture = mObjects->textures[texNum];
	char *farFileName = texture->path;
	char *texFileName = &texture->path[strlen(texture->path) + 1];
	uint8_t *data = farReadImgData(farFileName, texFileName, imgDims);
	if(data == 0) {
		fprintf(stderr, "C3DgObj::loadTexture(): !farReadImgData(%s, %s)\n",
		        farFileName, texFileName);
		return false;
	}

	//determine pixel format
	GLint bytesPerPixel;
	GLenum format;
	switch(texture->type) {
		case PIXEL_MAP: {
			switch(imgDims.bitsPerPixel) {
				case 24:
					format = GL_BGR;
					bytesPerPixel = 3;
					break;
				case 32:
					format = GL_BGRA;
					bytesPerPixel = 4;
					break;
				default: {
					delete[] data;
					fprintf(stdout, "C3DgObj::loadTexture(%s, %s): bad image format!\n",
					        farFileName, texFileName);
					return false;
				}
			}
			break;
		}
		default: {
			delete[] data;
			fprintf(stdout, "C3DgObj::loadTexture(%s, %s): bad texture type!\n",
			        farFileName, texFileName);
			return false;
		}
	}
	glGenTextures(1, &mTextureIDs[texNum]);
	glBindTexture(GL_TEXTURE_2D, mTextureIDs[texNum]);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
	//set the texture's wrap mode
	switch(texture->wrapModeS) {
		case TEX_WRAP_REPEAT:
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
			break;
		case TEX_WRAP_MIRRORED_REPEAT:
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
			break;
		case TEX_WRAP_CLAMP:
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
			break;
		case TEX_WRAP_CLAMP_TO_EDGE:
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
			break;
		case TEX_WRAP_CLAMP_TO_BORDER:
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
			break;
		default:
			break;
	}
	switch(texture->wrapModeT) {
		case TEX_WRAP_REPEAT:
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
			break;
		case TEX_WRAP_MIRRORED_REPEAT:
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
			break;
		case TEX_WRAP_CLAMP:
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
			break;
		case TEX_WRAP_CLAMP_TO_EDGE:
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
			break;
		case TEX_WRAP_CLAMP_TO_BORDER:
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
			break;
		default:
			break;
	}

	//turn on mipmapping for new texture and generate mipmaps
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	GLenum result = gluBuild2DMipmaps(GL_TEXTURE_2D, bytesPerPixel,
	                                  imgDims.width, imgDims.height, format,
	                                  GL_UNSIGNED_BYTE, data);
	delete[] data;
	if(result != GL_NO_ERROR) {
		fprintf(stdout, "C3DgObj::loadTexture(%s, %s):\n"
		        "\tgluBuild2DMipmaps() returned \'%s\'!\n",
		        farFileName, texFileName, gluErrorString(result));
		return false;
	}
	return true;
}

//COSDClass class definition
COSDClass::COSDClass() :
	mImages(0),
	mImgsCount(0),
	mTextScrollSpeed(0.0f),
	mTextPosOffset(0.0f),
	mIncomingChars(0),
	mPlotterPosOffset(0.0f),
	mVisionNoise(false),
	mBloodTexNum(0),
	mMapTextureNum(0),
	mPosCursorTexNum(0),
	mMap(false),
	mPause(false)
{
	memset(mNoiseTexNum, 0, sizeof(mNoiseTexNum));
	memset(mMapImage, 0, sizeof(mMapImage));
	memset(mBloodPixels, 0, sizeof(mBloodPixels));
}

void COSDClass::init(GLsizei width, GLsizei height)
{
	mScreenW = width;
	mScreenH = height;

	static float verts[] =
		//OSD panel
		{0.0f, 0.0f, 320.0f, 0.0f, 320.0f, 40.0f, 0.0f, 40.0f,
		//nrg: hundreds, tens, ones digit
		57.0f, 13.0f, 70.0f, 13.0f, 70.0f, 32.0f, 57.0f, 32.0f,
		73.0f, 13.0f, 86.0f, 13.0f, 86.0f, 32.0f, 73.0f, 32.0f,
		89.0f, 13.0f, 102.0f, 13.0f, 102.0f, 32.0f, 89.0f, 32.0f,
		//weapon img
		184.0f, 13.0f, 216.0f, 13.0f, 216.0f, 38.0f, 184.0f, 38.0f,
		//ammo: hundreds, tens, ones digit
		225.0f, 13.0f, 238.0f, 13.0f, 238.0f, 32.0f, 225.0f, 32.0f,
		241.0f, 13.0f, 254.0f, 13.0f, 254.0f, 32.0f, 241.0f, 32.0f,
		257.0f, 13.0f, 270.0f, 13.0f, 270.0f, 32.0f, 257.0f, 32.0f,
		//cards: blue, green, red
		313.0f, 14.0f, 318.0f, 14.0f, 318.0f, 21.0f, 313.0f, 21.0f,
		313.0f, 22.0f, 318.0f, 22.0f, 318.0f, 29.0f, 313.0f, 29.0f,
		313.0f, 30.0f, 318.0f, 30.0f, 318.0f, 37.0f, 313.0f, 37.0f,
		//bomb img
		3.0f, 3.0f, 44.0f, 3.0f, 44.0f, 36.0f, 3.0f, 36.0f};
	static float texCoords[] =
		{0.0f, 0.0f, 0.625f, 0.0f, 0.625f, 0.15625f, 0.0f, 0.15625f,
		0.0f, 0.4375f, 0.8125f, 0.4375f, 0.8125f, 1.0f, 0.0f, 1.0f,
		0.0f, 0.4375f, 0.8125f, 0.4375f, 0.8125f, 1.0f, 0.0f, 1.0f,
		0.0f, 0.4375f, 0.8125f, 0.4375f, 0.8125f, 1.0f, 0.0f, 1.0f,
		0.0f, 0.21875f, 1.0f, 0.21875f, 1.0f, 1.0f, 0.0f, 1.0f,
		0.0f, 0.4375f, 0.8125f, 0.4375f, 0.8125f, 1.0f, 0.0f, 1.0f,
		0.0f, 0.4375f, 0.8125f, 0.4375f, 0.8125f, 1.0f, 0.0f, 1.0f,
		0.0f, 0.4375f, 0.8125f, 0.4375f, 0.8125f, 1.0f, 0.0f, 1.0f,
		0.0f, 0.125f, 0.625f, 0.125f, 0.625f, 1.0f, 0.0f, 1.0f,
		0.0f, 0.125f, 0.625f, 0.125f, 0.625f, 1.0f, 0.0f, 1.0f,
		0.0f, 0.125f, 0.625f, 0.125f, 0.625f, 1.0f, 0.0f, 1.0f,
		0.0f, 0.0f, 0.65625f, 0.0f, 0.65625f, 0.53125f, 0.0f, 0.53125f};
	mVerts = verts;
	mTexCoords = texCoords;
	memset(mOSDText, ' ', 34 * sizeof(char));
	mOSDText[34] = '\0';

	//plotter coords
	mPlotterWScale = 1.0f;
	float plotterV[] = {83.0f, 20.0f, 139.0f, 20.0f, 83.0f, 32.0f, 139.0f, 32.0f};
	float plotterT[] = {0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f};
	memcpy(mPlotterVerts, plotterV, 8 * sizeof(float));
	memcpy(mPlotterTexCoords, plotterT, 8 * sizeof(float));
	mPlotterElements = 1;
}

void COSDClass::clear()
{
	memset(mNoiseTexNum, 0, sizeof(mNoiseTexNum));
	memset(mMapImage, 0, sizeof(mMapImage));
	memset(mBloodPixels, 0, sizeof(mBloodPixels));
}

bool COSDClass::createImagesArray(uint32_t imagesCount)
{
	if(mImages != 0)
		deleteImages();
	mImages = new GLuint[imagesCount];
	if(mImages == 0) {
		fprintf(stderr, "COSDClass::createImagesArray(): !mImages - Out of memory\n");
		return false;
	}
	//set all the texture indexes to a value of 0 (OpenGL begins numbering the textures from 1)
	memset(mImages, 0, imagesCount * sizeof(GLuint));

	mImgsCount = imagesCount;
	return true;
}

bool COSDClass::loadImg(uint32_t num, const char* farFilePath, const char* imgFileName)
{
	//if a texture is already loaded to the object - we don't load
	if(num >= mImgsCount or mImages[num] > 0)
		return true;

	ImgDims imgDims;
	uint8_t *data = farReadImgData(farFilePath, imgFileName, imgDims);
	if(data == 0) {
		fprintf(stderr, "COSDClass::loadImg(): !farReadImgData(%s, %s)\n", farFilePath, imgFileName);
		return false;
	}

	//determine pixel format
	GLint bytesPerPixel;
	GLenum format;
	switch(imgDims.bitsPerPixel) {
	case 24:
		format = GL_BGR;
		bytesPerPixel = GL_RGB8;
		break;
	case 32:
		format = GL_BGRA;
		bytesPerPixel = GL_RGBA8;
		break;
	default: {
			delete[] data;
			fprintf(stdout, "COSDClass::loadImg(%s, %s): bad image format!\n", farFilePath, imgFileName);
			return false;
		}
	}
	glGenTextures(1, &mImages[num]);
	glBindTexture(GL_TEXTURE_2D, mImages[num]);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

	//clear the error buffer before creating the texture
	while(glGetError() != GL_NO_ERROR);

	//don't need mipmaps - images are to small to be minified in used
	//resolutions
	glTexImage2D(GL_TEXTURE_2D, 0, bytesPerPixel, imgDims.width,
	             imgDims.height, 0, format, GL_UNSIGNED_BYTE, data);
	delete[] data;
	//check if any error occured
	GLenum result = glGetError();
	if(result != GL_NO_ERROR) {
		fprintf(stdout, "COSDClass::loadImg(%s, %s):\n"
		                "\tglTexImage2D() failed\n"
		                "\tglGetError() returned \'%s\'!\n",
		                farFilePath, imgFileName, gluErrorString(result));
		return false;
	}
	return true;
}

void COSDClass::setImage(uint32_t imgNum, OSDElement element)
{
	mElementTex[element] = imgNum;
}

void COSDClass::deleteImages()
{
	if(mImages) {
		glDeleteTextures(mImgsCount, mImages);
		delete[] mImages;
		mImages = 0;
		mImgsCount = 0;
	}
}

void COSDClass::deleteFXTextures()
{
	mapOff();
	freeBlood();
	freeNoise();
}

void COSDClass::enable2D()
{
	//now we draw on whole screen
	//(vision noise and map are also drawn by OSDClass)
	glViewport(0, 0, mScreenW, mScreenH);

	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glOrtho(0.0, 320.0, 0.0, 200.0, 1.0, -1.0);

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
}

void COSDClass::disable2D()
{
	//set the viewport back to its original size and position
	glViewport(0, (GLsizei)(mScreenH * 0.2f), mScreenW, (GLsizei)(mScreenH * 0.8f));
	//every matrix mode has its own stack, so the order doesn't matter;
	//the modelview matrix is poped at the end so we don't need to set is as
	//the current matrix mode again
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
}

void COSDClass::draw(float sin, float cos, uint32_t diff)
{
	//disable the texturing unit 1 (we don't wont any artifacts from this unit
	//to be displayed on the OSD, which uses only unit 0)
	glActiveTexture(GL_TEXTURE1);
	glDisable(GL_TEXTURE_2D);
	glClientActiveTexture(GL_TEXTURE1);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	mDiff = diff;
	glActiveTexture(GL_TEXTURE0);
	glClientActiveTexture(GL_TEXTURE0);
	enable2D();
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_STENCIL_TEST);
	//stencil mask for text and heart plotter - we must draw it before
	//anything else
	glDisable(GL_TEXTURE_2D);
	glClear(GL_STENCIL_BUFFER_BIT);
	glStencilFunc(GL_ALWAYS, 1, 1);
	glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
	glBegin(GL_QUADS);
	//text
	glVertex2f(47.0f, 3.0f);
	glVertex2f(319.0f, 3.0f);
	glVertex2f(319.0f, 10.0f);
	glVertex2f(47.0f, 10.0f);
	//plotter
	glVertex2f(110.0f, 20.0f);
	glVertex2f(139.0f, 20.0f);
	glVertex2f(139.0f, 32.0f);
	glVertex2f(110.0f, 32.0f);
	glEnd();
	glEnable(GL_TEXTURE_2D);
	glDisable(GL_STENCIL_TEST);

	//draw OSD elements
	for(int32_t i = 0; i < OSD_ELEMENTS_COUNT; ++i) {
		if(i == OSD_PLOTTER or i == OSD_TEXT)
			continue;
		glTexCoordPointer(2, GL_FLOAT, 0, mTexCoords + i * 8);
		glBindTexture(GL_TEXTURE_2D, mImages[mElementTex[i]]);
		glVertexPointer(2, GL_FLOAT, 0, mVerts + i * 8);
		glDrawArrays(GL_QUADS, 0, 4);
	}
	//draw direction pointer
	glDisable(GL_TEXTURE_2D);
	glBegin(GL_LINES);
	glVertex2f(160.0f, 25.0f);
	glVertex2f(160.0f + 10.0f * sin, 25.0f + 10.0f * cos);
	glEnd();
	glEnable(GL_TEXTURE_2D);

	//draw text
	glEnable(GL_STENCIL_TEST);
	glStencilFunc(GL_EQUAL, 1, 1);
	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
	glBindTexture(GL_TEXTURE_2D, mImages[mElementTex[OSD_TEXT]]);
	drawText();
	//plotter
	glBindTexture(GL_TEXTURE_2D, mImages[mElementTex[OSD_PLOTTER]]);
	drawHeartPlotter();
	glDisable(GL_STENCIL_TEST);
	//vision noise
	if(mVisionNoise and not mMap)
		drawNoise();

	glEnable(GL_DEPTH_TEST);
	disable2D();
}

void COSDClass::addText(const char *text)
{
	uint32_t newChars = (uint32_t)strlen(text);
	uint32_t osdTextLen = strlen(mOSDText);
	//text buffer length is 128 chars (including terminating null char)
	if(osdTextLen + newChars > 127)
		return;
	mIncomingChars += newChars;
	sprintf(&mOSDText[osdTextLen], "%s", text);
	mTextScrollSpeed = 0.4f;
}

void COSDClass::drawText()
{
	mTextPosOffset += mDiff * mTextScrollSpeed;
	//if offset is greater than character width
	while(mTextPosOffset >= 8.0f) {
		//decrease the offset
		mTextPosOffset -= 8.0f;
		//move the text one letter left
		int32_t strLen = strlen(mOSDText);
		for(int32_t i = 0; i < strLen; ++i)
			mOSDText[i] = mOSDText[i + 1];
		if(strLen == 34) {
			mOSDText[33] = ' ';
			mOSDText[34] = '\0';
		}
		if(mIncomingChars)
			--mIncomingChars;
	}
	if(not mIncomingChars)
		mTextScrollSpeed = 0.0533333f;

	float x = 47.0f - mTextPosOffset;

	for(int32_t i = 0; mOSDText[i] != '\0'; ++i) {
		char c = mOSDText[i];
		int32_t xOffs = c % 16 + 1;
		int32_t yOffs = 16 - (c / 16);
		glBegin(GL_TRIANGLE_STRIP);
		glTexCoord2f(0.0625f * (xOffs - 1), 0.0625f * (yOffs - 1) + 0.0078125f);
		glVertex2f(x, 3.0f);
		glTexCoord2f(0.0625f * xOffs, 0.0625f * (yOffs - 1) + 0.0078125f);
		glVertex2f(x + 8.0f, 3.0f);
		glTexCoord2f(0.0625f * (xOffs - 1), 0.0625f * yOffs);
		glVertex2f(x, 10.0f);
		glTexCoord2f(0.0625f * xOffs, 0.0625f * yOffs);
		glVertex2f(x + 8.0f, 10.0f);
		glEnd();
		x += 8.0f;
	}
}

void COSDClass::setHeartSpeed(float heartBeat)
{
	mPlotterWScale = 11.0f / heartBeat;
}

void COSDClass::drawHeartPlotter()
{
	mPlotterPosOffset = mDiff * 0.0533333f;
	//if offset is greater than plotter width
	float scaledW = mPlotterWScale * 56.0f;
	//minimum value for fScaledW is 11.0 and maximum value for
	//mPlotterPosOffset is 13.(3) so in extremal case mPlotterPosOffset
	//would have to be decreased only once and there is no need to use while()
	//loop here
	if(mPlotterPosOffset >= scaledW)
		mPlotterPosOffset -= scaledW;

	//move elements
	if(not mMap and not mPause) {
		for(int32_t i = 0; i < mPlotterElements; ++i) {
			mPlotterVerts[i * 8] -= mPlotterPosOffset;
			mPlotterVerts[i * 8 + 2] -= mPlotterPosOffset;
			mPlotterVerts[i * 8 + 4] -= mPlotterPosOffset;
			mPlotterVerts[i * 8 + 6] -= mPlotterPosOffset;
		}
	}
	//if last element's right x coord is < than plotter window's right x coord
	//than add new element
	int32_t tabPos = ((mPlotterElements - 1) * 8) + 2;
	float x = mPlotterVerts[tabPos];
	if(x < 139.0f and mPlotterElements < 4) {
		float newVerts[] = {x, 20.0f, x + scaledW, 20.0f, x, 32.0f, x + scaledW, 32.0f};
		float newTC[] = {0.0f, 0.0f, mPlotterWScale, 0.0f, 0.0f, 1.0f, mPlotterWScale, 1.0f};
		memcpy(&mPlotterVerts[mPlotterElements * 8], newVerts, 8 * sizeof(float));
		memcpy(&mPlotterTexCoords[mPlotterElements * 8], newTC, 8 * sizeof(float));
		++mPlotterElements;
	}
	//the part of element outside the window can be resized
	else {
		x = mPlotterVerts[tabPos - 2] + scaledW;
		if(x < 139.0f) {
			mPlotterVerts[tabPos] = mPlotterVerts[tabPos + 4] = 139.0f;
			float tc = (139.0f - mPlotterVerts[tabPos - 2]) / 56.0f;
			mPlotterTexCoords[tabPos] = mPlotterTexCoords[tabPos + 4] = tc;
		} else {
			mPlotterVerts[tabPos] = mPlotterVerts[tabPos + 4] = x;
			mPlotterTexCoords[tabPos] = mPlotterTexCoords[tabPos + 4] = mPlotterWScale;
		}
	}
	//check if first element is outside the plotter window
	//if so than remove this element
	if(mPlotterVerts[2] < 110.0f) {
		for(int32_t i = 0, j = 1; i < (mPlotterElements - 1); ++i, ++j) {
			//only x coords - y are the same for all elements
			mPlotterVerts[i * 8] = mPlotterVerts[j * 8];
			mPlotterTexCoords[i * 8] = mPlotterTexCoords[j * 8];
			mPlotterVerts[i * 8 + 2] = mPlotterVerts[j * 8 + 2];
			mPlotterTexCoords[i * 8 + 2] = mPlotterTexCoords[j * 8 + 2];
			mPlotterVerts[i * 8 + 4] = mPlotterVerts[j * 8 + 4];
			mPlotterTexCoords[i * 8 + 4] = mPlotterTexCoords[j * 8 + 4];
			mPlotterVerts[i * 8 + 6] = mPlotterVerts[j * 8 + 6];
			mPlotterTexCoords[i * 8 + 6] = mPlotterTexCoords[j * 8 + 6];
		}
		--mPlotterElements;
	}

	//draw
	for(int32_t i = 0; i < mPlotterElements; ++i) {
		glTexCoordPointer(2, GL_FLOAT, 0, mPlotterTexCoords + i * 8);
		glVertexPointer(2, GL_FLOAT, 0, mPlotterVerts + i * 8);
		glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
	}
}

void COSDClass::drawMap(uint8_t playerPosX, uint8_t playerPosY)
{
	enable2D();
	glDisable(GL_DEPTH_TEST);
	//background
	glBindTexture(GL_TEXTURE_2D, mImages[mElementTex[OSD_PANEL]]);
	float bkgndV[] = {0.0f, 40.0f, 320.0f, 40.0f, 0.0f, 200.0f, 320.0f, 200.0f};
	float bkgndT[] = {0.0f, 0.15625f, 0.625f, 0.15625f, 0.0f, 0.78125f, 0.625f, 0.78125f};
	glTexCoordPointer(2, GL_FLOAT, 0, bkgndT);
	glVertexPointer(2, GL_FLOAT, 0, bkgndV);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	//map
	float posX = 8.0f * playerPosX, posY = 8.0f * playerPosY;
	glEnable(GL_STENCIL_TEST);
	//stencil mask for map
	glClear(GL_STENCIL_BUFFER_BIT);
	glStencilFunc(GL_ALWAYS, 1, 1);
	glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
	glBegin(GL_TRIANGLE_STRIP);
	glVertex2f(64.0f, 56.0f);
	glVertex2f(256.0f, 56.0f);
	glVertex2f(64.0f, 184.0f);
	glVertex2f(256.0f, 184.0f);
	glEnd();
	glStencilFunc(GL_EQUAL, 1, 1);
	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
	glBindTexture(GL_TEXTURE_2D, mMapTextureNum);
	float mapV[] = {160.0f - posX, 112.0f - posY, 672.0f - posX, 112.0f - posY,
	                160.0f - posX, 624.0f - posY, 672.0f - posX, 624.0f - posY};
	float mapT[] = {0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f};
	glTexCoordPointer(2, GL_FLOAT, 0, mapT);
	glVertexPointer(2, GL_FLOAT, 0, mapV);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
	glDisable(GL_STENCIL_TEST);
	//position cursor
	glBindTexture(GL_TEXTURE_2D, mPosCursorTexNum);
	float curV[] = {160.0f, 112.0f, 168.0f, 112.0f, 160.0f, 120.0f, 168.0f, 120.0f};
	float curT[] = {0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f};
	glTexCoordPointer(2, GL_FLOAT, 0, curT);
	glVertexPointer(2, GL_FLOAT, 0, curV);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
	glEnable(GL_DEPTH_TEST);
	disable2D();
}

void COSDClass::drawMapPos(MapDataType *mapPos, uint8_t posX, uint8_t posY, GameObjectsManager *objects)
{
	int32_t x = posX * 24, y = posY * 8;
	//background color
	for(int32_t i = 0; i < 8; ++i)
		memset(&mMapImage[1536 * (y + i) + x], 64, 24);

	MapDataType posData = mapPos[4];
	//first the door if closed
	//N
	if(posData & 256) {
		GODoor *door = (GODoor *)objects->getGameObject(mapPos[0]);
		uint32_t mtlNum = door->getFrameMesh()->materialNumber;
		if(mtlNum == 14 or mtlNum == 17)
			memset(&mMapImage[1536 * (y + 7) + x], 96, 24);
	}
	//E
	if(posData & 512) {
		GODoor *door = (GODoor *)objects->getGameObject(mapPos[1]);
		uint32_t mtlNum = door->getFrameMesh()->materialNumber;
		if(mtlNum == 14 or mtlNum == 17) {
			for(int32_t i = 0; i < 8; ++i)
				memset(&mMapImage[1536 * (y + i) + x + 21], 96, 3);
		}
	}
	//S
	if(posData & 1024) {
		GODoor *door = (GODoor *)objects->getGameObject(mapPos[2]);
		uint32_t mtlNum = door->getFrameMesh()->materialNumber;
		if(mtlNum == 14 or mtlNum == 17)
			memset(&mMapImage[1536 * y + x], 96, 24);
	}
	//W
	if(posData & 2048) {
		GODoor *door = (GODoor *)objects->getGameObject(mapPos[3]);
		uint32_t mtlNum = door->getFrameMesh()->materialNumber;
		if(mtlNum == 14 or mtlNum == 17) {
			for(int32_t i = 0; i < 8; ++i)
				memset(&mMapImage[1536 * (y + i) + x], 96, 3);
		}
	}

	//walls - N (only if it isn't door)
	if((posData & 1) and not (posData & 256))
		memset(&mMapImage[1536 * (y + 7) + x], 160, 24);
	//E
	if((posData & 2) and not (posData & 512)) {
		for(int32_t i = 0; i < 8; ++i)
			memset(&mMapImage[1536 * (y + i) + x + 21], 160, 3);
	}
	//S
	if((posData & 4) and not (posData & 1024))
		memset(&mMapImage[1536 * y + x], 160, 24);
	//W
	if((posData & 8) and not (posData & 2048)) {
		for(int32_t i = 0; i < 8; ++i)
			memset(&mMapImage[1536 * (y + i) + x], 160, 3);
	}

	if(posData & 16) {
		switch(objects->meshes[mapPos[5]]->materialNumber) {
			//teleport
			case 30: {
				memset(&mMapImage[1536 * (y + 2) + x + 9], 128, 6);
				memset(&mMapImage[1536 * (y + 3) + x + 6], 128, 3);
				memset(&mMapImage[1536 * (y + 3) + x + 15], 128, 3);
				memset(&mMapImage[1536 * (y + 4) + x + 6], 128, 3);
				memset(&mMapImage[1536 * (y + 4) + x + 15], 128, 3);
				memset(&mMapImage[1536 * (y + 5) + x + 9], 128, 6);
				break;
			}
			//blocade
			case 27:
			case 28:
			case 44: {
				memset(&mMapImage[1536 * (y + 2) + x + 9], 128, 6);
				memset(&mMapImage[1536 * (y + 3) + x + 6], 128, 12);
				memset(&mMapImage[1536 * (y + 4) + x + 6], 128, 12);
				memset(&mMapImage[1536 * (y + 5) + x + 9], 128, 6);
				break;
			}
		}
	}
}

void COSDClass::mapOn()
{
	mMap = true;
	glActiveTexture(GL_TEXTURE0);
	glClientActiveTexture(GL_TEXTURE0);

	//draw position cursor on map
	uint32_t posCursor[64];
	memset(posCursor, 0, 256);
	//create cursor image
	for(int32_t i = 1; i < 7; ++i) {
		posCursor[i * 9] = 0xff0010f0;
		posCursor[(i + 1) * 7] = 0xff0010f0;
	}

	//map texture
	glGenTextures(1, &mMapTextureNum);
	glBindTexture(GL_TEXTURE_2D, mMapTextureNum);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, 512, 512, 0, GL_RGB,
	             GL_UNSIGNED_BYTE, mMapImage);
	//position cursor texture
	glGenTextures(1, &mPosCursorTexNum);
	glBindTexture(GL_TEXTURE_2D, mPosCursorTexNum);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, posCursor);
}

void COSDClass::mapOff()
{
	glDeleteTextures(1, &mPosCursorTexNum);
	mPosCursorTexNum = 0;
	glDeleteTextures(1, &mMapTextureNum);
	mMapTextureNum = 0;
	mMap = false;
}

void COSDClass::addNoise(uint32_t *noiseLines)
{
	glDeleteTextures(2, mNoiseTexNum);
	memset(mNoiseTexNum, 0, sizeof(mNoiseTexNum));

	uint32_t noiseColor = 0xffd09090;
	uint32_t noiseLine[320];
	for(int32_t i = 0; i < 320; ++i)
		noiseLine[i] = noiseColor;

	glGenTextures(2, mNoiseTexNum);
	uint8_t  noisePixels[524288];
	//first noise texture
	for(int32_t i = 0, j = 0; i < 160; ++i, ++j) {
		if(noiseLines[i / 32] & (1 << j))
			memcpy(&noisePixels[i * 2048], noiseLine, 1280);
		else
			memset(&noisePixels[i * 2048], 0, 1280);
		if(j == 31)
			j = -1;
	}
	glBindTexture(GL_TEXTURE_2D, mNoiseTexNum[0]);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, noisePixels);

	//second noise texture
	for(int32_t i = 0, j = 0; i < 160; ++i, ++j) {
		if(noiseLines[(i / 32) + 5] & (1 << j))
			memcpy(&noisePixels[i * 2048], noiseLine, 1280);
		else
			memset(&noisePixels[i * 2048], 0, 1280);
		if(j == 31)
			j = -1;
	}
	glBindTexture(GL_TEXTURE_2D, mNoiseTexNum[1]);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, noisePixels);

	mVisionNoise = true;
	mNoiseTime = 0;
}

void COSDClass::drawNoise()
{
	if(mNoiseTime <= 500) {
		if(not mPause)
			mNoiseTime += mDiff;
		if(mNoiseTime <= 250)
			glBindTexture(GL_TEXTURE_2D, mNoiseTexNum[0]);
		else
			glBindTexture(GL_TEXTURE_2D, mNoiseTexNum[1]);
		glBegin(GL_TRIANGLE_STRIP);
		glTexCoord2f(0.0f, 0.0f);
		glVertex2f(0.0f, 40.0f);
		glTexCoord2f(0.625f, 0.0f);
		glVertex2f(320.0f, 40.0f);
		glTexCoord2f(0.0f, 0.625f);
		glVertex2f(0.0f, 200.0f);
		glTexCoord2f(0.625f, 0.625f);
		glVertex2f(320.0f, 200.0f);
		glEnd();
	} else freeNoise();
}

void COSDClass::freeNoise()
{
	glDeleteTextures(2, mNoiseTexNum);
	memset(mNoiseTexNum, 0, sizeof(mNoiseTexNum));
	mVisionNoise = false;
}

void COSDClass::addBlood(uint8_t *bloodLines, int32_t elements)
{
	glDeleteTextures(1, &mBloodTexNum);
	mBloodTexNum = 0;
	glGenTextures(1, &mBloodTexNum);

	//draw the blood texture
	for(int32_t i = 0; i < elements; ++i) {
		for(int32_t j = 0; j < bloodLines[i]; ++j)
			mBloodPixels[(i * 256) + j] = 0xff0000bb;
	}

	glBindTexture(GL_TEXTURE_2D, mBloodTexNum);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 256, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, mBloodPixels);
}

void COSDClass::drawBlood()
{
	enable2D();
	glDisable(GL_DEPTH_TEST);

	glBindTexture(GL_TEXTURE_2D, mBloodTexNum);
	glBegin(GL_TRIANGLE_STRIP);
	glTexCoord2f(1.0f, 0.0f);
	glVertex2f(0.0f, 0.0f);
	glTexCoord2f(1.0f, 0.625f);
	glVertex2f(320.0f, 0.0f);
	glTexCoord2f(0.0f, 0.0f);
	glVertex2f(0.0f, 200.0f);
	glTexCoord2f(0.0f, 0.625f);
	glVertex2f(320.0f, 200.0f);
	glEnd();

	glEnable(GL_DEPTH_TEST);
	disable2D();
}

void COSDClass::freeBlood()
{
	glDeleteTextures(1, &mBloodTexNum);
	mBloodTexNum = 0;
}
