// ### OpenGTA3 ###
// Configuration file handling

#include "stdafx.h"
#include "gtasaQueue.h"
#include "ogUtil.h"
#include "ogStreaming.h"
#include "ogEngine.h"
#include "ogPools.h"
#include "ogModels.h"
#include "ogHandling.h"

#include <math.h>

#pragma warning(disable : 4996)	// sscanf/str[n]cpy
#pragma warning(disable : 6031) // sscanf return value ignored

// TODO: move to ogModels.cpp
int (*getIfpIndexByName)(char * name) = (int (__cdecl *)(char *))0x4D3990;
void (*ifpAddRef)(int index) = (void (__cdecl *)(int))0x4D3FB0;
void (*ifpRelease)(int index) = (void (__cdecl *)(int))0x4D3FD0;

bool (*_5D7F40)(RpAtomic *) = (bool (__cdecl *)(RpAtomic *))0x5D7F40;
void (*_5D7F00)(RpAtomic *) = (void (__cdecl *)(RpAtomic *))0x5D7F00;
bool (*_5D5B80)(RpAtomic *) = (bool (__cdecl *)(RpAtomic *))0x5D5B80;
void (*_5D5B60)(RpAtomic *) = (void (__cdecl *)(RpAtomic *))0x5D5B60;
void (*_49CE10)(RpAtomic *) = (void (__cdecl *)(RpAtomic *))0x49CE10;

BYTE * _A9B0B0 = (BYTE *)0xA9B0B0;	// use second atomics in objs2c ?


CColModel::CColModel () {
	initBBox ();
	_f28 = 0;
	_f2C = 0;
	flags = 4;	// (flags & 0xFC) | 4
}

CColModel::~CColModel() {
	if (flags&4)
		releaseData ();	
}


void CColModel::releaseData () {
	debug ("CColModel::releaseData: TODO: not implemented yet\n"); 
/*
	if (_f2C) {
		if (flags & 2) {
			_41640 (_f2C);
			free (_f2C);
		} else {
			// _f2C->_40F070 (); // NOTE: destructor call (should not be called directly)
			delete _f2C;
		}
	}
*/
}


void CColModel::initBBox () {
	inf.x = inf.y = inf.z = 1.0f;
	sup.x = sup.y = sup.z = -1.0f;
}

void * CColModel::operator new (size_t) {
	return (*colModelPool)->allocate ();
}

void CColModel::operator delete (void * ptr) {
	(*colModelPool)->release ((BYTE *)ptr);
}

#pragma pack(push,1)
struct ColModelCOL3 {
	D3DVECTOR	inf;	
	D3DVECTOR	sup;	// sup (NOTE: возможно, стоит собрать их в bbox)
	D3DVECTOR	center;
	float		radius;
	WORD		numberOfSpheres;
	WORD		numberOfBoxes;
	DWORD		numberOfFaces;
	DWORD		flags;
	DWORD		offsetSpheres;
	DWORD		offsetBoxes;
	DWORD		offset1;
	DWORD		offsetMeshVertices;
	DWORD		offsetMeshFaces;
	DWORD		offset2;
	DWORD		numberOfShadowMeshFaces;
	DWORD		offsetShadowMeshVertices;
	DWORD		offsetShadowMeshFaces;
};

struct ColDataHeader {
	WORD		numberOfSpheres;
	WORD		numberOfBoxes;
	WORD		numberOfFaces;
	BYTE		numberOfFacesHi;
	BYTE		flags;
	BYTE *		spheres;
	BYTE *		boxes;
	BYTE *		ptr1;	// from offset1
	BYTE *		meshVertices;
	BYTE *		meshFaces;
	DWORD		_f1C;
	DWORD		numberOfShadowMeshFaces;
	DWORD		numberOfShadowMeshVertices;
	BYTE *		shadowMeshVertices;
	BYTE *		shadowMeshFaces;

	DWORD		calculateNumberOfShadowMeshVertices ();
};

struct ColSurface {
	BYTE	material;
	BYTE	flags;
	BYTE	_f2;
	BYTE	light;
};

struct ColSphere {
	D3DVECTOR	center;
	float		radius;
	ColSurface	surface;
};
struct ColInitData {
	DWORD		_f0;
	DWORD		animID;
	DWORD		flags;
	D3DVECTOR	center;
	float		radius;
};
#pragma pack(pop)

void * (*RwMalloc)(int) = (void * (__cdecl *)(int))0x72F420; 

// find maximum vertex index
DWORD ColDataHeader::calculateNumberOfShadowMeshVertices() {
	int num = 0;
	WORD * verts = (WORD *)shadowMeshFaces;
	for (DWORD i = 0; i < numberOfShadowMeshFaces*4; i += 4) {
		if (verts[i] > num)
			num = verts[i];
		if (verts[i+1] > num)
			num = verts[i+1];
		if (verts[i+2] > num)
			num = verts[i+2];
	}
	return numberOfShadowMeshVertices = num+1;
}

// TODO: make CColModel method ?
void processCOL3 (BYTE * coldata, DWORD length, CColModel * model, char * modelName) {	// >> 537CE0 (4)
	ColModelCOL3 * header = (ColModelCOL3 *)coldata;

	model->inf = header->inf;
	model->sup = header->sup;
	model->center = header->center;
	model->radius = header->radius;
	if (header->flags & 2)
		model->flags |=  1;
	else
		model->flags &= ~1;
	
	if (length > sizeof (ColModelCOL3)) {
		BYTE * data = (BYTE *)RwMalloc (length-sizeof (ColModelCOL3)+sizeof (ColDataHeader));	// TODO: new []
		memcpy (data+sizeof (ColDataHeader), coldata+sizeof (ColModelCOL3), length-sizeof (ColModelCOL3));
		ColDataHeader * dh = (ColDataHeader *)data;
		model->_f2C = data;
		
		dh->numberOfSpheres = header->numberOfSpheres;
		dh->numberOfBoxes = header->numberOfBoxes;
		dh->numberOfFaces = (WORD)header->numberOfFaces;
		dh->numberOfFacesHi = (BYTE)((header->numberOfFaces)>>16);
		dh->flags = header->flags & 8 ? 2 : 0;
		dh->numberOfShadowMeshFaces = header->numberOfShadowMeshFaces;
		dh->spheres = header->offsetSpheres ? data+header->offsetSpheres-0x44 : NULL;
		dh->boxes = header->offsetBoxes ? data+header->offsetBoxes-0x44 : NULL;
		dh->ptr1 = header->offset1 ? data+header->offset1-0x44 : NULL;
		dh->meshVertices = header->offsetMeshVertices ? data+header->offsetMeshVertices-0x44 : NULL;
		dh->meshFaces = header->offsetMeshFaces ? data + header->offsetMeshFaces-0x44 : NULL;
		dh->shadowMeshVertices = header->offsetShadowMeshVertices ? data+header->offsetShadowMeshVertices-0x44 : NULL;
		dh->shadowMeshFaces = header->offsetShadowMeshFaces ? data + header->offsetShadowMeshFaces-0x44 : NULL;
		if (dh->shadowMeshFaces && dh->shadowMeshVertices && dh->numberOfShadowMeshFaces > 0) {
			dh->flags |= 4;
			dh->calculateNumberOfShadowMeshVertices ();
		} else 
			dh->numberOfShadowMeshVertices = 0;
		dh->_f1C = 0;
		model->flags |= 2;
	}
}

// выделить блок данных для CColModel 
void CColModel::initModelData(int numSpheres, int numBoxes, int num1, int numMeshVertices, int numFaces, BYTE f) {
	flags |= 2;

	int size = sizeof (ColDataHeader)+numSpheres*20+numBoxes*28+num1*(f ? 36 : 32)+numMeshVertices*6+numFaces*8+3;
	size &= ~3;	// align
	_f2C = RwMalloc (size); // TODO: new[]
	ColDataHeader * dh = (ColDataHeader *)_f2C;
	BYTE * data = (BYTE *)dh;
	
	dh->numberOfSpheres = numSpheres;
	dh->numberOfFacesHi = num1;	// TODO: something wrong...
	dh->numberOfBoxes = numBoxes;
	dh->numberOfFaces = numFaces;

	dh->spheres = data+sizeof(ColDataHeader);
	data = dh->spheres+numSpheres*20;

	dh->ptr1 = num1 ? data : NULL;	
	data += (f ? 36:32)*num1;

	dh->boxes = numBoxes ? data : NULL;
	data += numBoxes*28;

	dh->meshVertices = numMeshVertices ? data : NULL;
	data += (numMeshVertices*6+3)&~3; // здесь нужно выровнять, т.к. все остальные структуры кратны 4, а эта - нет.
	
	dh->meshFaces = numFaces ? data : NULL;

	dh->_f1C = NULL;
	dh->flags = f ? 1 : 0;			
}

// TODO: этому здесь не место!
void CQuaternion::get (RwMatrix * m) {
	float T = m->top.y + m->right.x + m->at.z;
	float S;
	if (T > 0.0f) {
		S = sqrt (1.0f+T)*2.0f;
	
		q.imag.x = (m->top.z - m->at.y)/S;
		q.imag.y = (m->at.x - m->right.z)/S;
		q.imag.z = (m->right.y - m->top.x)/S;
		q.real = S/4.0f;
		return;
	} 
	T = m->right.x - m->top.y - m->at.z;
	if (T > 0.0f) {
		S = sqrt (1.0f+T)*2.0f;

		q.imag.x = S/4.0f;
		q.imag.y = (m->top.x + m->right.y)/S;
		q.imag.z = (m->at.x + m->right.z)/S;
		q.real = (m->top.z - m->at.y)/S;
		return;
	}
	T = m->top.y - m->right.x - m->at.z;
	if (T > 0.0f) {
		S = sqrt (1.0f+T)*2.0f;

		q.imag.x = (m->top.x - m->right.y)/S;
		q.imag.y = S/4.0f;
		q.imag.z = (m->at.y + m->top.z)/S;
		q.real = (m->at.x - m->right.z)/S;
		return;
	}
	T = m->at.z - m->top.y - m->right.x;
	S = sqrt (1.0f+T)*2.0f;

	q.imag.x = (m->at.x + m->right.z)/S;
	q.imag.y = (m->at.y + m->top.z)/S;
	q.imag.z = S/4.0f;
	q.real  = (m->right.y - m->top.x)/S;
}

CModelTimeinfo * CModelTimeinfo::initPairedModel (char * modelName) {
	char pairName[24];
	strcpy_s (pairName, 24, modelName);

	char * s = strstr (pairName, "_nt");
	if(s) 
		strncpy (s, "_dy", 4);
	else {
		s = strstr (pairName, "_dy");
		if (!s)
			return NULL;
		strncpy (s, "_nt", 4);
	}

	DWORD hash = crc32FromUpcaseString ((BYTE *)pairName);
	for (int i = 0; i < 20000; i++) { // TODO: MODEL_COUNT
		if (!modelPtrs[i])
			continue;
		CModelTimeinfo * timeinfo = modelPtrs[i]->getTimeinfo();
		if (!timeinfo || modelPtrs[i]->modelNameHash != hash)
			continue;
		///debug (">> CModelTimeinfo (%s) - pair found: %d\n", modelName, i); 
		pairedModel = i;
		return timeinfo;
	}
	return NULL;
}

void CModelBase::init () {
	usageCount = 0;
	colModel = NULL;
	_f0E = _f10 = txdIndex = -1;
	_f0D = 0;
	drawDistance = 2000.0f;
	atomic = NULL;
	flags = 0x00C0;
}

void CModelBase::clear () {
	clearData ();
	if (colModel && !(flags & 0x80)) 
		delete colModel;
	flags |= 0x80;
	colModel = NULL;
	_f0E = _f10 = txdIndex = -1;
	_f0D = 0;
}

void CModelBase::setTxd(char * txdName) {
	txdIndex = (WORD)txdIndexByNameAlways (txdName);	
}

void CModelBase::addRef () {
	usageCount++;
	txdAddRef (txdIndex);
}

void CModelBase::release () {
	usageCount--;
	txdRelease (txdIndex);
}

void CModelBase::setColModel(CColModel *col, bool initPairedModel) {	// TODO: CColModel
	colModel = col;
	if (initPairedModel) {
		flags |= 0x80;
		CModelTimeinfo * timeinfo = getTimeinfo ();
		if (timeinfo && timeinfo->pairedModel != -1 && modelPtrs[timeinfo->pairedModel])
			modelPtrs[timeinfo->pairedModel]->setColModel (colModel, false);
		return;	// don't touch flags
	}
	flags &= ~0x80;
}

void CModelBase::unsetColModel() {
	if (colModel && (flags & 0x80))
		delete colModel;
	colModel = NULL;	
}

void CModelObjs::clearData() {
	if (!atomic)
		return;
	DWORD * pd2fx = *(DWORD **)(((BYTE *)atomic->geometry)+(*pl253F2F8_TK));
	DWORD d2fx = pd2fx ? *pd2fx : 0;
	_f0D -= (BYTE)d2fx;
	RwFrame * frame = (RwFrame *)atomic->object.object.parent;
	RpAtomicDestroy (atomic);
	RwFrameDestroy (frame);
	atomic = NULL;
	txdRelease (txdIndex);
	if (getIfpIndex() != -1) 
		ifpRelease (getIfpIndex ());

}

RpAtomic * CModelObjs::cloneDataAndSetMM(RwMatrix * mm) {
	if (!atomic)
		return NULL;
	addRef ();
	RpAtomic * a = RpAtomicClone (atomic);
	RwFrame * frame = RwFrameCreate ();
	frame->modelling = *mm;
	RpAtomicSetFrame (a, frame);
	release ();
	return a;
}

RpAtomic * getFirstAtomicCB (RpAtomic * atomic, void * data) { // >> 734810 (2)
	*(RpAtomic **)data = atomic;
	return NULL;
}

RpAtomic * getFirstAtomicInClump (RpClump * clump) { // >> 734820 (30)
	RpAtomic * data = NULL;
	RpClumpForAllAtomics (clump, getFirstAtomicCB, &data);
	return data;
}

RpAtomic * findFirstAtomicWith2dfxCB (RpAtomic * atomic, void * data) {
	DWORD * pd2fx = *(DWORD **)(((BYTE *)atomic->geometry)+(*pl253F2F8_TK));
	if (pd2fx && *pd2fx > 0) {
		*(RpAtomic **)data = atomic;
		return NULL;
	}
	return atomic;
}

RpAtomic * findFirstAtomicWith2dfx (RpClump * clump) {	// >> 734880 (4)
	RpAtomic * data = NULL;
	RpClumpForAllAtomics (clump, findFirstAtomicWith2dfxCB, &data);
	return data;
}

void CModelHier::clearData () {
	if (!clump)
		return;
	RpAtomic * a = findFirstAtomicWith2dfx (clump);
	if (a) {
		DWORD * pd2fx = *(DWORD **)(((BYTE *)a->geometry)+(*pl253F2F8_TK));
		DWORD d2fx = pd2fx ? *pd2fx : 0;
		_f0D -= (BYTE)d2fx;
	}
	RpClumpDestroy (clump);
	clump = NULL;

	txdRelease (txdIndex);
	if (getIfpIndex() != -1) 
		ifpRelease (getIfpIndex ());

	if (flags & 0x800) 
		unsetColModel ();
}

// NOTE: returns RpClump *
RpAtomic * CModelHier::cloneDataAndSetMM(RwMatrix *mm) {
	if (!clump)
		return NULL;
	RpClump * c = (RpClump *)cloneData ();
	RwFrame * frame = (RwFrame *)(c->object.parent);
	frame->modelling  = *mm;
	return (RpAtomic *)c;
}

RpAtomicCallBackRender	AtomicDefaultRenderCallBack = (RpAtomicCallBackRender)0x7491C0;

DWORD * skinAtomicOffset = (DWORD *)0xC978A4;
#define RpSkinAtomicSetHAnimHierarchy(a,h) *((void **)(((BYTE *)a)+*skinAtomicOffset))=h;

void (*_4D6720)(RpClump *) = (void (__cdecl *)(RpClump *))0x4D6720; // (8)
static RpHAnimHierarchy * (*frameGetHAnimHierarchy)(RwFrame * f) = (RpHAnimHierarchy * (__cdecl *)(RwFrame *))0x734AB0;	

RtAnimAnimation  * (*RtAnimAnimationCreate)(int typeID, int numFrames, int flags, float duration) = 
(RtAnimAnimation * (__cdecl *)(int, int, int, float))0x7CCE40;

bool (*RtAnimInterpolatorSetCurrentAnim)(RtAnimInterpolator *animI, RtAnimAnimation *anim) = 
(bool (__cdecl *)(RtAnimInterpolator *, RtAnimAnimation *))0x7CD5A0;

void (*_4D4410)(RpClump *, CIfpAnimations *, int, float) = (void (__cdecl *)(RpClump *, CIfpAnimations *, int, float))0x4D4410;

CIfpFile * ifpFiles = (CIfpFile *)0xB5D4A0;	// TODO: CIfpFile ifpFiles[180];
CIfpAnimations * ifpAnimations = (CIfpAnimations *)0xB4EA40; // TODO: CIfpAnimations ifpAnimations[2500];

RpHAnimHierarchy * clumpGetHAnimHierarchy (RpClump * c) {	// TODO: inline or make define
	return frameGetHAnimHierarchy ((RwFrame *)c->object.parent);
}

static RpAtomic * setAtomicHAnimHierarchyCB (RpAtomic * atomic, void * data) {
	if (data) {
		RpSkinAtomicSetHAnimHierarchy(atomic, data);
		return NULL;
	}
	RpSkinAtomicSetHAnimHierarchy (atomic, frameGetHAnimHierarchy ((RwFrame *)atomic->object.object.parent));
	return atomic;
}

static RtAnimAnimation * gtaAnimationCreate (RpHAnimHierarchy * hier) {
	if (!hier)
		return NULL;
	int num  = hier->numNodes;
	RtAnimAnimation * anim = RtAnimAnimationCreate (0x253F2FB, 0, 0, 0.0f);
	if (anim)
		anim->numFrames = num*2;
	return anim;
}

static CIfpAnimations * getAnimationForModel (DWORD hash, CIfpFile * ifp) {
	CIfpAnimations * anims = ifpAnimations+ifp->startAnimation;
	for (DWORD i = 0; i < ifp->animationCount; i++) 
		if (anims[i].hash == hash)
			return anims+i;
	return NULL;
}

RpAtomic * CModelHier::cloneData() {	
	if (!clump)
		return NULL;

	addRef ();
	RpClump * c = RpClumpClone (clump);
	RpAtomic * a = getFirstAtomicInClump (clump);
	if (a && RpSkinGeometryGetSkin (a->geometry) && !(flags & 0x200)) {
		RpHAnimHierarchy * hier = frameGetHAnimHierarchy ((RwFrame *)c->object.parent);
		RpClumpForAllAtomics (c, setAtomicHAnimHierarchyCB, hier);
		RtAnimInterpolatorSetCurrentAnim (hier->currentAnim, gtaAnimationCreate (hier));
		hier->flags = 0x3000;
	}

	if (flags & 0x100) {
		_4D6720 (c);
		CIfpAnimations * anim = getAnimationForModel (modelNameHash, ifpFiles+ifpIndex);
		if (anim) 
			_4D4410 (c, anim, 2, 1.0);		
	}

	release ();
	return (RpAtomic *)c;
}
// TODO: make CModelHier method or inline
static void (*setClumpVisibilityCallback)(RpClump *, CModelHier *) = 
(void (__cdecl *)(RpClump *, CModelHier *))0x733750;

RpAtomic * (*_4C4F30)(RpAtomic *, void *) = (RpAtomic * (__cdecl *)(RpAtomic *, void *))0x4C4F30;

// TODO: сделать после того, как будет описана RpSkin (возвращается поле по смещению 0x18)
RwMatrixWeights * (*RpSkinGetVertexBoneWeights)( RpSkin *skin ) = (RwMatrixWeights *(__cdecl *) (RpSkin *))0x7C77F0;

RwFrame * (*findFrameByNodeName)(RpClump * c, char * name) = (RwFrame * (__cdecl *)(RpClump *, char *))0x4C5400;
RpAtomic * (*getFrameFirstObject)(RwFrame *)  = (RpAtomic * (__cdecl *)(RwFrame *))0x7348C0;
void (*atomicSetAlphaForAllMaterials)(RpAtomic *, BYTE) = (void (__cdecl *)(RpAtomic *, BYTE))0x6D2960;

static RpAtomicCallBackRender weaponRenderCB = (RpAtomicCallBackRender)0x733670;	// TODO: собрать все такие коллбэки вместе
static RpAtomicCallBackRender pedRenderCB = (RpAtomicCallBackRender)0x7335B0;


void CModelHier::setClump(RpClump *c) {
	if (clump) {
		RpAtomic * a = findFirstAtomicWith2dfx (clump);
		if (a) {
			DWORD * pd2fx = *(DWORD **)(((BYTE *)a->geometry)+(*pl253F2F8_TK));
			DWORD d2fx = pd2fx ? *pd2fx : 0;
			_f0D -= (BYTE)d2fx;
		}
	}
	clump = c;
	if (clump) {
		RpAtomic * a = findFirstAtomicWith2dfx (clump);
		if (a) {
			DWORD * pd2fx = *(DWORD **)(((BYTE *)a->geometry)+(*pl253F2F8_TK));
			DWORD d2fx = pd2fx ? *pd2fx : 0;
			_f0D += (BYTE)d2fx;
		}
	}
	setClumpVisibilityCallback (clump, this);
	txdAddRef (txdIndex);	// CBaseModelInfo::AddTexDictionaryRef
	if (getIfpIndex () != -1)
		ifpAddRef (getIfpIndex ());
	RpClumpForAllAtomics (clump, _4C4F30, this);
	RpAtomic * a = getFirstAtomicInClump (clump);
	if (!a)
		return;
	RpSkin * skin = RpSkinGeometryGetSkin (a->geometry);
	if (!skin)
		return;
	if (flags & 0x200) {
		RpClumpForAllAtomics (clump, setAtomicHAnimHierarchyCB, NULL);
		return;
	}

	RpMorphTarget * mt = getFirstAtomicInClump (clump)->geometry->morphTarget;
	mt->boundingSphere.radius *= 1.2f;

	RpHAnimHierarchy * hier = clumpGetHAnimHierarchy (clump);
	RpClumpForAllAtomics (clump, setAtomicHAnimHierarchyCB, hier);
	a = getFirstAtomicInClump (clump); // TODO: вынести в начало конструкции 
	skin = RpSkinGeometryGetSkin (a->geometry);
	int n = a->geometry->numVertices;
	RwMatrixWeights * weights = RpSkinGetVertexBoneWeights (skin);

	for (int i = 0; i < n; i++) {
		float f = 1.0f/(weights[i].w0+weights[i].w1+weights[i].w2+weights[i].w3);
		weights[i].w0 *= f;
		weights[i].w1 *= f;
		weights[i].w2 *= f;
		weights[i].w3 *= f;
	}
	hier->flags = 0x3000;
}

// NOTE: такая структура используется во многих коллбэках.
// TODO: найти ей более пристойное место
struct FindData {
	void * param;
	void * data;
};

RwFrameCallBack _4C52F0 = (RwFrameCallBack)0x4C52F0;	// findVisibleFrameByNodeName ??
void (*setFrameVisibilityData)(RwFrame * f, DWORD d) = (void (__cdecl *)(RwFrame *, DWORD))0x732A00;

void CModelHier::initComponents(ModelComponent *components) {
	FindData f;

	while (components->name) {
		if (!(components->_f8 & 1)) {
			f.param = components->name;
			f.data = NULL;
			RwFrameForAllChildren ((RwFrame *)clump->object.parent, _4C52F0, &f);	// CClumpModelInfo::FindFrameFromNameWithoutIdCB
			if (f.data) 
				setFrameVisibilityData ((RwFrame *)f.data, components->_f4);	// CVisibilityPlugins::SetFrameHierarchyId
		}

		components++;
	}
}

void setAtomicRenderCallback (RpAtomic * atomic, RpAtomicCallBackRender data) {
	atomic->renderCallBack = data ? data : AtomicDefaultRenderCallBack;
}

RpAtomic * setAtomicRenderCallbackCB (RpAtomic * atomic, void * data ){	// TODO: используется еще в opcode handler 24
	setAtomicRenderCallback (atomic, (RpAtomicCallBackRender)data);
	return atomic;
}

void CModelWeap::setClump(RpClump *c) {
	CModelHier::setClump (c);
	RpClumpForAllAtomics (c, setAtomicRenderCallbackCB, weaponRenderCB);
	RwFrame * f = findFrameByNodeName (c, "gunflash");
	if (!f) 
		return ;
	RpAtomic * a = getFrameFirstObject (f);
	if (!a)
		return;
	atomicSetAlphaForAllMaterials (a, 0);
	a->object.object.flags = 0;
	RpMaterial * m = *(a->geometry->matList.materials);
	m->surfaceProps.ambient = 16.0f;
}

CModelPeds::~CModelPeds () {
	if (_f34)
		delete _f34;
}

void CModelPeds::clearData() {
	CModelHier::clearData ();

	if (_f34) {
		delete _f34;
		_f34 = NULL;	
	}
}

ModelComponent * pedComponents = (ModelComponent *)0x8A6268;
void CModelPeds::setClump (RpClump * c) {
	CModelHier::setClump (c);
	initComponents (pedComponents);
	if (!_f34) 
		initPedColModel (c);
	RpClumpForAllAtomics (clump, setAtomicRenderCallbackCB, pedRenderCB);
	clumpGetHAnimHierarchy (clump);	// TODO: а это еще зачем?
}

RpHAnimHierarchy * (*clumpGetFirstSkinAtomicHAnimHierarchy)(RpClump *) = (RpHAnimHierarchy *(__cdecl *)(RpClump *))0x734A40;
RwMatrix * (*RwMatrixInvert)(RwMatrix * out, RwMatrix * in) = (RwMatrix *(__cdecl *)(RwMatrix *, RwMatrix *))0x7F2070;
RwMatrix * (*RwMatrixTransform)(RwMatrix * matrix, RwMatrix * transform, int combineOp) = (RwMatrix *(__cdecl *)(RwMatrix *, RwMatrix *, int))0x7F25A0;
D3DVECTOR * (*RwTransformPoints)(D3DVECTOR * pointsOut, D3DVECTOR * pointIn, int numPoints, RwMatrix * transformMatrix) =
(D3DVECTOR * (__cdecl *)(D3DVECTOR *, D3DVECTOR *, int, RwMatrix *))0x7EDD90;
RwMatrix * (*RwMatrixRotate)(RwMatrix * m, D3DVECTOR * axis, float angle, int combineOp) = 
(RwMatrix * (__cdecl *)(RwMatrix *, D3DVECTOR *, float, int))0x7F1FD0; 

RwMatrix ** tmpMatrix = (RwMatrix **)0xB72920;
RwMatrix ** collisionTransformMatrix = (RwMatrix **)0xB72924;
ColInitData * pedColData = (ColInitData *)0x8A6308;
int  (*RpHAnimIDGetIndex)(RpHAnimHierarchy *hierarchy, int ID) = (int (__cdecl *)(RpHAnimHierarchy *, int))0x7C51A0;

// TODO: большая часть этой функции используется еще в двух функциях.
//	возможно, стоит ее вынести
void CModelPeds::initPedColModel (RpClump * c) {
	RpHAnimHierarchy * hier = clumpGetFirstSkinAtomicHAnimHierarchy (c);
	CColModel * cm = new CColModel ();
	cm->initModelData (12, 0, 0, 0, 0, 0);
	
	RwMatrixInvert (*tmpMatrix, &((RwFrame *)c->object.parent)->modelling);
	ColSphere * cs = (ColSphere *)(((ColDataHeader *)cm->_f2C)->spheres);
	ColInitData * data = pedColData;
	D3DVECTOR	point;
	RwMatrix * transformMatrix = *collisionTransformMatrix;
	
	for (int i = 0; i < 12; i++) {
		point = data[i].center;
		*transformMatrix = **tmpMatrix;		
		RwMatrix * animMatrix = hier->pMatrixArray+RpHAnimIDGetIndex (hier, data[i].animID);
		RwMatrixTransform (transformMatrix, animMatrix, 1);	// rwCOMBINEPRECONCAT
		RwTransformPoints (&point, &point, 1, transformMatrix);
		cs[i].center = point;
		cs[i].radius = data[i].radius;
		cs[i].surface.material = 0x3E;
		cs[i].surface.flags = (BYTE)data[i].flags;
	}
	
	// по выходу из цикла:
	cm->radius = 1.5f;
	cm->center.x = cm->center.y = cm->center.z = 0.0f;
	cm->inf.x = cm->inf.y = -0.5f;
	cm->inf.z = -1.2f;
	cm->sup.x = cm->sup.y = 0.5f;
	cm->sup.z = 1.2f;
	cm->_f28 = 0;
	_f34 = cm;
}

RpAtomic * CModelObjs::cloneData() {
	if (!atomic)
		return NULL;
	addRef ();
	RpAtomic * a = RpAtomicClone (atomic);
	RwFrame * frame = RwFrameCreate ();
	RpAtomicSetFrame (a, frame);
	release ();
	return a;
}

void CModelObjs::_m3C (RpAtomic * a) {
	DWORD * pd2fx;
	DWORD d2fx;
	if (atomic) {		
		pd2fx = *(DWORD **)(((BYTE *)atomic->geometry)+(*pl253F2F8_TK));
		d2fx = pd2fx ? *pd2fx : 0;
		_f0D -= (BYTE)d2fx;
	}
	atomic = a;
	pd2fx = *(DWORD **)(((BYTE *)atomic->geometry)+(*pl253F2F8_TK));
	d2fx = pd2fx ? *pd2fx : 0;
	_f0D += (BYTE)d2fx;
	
	txdAddRef (txdIndex);
	if (getIfpIndex() != -1) 
		ifpAddRef (getIfpIndex ());

	if (_5D7F40 (a)) 
		_5D7F00 (a);
	else if (_5D5B80 (a)) 
		_5D5B60 (a);
	
	if ((!(flags & 0x8000)) && (flags & 0x7800) == 0x3000)
		_49CE10 (a);
	flags |= 1;
}

void CModelObjs2c::init() {
	CModelObjs::init ();
	atomic2 = NULL;
}

void CModelObjs2c::clearData () {
	if (atomic2) {
		RwFrame * frame = (RwFrame *)atomic2->object.object.parent;
		RpAtomicDestroy (atomic2);
		RwFrameDestroy (frame);
		atomic2 = NULL;
	}
	CModelObjs::clearData ();
}

RpAtomic * CModelObjs2c::cloneDataAndSetMM(RwMatrix * mm) {
	if (_A9B0B0) {
		if (!atomic2)
			return NULL;	
		RpAtomic * a = RpAtomicClone (atomic2);
		RwFrame * frame = RwFrameCreate ();
		frame->modelling = *mm;
		RpAtomicSetFrame (a, frame);
		return a;
	}
	return CModelObjs::cloneDataAndSetMM (mm);
}

RpAtomic * CModelObjs2c::cloneData() {
	if (_A9B0B0) {
		if (!atomic2)
			return NULL;
		RpAtomic * a = RpAtomicClone (atomic2);
		RwFrame * frame = RwFrameCreate ();
		RpAtomicSetFrame (a, frame);
		return a;
	}
	return CModelObjs::cloneData ();
}

void CModelHier::initIfpName (char * name) {
	if (stricmp (name, "null")) {
		int l = strlen(name)+1;
		ifpName = new char [l];
		strncpy (ifpName, name, l);
	}	
}

void CModelHier::initIfp() {	
	if (ifpIndex != -1) {
		int idx = getIfpIndexByName (ifpName);
		delete ifpName;
		ifpIndex = idx;
	}
}

CVehicleStruct::CVehicleStruct () {
	for (int i = 0; i < 15; i++) 	// TODO: D3DVECTORS ? 
		_f00[i].x = _f00[i].y = _f00[i].z = 0.0f;
	for (int i = 0; i < 18; i++) 
		_fB4[i]._f1C = -1;
	for (int i = 0; i < 6; i++)
		atomics[i] = NULL;
	atomicCount = 0;
	_f310 = 0;
}

CVehicleStruct::~CVehicleStruct () {
	for (int i = 0; i < atomicCount; i++) {		
		RwFrame * f = (RwFrame *)(atomics[i]->object.object.parent);
		RpAtomicDestroy (atomics[i]);
		RwFrameDestroy (f);
	}
}

void * CVehicleStruct::operator new (size_t) {
	return (*vehicleStructPool)->allocate ();
}

void CVehicleStruct::operator delete (void * ptr) {
	(*vehicleStructPool)->release ((BYTE *)ptr);
}

void CModelCars::initIfpName (char * name) {
	if (strcmp (name, "null")) {
		int l = strlen(name)+1;
		carIfpName = new char [l];
		strncpy (carIfpName, name, l);
	}
}

void CModelCars::initIfp() {
	if (carIfpIndex != -1) {
		int idx = getIfpIndexByName (carIfpName);
		delete carIfpName;
		carIfpIndex = idx;
	}
}

CModelCars::CModelCars() : CModelHier () {
	vehicleStruct = NULL;
	_f2D0 = 0;
	vflags = 0;
	carIfpIndex = -1;
	for (int i = 0; i < 9; i++)
		_f2D6[i] = -1;
	_f2FA = _f2FE = -1;
}

void CModelCars::init() {
	CModelHier::init ();
	type = -1;
	_p1 = -1;
	_f58 = 9.9998999e2f;
}

void CModelCars::clearData() {
	if (vehicleStruct) {
		delete vehicleStruct;
		vehicleStruct = NULL;
	}
	CModelHier::clearData ();
}

RwFrame * (*_4C53C0)(RpClump *) = (RwFrame * (__cdecl *)(RpClump *))0x4C53C0;

static RpAtomic * (*_4C9410)(RpAtomic * atomic, void * data) = (RpAtomic *(__cdecl *)(RpAtomic *, void *))0x4C9410;

char * carCurrentVariation1 = (char *)0xB4E478;
char * carCurrentVariation2 = (char *)0xB4E479;
short * upcomingWeather = (short *)0xC8131C;
short * currentWeather = (short *)0xC81320;

RpAtomic * CModelCars::cloneData() {
	RpAtomic * a = CModelHier::cloneData ();
	RpClump * c = (RpClump *)a;
	if (vehicleStruct->atomicCount > 0) {
		RwFrame * f;
		if (type == 9 || type == 10) {	// BMX or bike
			f = _4C53C0 (c);
			if (!f)
				f = (RwFrame *)c->object.parent;
		} else 
			f = (RwFrame *)c->object.parent;
	
		int n = getCarVariation1 ();
		if (n != -1 && vehicleStruct->atomics[n]) {
			RpAtomic * va = RpAtomicClone (vehicleStruct->atomics[n]);
			RwFrame * vf = RwFrameCreate ();
			RwFrameTransform (vf, &(((RwFrame *)vehicleStruct->atomics[n]->object.object.parent)->modelling), 0);
			RpAtomicSetFrame (va, vf);
			RpClumpAddAtomic (c, va);
			RwFrameAddChild (f, vf);
		}
		*carCurrentVariation1 = n;
		n = getCarVariation2 ();
		if (n != -1 && vehicleStruct->atomics[n]) {
			RpAtomic * va = RpAtomicClone (vehicleStruct->atomics[n]);
			RwFrame * vf = RwFrameCreate ();
			RwFrameTransform (vf, &(((RwFrame *)vehicleStruct->atomics[n]->object.object.parent)->modelling), 0);
			RpAtomicSetFrame (va, vf);
			RpClumpAddAtomic (c, va);
			RwFrameAddChild (f, vf);
		}
		*carCurrentVariation2 = n;
	} else 
		*carCurrentVariation1 = *carCurrentVariation2 = -1;
	RpClumpForAllAtomics (c, _4C9410, NULL);
	return a;
}

static int (*_4C7FB0)(int, int) = (int (__cdecl *)(int, int))0x4C7FB0;	// что-то связанное с вычислением модификации машины для погоды
static int (*_4C7E50)(int comprules, int atomicCount, int *) = (int (__cdecl *)(int, int, int *))0x4C7E50; 

static bool checkWeatherForCarGeneration (int flag) {
	return flag != 2 || *currentWeather == 0x10 || *upcomingWeather == 0x10 || *currentWeather == 8 || *upcomingWeather == 8;
}

int CModelCars::getCarVariation1() {
	int v = *carCurrentVariation1;
	if (v != -2) {
		*carCurrentVariation1 = -2;
		return v;
	}

	int c = (comprules >> 12)&15;
	if (c != 0 && checkWeatherForCarGeneration (c)) 
		return _4C7FB0 (comprules&0xFFF, c);
	
	if (random (0, 3) < 2) {
		int _v18[6];
		c = _4C7E50 (comprules, vehicleStruct->atomicCount, _v18);
		if (c)
			return _v18[random(0, c)];
	}
	return -1;
}
int CModelCars::getCarVariation2() {
	int v = *carCurrentVariation2;
	if (v != -2) {
		*carCurrentVariation2 = -2;
		return v;
	}

	int c = (comprules >> 28)&15;
	if (c != 0 && checkWeatherForCarGeneration (c)) 
		return _4C7FB0 (c, (comprules >> 16)&0xFFF);
	
	if (checkWeatherForCarGeneration ((c >> 12)&0xFFF) && random (0,3) < 2) {	// TODO: 12 - это правильно?
		int _v18[6];
		c = _4C7E50 (comprules, vehicleStruct->atomicCount, _v18);
		if (c)
			return _v18[random(0, c)];
	}
	return -1;
}

ModelComponent ** carComponentsByType = (ModelComponent **)0x8A7740;
void (*prepareLiceseplate)() = (void (__cdecl *)())0x4C9450;

void CModelCars::setClump(RpClump *clump) {
	vehicleStruct = new CVehicleStruct ();
	CModelHier::setClump(clump);
	setVehicleRenderCallbacks ();
	initComponents (carComponentsByType[type]);

	_4C8900 ();
	preprocessHierarchy ();
	_4C8BD0 ();

	_f2D2 = _f2D3 = _f2D4 = _f2D5 = 0xFF;
	prepareLiceseplate ();
}


RpAtomic * (*_5DA610)(RpAtomic *) = (RpAtomic * (__cdecl *)(RpAtomic *))0x5DA610;
// что-то связанное с назначением материалов
static RpAtomic * _5D5B30 (RpAtomic * a, void * data) {
	return _5DA610 (a);
}

RwFrameCallBack _4C5350 = (RwFrameCallBack)0x4C5350;

RwFrame ** carFrame = (RwFrame **)0xB4E6B8;
void CModelCars::_4C8900 () {
	RpClumpForAllAtomics (clump, _5D5B30, NULL);	// NOTE: inlined _5D5B40

	if (!*carFrame) {
		RwFrame * f = *carFrame = RwFrameCreate ();
		
		D3DVECTOR axis;
		axis.x = 1.0f;
		axis.y = axis.z = 0.0f;
		RwMatrixRotate (&f->modelling, &axis, 60.0f, 0);
		RwFrameUpdateObject (f);
		RwFrameGetLTM (f);	// sync hierarch LTMs
	}
}
CHandling * handling = (CHandling *)0xC2B9C8;
int (*_732A20)(RwFrame *) = (int (__cdecl *)(RwFrame *))0x732A20;
static RwFrame * getFirstChldCB(RwFrame * f, void * d) {
	*(RwFrame **)d = f;
	return NULL;
}

static RwFrame * (*_4C8E30) (RwFrame * f, void * d) = (RwFrame *(__cdecl *)(RwFrame *, void *))0x4C8E30;	// set frame for all objects in hierarchy
static RwFrame * (*_4C7BD0) (RwFrame * f, void * d) = (RwFrame *(__cdecl *)(RwFrame *, void *))0x4C7BD0;

void CModelCars::preprocessHierarchy () {
	ModelComponent * comp = carComponentsByType[type];
	_f4C = 0;

	FindData	data;
	RpAtomic *	ca = NULL;
	RpAtomic *  _v40 = NULL;
	RwFrame * cf;

	while (comp->name) {	// for each component TODO: change to for
		if (comp->_f8 & 0x20208) {
			data.data = NULL;
			data.param = comp->name;
			RwFrameForAllChildren ((RwFrame *)clump->object.parent, _4C52F0, &data);	// CClumpModelInfo::FindFrameFromNameWithoutIdCB
			if (data.data) {
				cf = (RwFrame *)data.data;
				if (comp->_f8 & 8) {	
					D3DVECTOR * point = vehicleStruct->_f00+comp->_f4;
					*point = cf->modelling.pos;
					RwFrame * pcf = (RwFrame *)cf->object.parent;					
					while (pcf && pcf->object.parent) {
						RwTransformPoints (point, point, 1, &pcf->modelling);
						pcf = (RwFrame *)pcf->object.parent;
					}					
					RwFrameDestroy (cf);
				} else if (comp->_f8 & 0x20000) {	// _4C8F7B	

					vehicleStruct->_fB4[comp->_f4]._f0 = cf->modelling.pos;
					vehicleStruct->_fB4[comp->_f4].q.get (&cf->modelling);
					vehicleStruct->_fB4[comp->_f4]._f1C = _732A20 ((RwFrame *)cf->object.parent);			// CVisibilityPlugins::GetFrameHierarchyId
				} else {	// _4C8FF6
					RpAtomic * a = getFrameFirstObject (cf);
					RpClumpRemoveAtomic (clump, a);
					RwFrameRemoveChild (cf);
					setVehicleComponentFlags  (cf, comp->_f8);
					vehicleStruct->atomics[vehicleStruct->atomicCount++] = a;
				}
			}
		}
		// _4C9042

		if (comp->_f8 & 0x110000) {	// clonable component
			data.data = NULL;
			data.param = (void *)comp->_f4;
			RwFrameForAllChildren ((RwFrame *)clump->object.parent, _4C5350, &data);	// CClumpModelInfo::FindFrameFromIdCB
			cf = (RwFrame *)data.data;			
			while (cf) {	// TODO: convert to for
				void * o = getFrameFirstObject (cf);
				if (o) {
					if (comp->_f8 & 0x10000)	// wheel ?
						ca = getFrameFirstObject (cf);
					else
						_v40 = getFrameFirstObject (cf);
					break;
				}				
				RwFrame * f = NULL;
				RwFrameForAllChildren (cf, getFirstChldCB, &f);
				cf = f;
			}
		}
		// _4C90C7
		comp++;
	}
	
	for (comp = carComponentsByType[type]; comp->name; comp++) {	// TODOO: change to for
		if (comp->_f8 & 0x20208) 
			continue;

		data.data = NULL;
		data.param = (void *)comp->_f4;
		RwFrameForAllChildren ((RwFrame *)clump->object.parent, _4C5350, &data);	// CClumpModelInfo::FindFrameFromIdCB
		if (!data.data)
			continue;

		cf = (RwFrame *)data.data;	// current component frame
		if (comp->_f8 & 0x10)
			_f4C++;

		if (comp->_f8 & 2) {
			RwFrameForAllChildren (cf, _4C8E30, cf);
			RwFrameUpdateObject (cf);
			data.data = data.param = NULL;
			RwFrameForAllObjects (cf, _4C7BD0, &data);
			if (data.param && data.data) {
				((RpAtomic *)data.data)->renderCallBack = ((RpAtomic *)data.param)->renderCallBack;
				if (!((RpAtomic *)data.data)->renderCallBack)
					((RpAtomic *)data.data)->renderCallBack = AtomicDefaultRenderCallBack;
				vehicleStruct->_f310 |= 1 << comp->_f4;
			}
		}
		setVehicleComponentFlags  (cf, comp->_f8);
		if (comp->_f8 & 0x10004) {
			if (!ca)
				continue;
			if (!(comp->_f8 & 0x10000)) {
				RpAtomic * a = RpAtomicClone (ca);
				RpAtomicSetFrame (a, cf);
				RpClumpAddAtomic (clump, a);
				setAtomicRenderCallback (a, AtomicDefaultRenderCallBack);
				if (comp->_f4 == 2 || comp->_f4 == 5 || !(handling->vehicleHandling[handlingIndex].modelFlags & 0x20000000))
					continue;

				a = RpAtomicClone (ca);
				RwFrame * f = RwFrameCreate ();
				RpAtomicSetFrame (a, f);
				RwFrameAddChild (cf, f);

				RwMatrix * m = &f->modelling;					
				m->flags |= 0x20003;
				m->right.x = m->top.y = m->at.z = 1.0f;
				m->pos.x = -0.25f*1.15f;
				m->right.y = m->right.z = m->top.x = m->top.z = m->at.x = m->at.y = m->pos.y = m->pos.z = 0.0f;
				RpClumpAddAtomic (clump, a);
				setAtomicRenderCallback (a, AtomicDefaultRenderCallBack);
			} else {
				RwFrameForAllChildren (cf, _4C8E30, cf);
				RwFrameUpdateObject (cf);
				setAtomicRenderCallback (ca, AtomicDefaultRenderCallBack); // _7323C0
			}
		} else if ((comp->_f8 & 0x200000) && _v40) {	// 4C92F9
			RpAtomic * a = RpAtomicClone (_v40);
			RpAtomicSetFrame (a, cf);
			RpClumpAddAtomic (clump, a);
			setAtomicRenderCallback (a, AtomicDefaultRenderCallBack);	// _7323C0
		}		
	}
}


void (*_72F480)() = (void (__cdecl *)())0x72F480;	// TODO: initialization of unused structure
static RpAtomic * (*_4C8B60) (RpAtomic * a, void * d) = (RpAtomic *(__cdecl *)(RpAtomic *, void *))0x4C8B60;	// make material list
static RpAtomic * (*clearAtomicVisibilityFlagsCB)(RpAtomic * a, void * data) = (RpAtomic * (__cdecl *)(RpAtomic *, void *))0x732330;	

void (*_rpMaterialListDeinitialize) (RpMaterialList *) = (void (__cdecl *)(RpMaterialList *))0x74E150;

void CModelCars::_4C8BD0 () {
	_72F480 ();	// TODO: remove it?

	RpMaterialList	mlist;
	mlist.materials = (RpMaterial **)RwMalloc (0x50);
	mlist.numMaterials = 0;
	mlist.space = 0x14;
	RpClumpForAllAtomics (clump, _4C8B60, &mlist);	// CVehicleModelInfo::GetEditableMaterialListCB
	for (int i = 0; i < vehicleStruct->atomicCount; i++) 
		_4C8B60 (vehicleStruct->atomics[i], &mlist);	// add materials from the vehicle struct atomics
	_rpMaterialListDeinitialize (&mlist);
	
	// NOTE: похоже, что все, выше этого места, используется только для отладки
	RpClumpForAllAtomics (clump, clearAtomicVisibilityFlagsCB, (void *)0x2000);	// NOTE: inlined
}

RwFrameCallBack setAtomicVisibilityFlagsCB = (RwFrameCallBack)0x4C7B90;
void CModelCars::setVehicleComponentFlags  (RwFrame * f, DWORD flags) {
	DWORD mf = handling->vehicleHandling[handlingIndex].modelFlags;

	if (flags & 0x1000) 
		RwFrameForAllObjects (f, setAtomicVisibilityFlagsCB, (void *)0x80);
	if (flags & 0x400000) 
		RwFrameForAllObjects (f, setAtomicVisibilityFlagsCB, (void *)0x400);
	if (flags & 0x40000) 
		RwFrameForAllObjects (f, setAtomicVisibilityFlagsCB, (void *)0x2000);

	DWORD fl = 0;
	if (flags & 0x80)
		fl = 0x10;
	else if ((flags & 0x100) && ((mf & 1) || !(flags & 0x60)))
		fl = 0x20;
	else if (flags & 0x20)
		fl = 4;
	else if (flags & 0x40)
		fl = 8;
	if (fl) 
		RwFrameForAllObjects (f, setAtomicVisibilityFlagsCB, (void *)fl);

	if ((flags & 0x8000) && ((mf & 0x80000000) || (flags & 0x6000)))		
		RwFrameForAllObjects (f, setAtomicVisibilityFlagsCB, (void *)0x8000);

	if (flags & 0x2000)
		RwFrameForAllObjects (f, setAtomicVisibilityFlagsCB, (void *)0x100);
	else if (flags & 0x4000)
		RwFrameForAllObjects (f, setAtomicVisibilityFlagsCB, (void *)0x200);
	
	if (flags & 0x400) 
		RwFrameForAllObjects (f, setAtomicVisibilityFlagsCB, (void *)0x40);
}


typedef RpAtomic   *(*RpAtomicCallBack) (RpAtomic * atomic, void *data);

static RpAtomic * (*SetAtomicRendererCB_Train)	(RpAtomic * a, void * d) = (RpAtomicCallBack)0x4C7AA0;
static RpAtomic * (*SetAtomicRendererCB_Boat)	(RpAtomic * a, void * d) = (RpAtomicCallBack)0x4C79A0;
static RpAtomic * (*SetAtomicRendererCB_Heli)	(RpAtomic * a, void * d) = (RpAtomicCallBack)0x4C7870;
static RpAtomic * (*SetAtomicRendererCB_Plane)	(RpAtomic * a, void * d) = (RpAtomicCallBack)0x4C7930;
static RpAtomic * (*SetAtomicRendererCB)		(RpAtomic * a, void * d) = (RpAtomicCallBack)0x4C77E0;

void CModelCars::setVehicleRenderCallbacks () {
	RpAtomicCallBack cb;
	switch (type) {
	case 3:
		cb = SetAtomicRendererCB_Heli;
		break;
	case 4:
	case 8:
		cb = SetAtomicRendererCB_Plane;
		break;
	case 5:
		cb = SetAtomicRendererCB_Boat;
		break;
	case 6:	// Train
		cb = SetAtomicRendererCB_Train;
		break;
	default:
		cb = SetAtomicRendererCB;
	}
	RpClumpForAllAtomics (clump, cb, clump);
}

CModelBase ** modelPtrs = (CModelBase **)0xA9B0C8;

CModelBase * ModelObjsData::allocate(int objectID) {
	objects[count].init ();
	modelPtrs[objectID] = objects+count;
	count++;
	return modelPtrs[objectID];
}

CModelBase * ModelObjs2cData::allocate(int objectID) {
	objects[count].init ();
	modelPtrs[objectID] = objects+count;
	count++;
	return modelPtrs[objectID];
}

CModelBase * ModelTobjData::allocate(int objectID) {
	objects[count].init ();
	modelPtrs[objectID] = objects+count;
	count++;
	return modelPtrs[objectID];
}

CModelBase * ModelHierData::allocate(int objectID) {
	objects[count].init ();
	modelPtrs[objectID] = objects+count;
	count++;
	return modelPtrs[objectID];
}

CModelBase * ModelWeapData::allocate(int objectID) {
	objects[count].init ();
	modelPtrs[objectID] = objects+count;
	count++;
	return modelPtrs[objectID];
}

CModelBase * ModelPedsData::allocate(int objectID) {
	objects[count].init ();
	modelPtrs[objectID] = objects+count;
	count++;
	return modelPtrs[objectID];
}
CModelBase * ModelCarsData::allocate(int objectID) {
	objects[count].init ();
	modelPtrs[objectID] = objects+count;
	count++;
	return modelPtrs[objectID];
}


int * lastModelFound = (int *)0xAAE948;	// TODO: используется еще в readCutsFile

CModelBase * getModelIndexByName (char * name, DWORD * pIndex) {
	DWORD hash = crc32FromUpcaseString ((BYTE *)name);
	int i;
	for (i = *lastModelFound; i < 20000; i++) 
		if (modelPtrs[i] && (modelPtrs[i]->modelNameHash == hash))
			break;
	if (i >= 20000) 
		for (i = *lastModelFound; i >= 0; i--)
			if (modelPtrs[i] && (modelPtrs[i]->modelNameHash == hash))
				break;

	if ((i >= 0) && (i < 20000)) {
		if (pIndex)
			*pIndex = i;
		*lastModelFound = i;
		return modelPtrs[i];
	}
	// debug ("getModelIndexByName: ERROR: model '%s' not found (%d, %08x)\n", name, hash);
	return NULL;
}

ModelObjsData	objs;
ModelObjs2cData	objs2c;
ModelTobjData	tobj;
ModelHierData	hier;
ModelWeapData	weap;
ModelPedsData	peds;
ModelCarsData	cars;
// /ogModels.cpp

CColModel * colModelWeap = (CColModel *)0x968FD0; // TODO: CColModel
CColModel * colModelHier = (CColModel *)0x968A00; // TODO: CColModel
CColModel * colModelPeds = (CColModel *)0x968DF0; // TODO: CColModel


static char lineBuffer[512];
char * __cdecl getConfigLine (FILE * f) {	// >> 536F80 (74)	
	if (!fgets (lineBuffer, 512, f)) 
		return NULL;
	char * s;
	for (s = lineBuffer; *s; s++)
		if (*s < ' ' || *s == ',')
			*s = ' ';
	for (s = lineBuffer; *s; s++)
		if (*s > ' ')
			break;
	return s;	
}


static RpAtomic * (*_5B3930)(RpAtomic * clump, void * param) = (RpAtomic * (__cdecl *)(RpAtomic *, void *))0x5B3930;

static int (__stdcall *findVehicleIndex2)(char * name) = (int (__stdcall *)(char *))0x6F4FD0;	// TODO: метод CHandlingData. 
// NOTE: поскольку эта функция используется только в одном месте - можно написать ее здесь как отдельную функцию или заинлайнить
int (*getPedTypeByName)(char * name) = (int (__cdecl *)(char *))0x608790;
static int (*getPedStatTypeByName)(char * name) = (int (__cdecl *)(char *))0x608790;
char * (*getAnimationStyleName)(int index) = (char * (__cdecl *)(int))0x4D3A20;
int * animationStylesCount = (int *)0xB4EA28;
static int (*getPedModelByName)(char * name) = (int (__cdecl *)(char *))0x5B6D40;
static int (*getPedVoiceTypeByName)(char * name) = (int (__cdecl *)(char *))0x4E3C60;
static int (*getPedVoiceByName)(char * name, short voiceType) = (int (__cdecl *)(char *, short))0x4E3CD0;


static void loadModelFile(char * filename) {
	RwStream * stream = RwStreamOpen (rwSTREAMFILENAME, rwSTREAMREAD, filename);
	if (!stream) 
		debug ("Unable to open model file '%s'\n", filename);
	if (RwStreamFindChunk (stream, 0x10, NULL, NULL)) {
		void *clump = RpClumpStreamRead (stream);
		if (clump) {
			RpClumpForAllAtomics (clump, _5B3930, clump);
			RpClumpDestroy (clump);
		}
	}
	RwStreamClose (stream, 0);			
}

///static int (*readIdeObjs)(char *) = (int (__cdecl *)(char *))0x5B3C60;
///static int (*readIdeTobj)(char *) = (int (__cdecl *)(char *))0x5B3DE0;
///static int (*readIdeWeap)(char *) = (int (__cdecl *)(char *))0x5B3FB0;
///static int (*readIdeHier)(char *) = (int (__cdecl *)(char *))0x5B4040;
///static int (*readIdeAnim)(char *) = (int (__cdecl *)(char *))0x5B40C0;
///static int (*readIdeCars)(char *) = (int (__cdecl *)(char *))0x5B6F30;
///static int (*readIdePeds)(char *) = (int (__cdecl *)(char *))0x5B7420;
///static void (*readIdeTxdp)(char *) = (int (__cdecl *)(char *))0x5B75E0;
///static CModelBase * (*allocObjsModel) (int objID) = (CModelBase * (__cdecl *)(int))0x4C6620;
///static CModelBase * (*allocObjsModel2Clump) (int objID) = (CModelBase * (__cdecl *)(int))0x4C6650;
///static CModelBase * (*allocTobjModel) (int objId) = (CModelBase * (__cdecl *)(int))0x4C66B0;
///static CModelBase * (*allocWeapModel) (int objId) = (CModelBase * (__cdecl *)(int))0x4C6710;
///static CModelBase * (*allocHierModel) (int objId) = (CModelBase * (__cdecl *)(int))0x4C6740;
///static CModelBase * (*allocPedsModel) (int objId) = (CModelBase * (__cdecl *)(int))0x4C67A0;
///static CModelBase * (*allocCarsModel) (int objId) = (CModelBase * (__cdecl *)(int))0x4C6770;


static CModelBase * allocObjsModel (int objId) {	// TODO: inline it
	return objs.allocate (objId);
}

static CModelBase * allocObjsModel2Clump (int objId) {
	return objs2c.allocate (objId);
}

static CModelBase * allocTobjModel (int objId) {
	return tobj.allocate (objId);
}

static CModelBase * allocWeapModel (int objId) {
	return weap.allocate (objId);
}

static CModelBase * allocHierModel (int objId) {
	return hier.allocate (objId);
}

static CModelBase * allocPedsModel (int objId) {
	return peds.allocate (objId);
}
static CModelBase * allocCarsModel (int objId) {
	return cars.allocate (objId);
}


static void setModelGraphicsFlags (CModelBase * model, DWORD flags) {
	if (flags & 0x0C)	// mfALPHATRANSPARENCY1|mfALPHATRANSPARENCY2
		model->flags |= 2;
	else
		model->flags &= ~2;

	if (flags & 0x08)	// mfALPHATRANSPARENCY2
		model->flags |= 4;
	else
		model->flags &= ~4;

	if (flags & 0x40)	// mfDISABLESHADOWMESH
		model->flags |= 8;
	else
		model->flags &= ~8;

	if (flags & 0x80)	// mfDISABLECOLLISIONMESH
		model->flags |= 0x10;
	else 
		model->flags &= ~0x10;

	if (flags & 0x200000)	// mfNOBACKFACECULLING
		model->flags &= ~0x40;
	else
		model->flags |= 0x40;
}

static void (*setModelFlags) (CModelBase * model, DWORD flags) = (void (__cdecl *)(CModelBase *, DWORD))0x5B3B20;

static int readIdeObjs (char * line) {
	int	objID;	// unique object ID (integer) 
	int objCount = 1;
	float drawDist;
	float drawDist2;
	float drawDist3;
	DWORD flags;
	char modelName[24];
	char texName[24];

	int fieldCount = sscanf (line, "%d %s %s %f %d", &objID, modelName, texName, &drawDist, &flags);	// try parse line without object count
	if (fieldCount != 5 || drawDist < 4.0f) {
		if (sscanf (line, "%d %s %s %d", &objID, modelName, texName, &objCount) != 4)
			return -1;	// invalid line format
		if (objCount == 1) 
			sscanf (line, "%d %s %s %d %f %d", &objID, modelName, texName, &objCount, &drawDist, &flags);
		else if (objCount == 2)
			sscanf (line, "%d %s %s %d %f %f %d", &objID, modelName, texName, &objCount, &drawDist, &drawDist2, &flags);
		else if (objCount == 3)
			sscanf (line, "%d %s %s %d %f %f %f %d", &objID, modelName, texName, &objCount, &drawDist, &drawDist2, &drawDist3, &flags);
	}

	///debug ("OBJS: %d %s %s %d %f %08x\n", objID, modelName, texName, objCount, drawDist, flags);
	CModelBase * model = flags & 0x1000 ? allocObjsModel2Clump (objID) : allocObjsModel (objID);
	model->drawDistance = drawDist;
	model->modelNameHash = crc32FromUpcaseString ((BYTE *)modelName);
	model->setTxd (texName);
	setModelFlags (model, flags);
	return objID;
}

static int readIdeTobj (char * line) {
	int	objID;	// unique object ID (integer) 
	int objCount = 1;
	float drawDist;
	float drawDist2;
	float drawDist3;
	DWORD flags;
	char modelName[24];
	char texName[24];
	int timeOn;
	int timeOff;

	int fieldCount = sscanf (line, "%d %s %s %f %d %d %d", &objID, modelName, texName, &drawDist, &flags, &timeOn, &timeOff);	// try parse line without object count
	if (fieldCount != 7 || drawDist < 4.0f) {
		if (sscanf (line, "%d %s %s %d", &objID, modelName, texName, &objCount) != 4)
			return -1;	// invalid line format
		if (objCount == 1) 
			sscanf (line, "%d %s %s %d %f %d %d %d", &objID, modelName, texName, &objCount, &drawDist, &flags, &timeOn, &timeOff);
		else if (objCount == 2)
			sscanf (line, "%d %s %s %d %f %f %d %d %d", &objID, modelName, texName, &objCount, &drawDist, &drawDist2, &flags, &timeOn, &timeOff);
		else if (objCount == 3)
			sscanf (line, "%d %s %s %d %f %f %f %d %d %d", &objID, modelName, texName, &objCount, &drawDist, &drawDist2, &drawDist3, &flags, &timeOn, &timeOff);
	}
	///debug ("TOBJ: %d %s %s %d %f %08x %d-%d\n", objID, modelName, texName, objCount, drawDist, flags, timeOn, timeOff);
	CModelBase * model = allocTobjModel (objID);
	CModelTimeinfo * timeinfo = model->getTimeinfo();
	model->drawDistance = drawDist;
	model->modelNameHash = crc32FromUpcaseString ((BYTE *)modelName);
	model->setTxd (texName);
	timeinfo->timeOn = (BYTE)timeOn;
	timeinfo->timeOff = (BYTE)timeOff;
	setModelFlags (model, flags);
	CModelTimeinfo * pairedTimeinfo = timeinfo->initPairedModel (modelName);	// TODO: inline it ?
	if (pairedTimeinfo)	
		pairedTimeinfo->pairedModel = objID;
	return objID;	
}


static int readIdeWeap (char * line) {
	int objID;
	char modelName[24];
	char texName[24];
	char animName[16];
	int p1;
	float drawDist;

	sscanf (line, "%d %s %s %s %d %f", &objID, modelName, texName, animName, &p1, &drawDist);
	///debug ("WEAP: %d %s %s %s %f\n", objID, modelName, texName, animName, drawDist);
	CModelBase * model = allocWeapModel (objID);
	model->drawDistance = drawDist;
	model->modelNameHash = crc32FromUpcaseString ((BYTE *)modelName);
	model->setTxd (texName);
	model->initIfpName (animName);	
	model->setColModel (colModelWeap, false);
	return objID;
}

static int readIdeHier (char * line) {
	int objID;
	char modelName[24];
	char texName[24];

	if (sscanf (line, "%d %s %s", &objID, modelName, texName) != 3)
		return -1;

	///debug ("HIER: %d %s %s\n", objID, modelName, texName);
	CModelBase * model = allocHierModel (objID);
	model->modelNameHash = crc32FromUpcaseString ((BYTE *)modelName);
	model->setTxd (texName);
	model->setColModel (colModelHier, false);
	return objID;
}

static int readIdeAnim (char * line) {
	int objID;
	char modelName[24];
	char texName[24];
	char animName[16];
	float drawDist;
	DWORD flags;

	if (sscanf (line, "%d %s %s %s %f %d", &objID, modelName, texName, animName, &drawDist, &flags) != 6)
		return -1;

	/// debug ("ANIM: %d %s %s %s %f %08x\n", objID, modelName, texName, animName, drawDist, flags);
	CModelBase * model = allocHierModel (objID);
	model->drawDistance = drawDist;
	model->modelNameHash = crc32FromUpcaseString ((BYTE *)modelName);
	model->setTxd (texName);
	model->initIfpName (animName);	

	setModelGraphicsFlags (model, flags);
	if (flags & 0x20) // mfINTERIOROBJECT
		model->flags |= 0x400;
	else
		model->flags &= ~0x400;

	if (strcmp (animName, "null"))
		model->flags |= 0x100;

	return objID;
}


static int readIdeCars (char * line) {

	int		objID;
	char	modelName[24];
	char	texName[24];
	char	type[8];
	char	handlingID[16];
	char	gameName[32];
	char	anims[16];
	char	vclass[16];
	int		frq;
	int		vflags;
	int		comprules;
	int		_p1;
	float	_p2;
	float	_p3;
	int		_p4;

	sscanf (line, "%d %s %s %s %s %s %s %s %d %d %x %d %f %f %d", 
		&objID, modelName, texName, type, handlingID, gameName, anims, vclass, &frq, &vflags, &comprules, &_p1, &_p2, &_p3, &_p4);

	CModelCars * model = (CModelCars *)allocCarsModel (objID);
	model->modelNameHash = crc32FromUpcaseString ((BYTE *)modelName);
	model->setTxd (texName);
	CTxdEntry * txd = (CTxdEntry *)((*txdPool)->at (model->txdIndex));
	txd->parentIndex = (WORD)txdIndexByNameAlways ("vehicle"); 
	model->initIfpName (anims);	
	
	for (char * p = gameName; *p; p++)
		if (*p == '_')
			*p = ' ';

	strncpy (model->gamename, gameName, 8);
	model->vflags = vflags;
	model->comprules = comprules;
	model->handlingIndex = findVehicleIndex2 (handlingID);

	model->_p1 = _p1;	// NOTE: правильно ли это?
	model->_p2 = _p2;
	model->_p3 = _p3;
	model->_p4 = _p4;

	if (!strncmp (type, "car", 4)) 
		model->type = 0;
	else if (!strncmp (type, "mtruck", 7))
		model->type = 1;
	else if (!strncmp (type, "quad", 5))
		model->type = 2;
	else if (!strncmp (type, "heli", 5))
		model->type = 3;
	else if (!strncmp (type, "plane", 6))
		model->type = 4;
	else if (!strncmp (type, "boat", 5))	// здесь не устанавливались p1..p3
		model->type = 5;
	else if (!strncmp (type, "train", 6))
		model->type = 6;
	else if (!strncmp (type, "f_heli", 7)) 
		model->type = 3;
	else if (!strncmp (type, "f_plane", 8)) {
		model->type = 8;
		model->_p2 = model->_p3 = 1.0;
	} else if (!strncmp (type, "bike", 5)) {
		model->type = 9;
		model->_f58 = (float)_p1;	// соответственно, _p1 просто так не устанавливался
	} else if (!strncmp (type, "bmx", 4)) {
		model->type = 10;
		model->_f58 = (float)_p1;
	} else if (!strncmp (type, "trailer", 8))
		model->type = 11;
	else
		debug ("readIdeCars: ERROR: unknown vehicle type '%s'\n", type);
	
	if (!strncmp (vclass, "normal", 7)) 
		model->vclass = 0;
	else if (!strncmp (vclass, "poorfamily", 11)) 
		model->vclass = 1;
	else if (!strncmp (vclass, "richfamily", 11)) 
		model->vclass = 2;
	else if (!strncmp (vclass, "executive", 10)) 
		model->vclass = 3;
	else if (!strncmp (vclass, "worker", 7)) 
		model->vclass = 4;
	else if (!strncmp (vclass, "big", 4)) 
		model->vclass = 5;
	else if (!strncmp (vclass, "taxi", 5)) 
		model->vclass = 6;
	else if (!strncmp (vclass, "moped", 6)) 
		model->vclass = 7;
	else if (!strncmp (vclass, "motorbike", 10)) 
		model->vclass = 8;
	else if (!strncmp (vclass, "leisureboat", 12)) 
		model->vclass = 9;
	else if (!strncmp (vclass, "workerboat", 11)) 
		model->vclass = 10;
	else if (!strncmp (vclass, "bicycle", 8)) 
		model->vclass = 11;
	else if (!strncmp (vclass, "ignore", 7))  {
		model->vclass = 255;
		return objID;
	} else
		debug ("readIdeCars: ERROR: unknown vehicle class '%s'\n", vclass);

	model->frq = frq;
	return objID;
}


static int readIdePeds (char * line) {
	int objID;
	char modelName[24];
	char texName[24];
	char pedType[24];
	char statName[24];
	char animGroup[24];
	int carMask;
	int flags;
	char animFile[16];
	int radio1, radio2;
	char voiceType[20];
	char voice1[60];
	char voice2[60];
	sscanf (line, "%d %s %s %s %s %s %x %x %s %d %d %s %s %s", 
		&objID, modelName, texName, pedType, statName, animGroup, &carMask, &flags, animFile, &radio1, &radio2, voiceType, voice1, voice2);

	CModelPeds * model = (CModelPeds *)allocPedsModel (objID);
	model->modelNameHash = crc32FromUpcaseString ((BYTE *)modelName);
	model->setTxd (texName);
	model->initIfpName (animFile);	
	model->setColModel (colModelPeds, false);
	model->pedType = getPedTypeByName (pedType);
	model->statType = getPedStatTypeByName (statName);
	
	for (int i =0 ; i < *animationStylesCount; i++)
		if (!strcmp (getAnimationStyleName (i), animGroup)) {
			model->animType = i;
			break;
		}
	
	model->carMask = carMask;
	model->flags = flags;
	model->radio1 = (BYTE)radio1+1;
	model->radio2 = (BYTE)radio2+1;
	model->pedModel = getPedModelByName (modelName);
	model->voiceType = getPedVoiceTypeByName (voiceType);
	model->voice1 = model->voice3 = getPedVoiceByName (voice1, model->voiceType);
	model->voice2 = getPedVoiceByName (voice2, model->voiceType);

	return objID;
}

static int (*readIde2dfx)(char *) = (int (__cdecl *)(char *))0x5B7670;

static void readIdeTxdp (char * line) {
	char	name[32];
	char	parentName[32];
	sscanf (line, "%s %s", name, parentName);

	CTxdEntry * txdEntry = (CTxdEntry *)(*txdPool)->at (txdIndexByNameAlways (name));
	if (txdEntry) 
		txdEntry->parentIndex = (WORD)txdIndexByNameAlways (parentName);
}

static int readIdePathHead(char * line, int * p1) {
	int d1;
	char s[80];
	sscanf (line, "%d %d %s", p1, &d1, s);
	return d1;
}
static int (*readIdePathData0)(char *, int, int) = (int (__cdecl *)(char *, int, int))0x5B41F0;
static int (*readIdePathData)(char *, int, int, int) = (int (__cdecl *)(char *, int, int, int))0x5B4380;

///static void (*loadIdeFile)(char * filename) = (void (__cdecl *)(char *))0x5B8400;
///static void (*loadHierFile)(char * filename) = (void (__cdecl *)(char *))0x5B3A30;
///static void (*loadIplFile)(char * filename) = (void (__cdecl *)(char *))0x5B8700;

void loadIdeFile (char * filename) {
	int _v10C	= -1;
	int _v108;
	int _v104 = 0x7FFFFFFF;

	FILE * f = fopen (filename, "rb");
	if (!f) {
		debug ("loadIdeFile: Unable to open '%s'\n", filename);
		return ;
	}
	DWORD tag = 0;	// group type 'objs', ...
	while (char * line = getConfigLine (f)) {
		if ('#' == *line || '\0' == *line)
			continue;	// skip comment

		if (tag == 0) {
			tag = *(DWORD *)line;	// get 4 chars from input string
			continue;
		} 
		
		if (((*(DWORD *)line) & 0x00FFFFFF) == 0x646e65) {	// 'end' ?
			tag = 0;	// end of block
			continue;
		}
		
		int num;
		int path = -1;	// номер строки в блоке path
		int pathid;
		switch (tag) {
		case 0x736a626f: // 'objs' - 1
			num = readIdeObjs (line);
			break;
		case 0x6a626f74: // 'tobj' - 3
			num = readIdeTobj (line);
			break;
		case 0x70616577: // 'weap' - 4
			num = readIdeWeap (line);
			break;
		case 0x72656968: // 'hier' - 5
			num = readIdeHier (line);
			break;
		case 0x6d696e61: // 'anim' - 6
			num = readIdeAnim (line);
			break;
		case 0x73726163: // 'cars' - 7
			num = readIdeCars (line);
			break;
		case 0x73646570: // 'peds' - 8
			num = readIdePeds (line);
			break;
		case 0x68746170: // 'path' - 9 (GTA3 only)
			if (path == -1) {
				pathid = readIdePathHead (line, &_v108);
				path = 0;	
			} else {
				if (_v108 == 0) 
					readIdePathData0 (line, pathid, path);
				else if (_v108 == 1 || _v108 == 2)
					readIdePathData (line, pathid, path, _v108 == 1 ? 0 : 1);				
				if (++path >= 12)
					path = -1;
			}
			break;
		case 0x78666432: // '2dfx' - 10
			readIde2dfx (line);
			num = -1;
			break;
		case 0x70647874: // 'txdp' - 11
			readIdeTxdp (line);
			num = -1;
			break;
		default:
			continue;
		}
		if (num == -1)
			continue;
		if (num > _v10C)
			_v10C = num;
		if (num < _v104)
			_v104 = num;

	}
	fclose (f);
}

static void (*loadColFile)(char * filename, BYTE param) = (void (__cdecl *)(char *, BYTE))0x5B4E60; 


static void (*setModelIndices)() = (void (__cdecl *)())0x5B57C0;
static void (*loadObjectDat)(char * filename, bool) = (void (__cdecl *)(char *, bool))0x5B5360;
/// static void (*setupVehicleInfo)() = (void (__cdecl *)())0x5B8F00;

static void (*readCarcolsDat) () = (void (__cdecl *)())0x5B6890;
static void (*updateLoadingBar) () = (void (__cdecl *)())0x590D00;
static void (*readCarmodsDat)() = (void (__cdecl *)())0x5B65A0;

RwTexture ** whiteTexture = (RwTexture **)0xB4E3EC;	
RwTexDictionary ** vehicleTxd = (RwTexDictionary **)0xB4E688;	// используется еще в одном коллбэке
RwTexture ** texVehiclelights128 = (RwTexture **)0xB4E68C;
RwTexture ** texVehiclelightson128 = (RwTexture **)0xB4E690;
static void (*_5D5BC0)() = (void (__cdecl *)())0x5D5BC0;

static void setupWhiteTexture () {	// TODO: optimize function!
	saveTexDictionary ();
	txdSetCurrent (txdIndexByName ("particle"));
	if (*whiteTexture == NULL) {
		RwTexture * wt = RwReadTexture ("white", NULL); 
		*whiteTexture = wt;
		wt->name[0] = '@';
		wt->filterAddressing &= 0xFFFFFF00;	// TODO: проверить, нужно ли это
		wt->filterAddressing |= 2;
	}
	restoreTexDictionary ();
}

static void setupVehicleInfo () {
	readCarcolsDat ();
	updateLoadingBar ();
	readCarmodsDat ();
	updateLoadingBar ();
	setupWhiteTexture ();
	updateLoadingBar ();

	int vtxd = txdIndexByNameAlways ("vehicle");
	///debug ("'vehicle' TXD = %d\n", vtxd);
	txdLoad (vtxd, "MODELS\\GENERIC\\VEHICLE.TXD");
	txdAddRef (vtxd);
	*vehicleTxd = ((CTxdEntry *)(*txdPool)->at (vtxd))->dictionary;		
	*texVehiclelights128 = RwTexDictionaryFindNamedTexture (*vehicleTxd, "vehiclelights128");
	*texVehiclelightson128 = RwTexDictionaryFindNamedTexture (*vehicleTxd, "vehiclelightson128");
	updateLoadingBar ();
	*vehicleStructPool = new CPool (50, 0x314);	// TODO: move to pool initialization
	_5D5BC0 ();	// setup texture 'vehiclegrunge256' TODO: inline it!
}

static void (*setupImgStreaming)() = (void (__cdecl *)())0x5B8AD0;
static void (*_590D00)() = (void (__cdecl *)())0x590D00;
static void (*_4113D0)() = (void (__cdecl *)())0x4113D0;

// ## parse IPL tags ##
int *	_iplInstBlockNum = (int *)0x8E3F00;	// = 0;
void *** _iplInstBlocks = (void ***)0x8E3F08;	// [40]

static int *	_iplInstNum = (int *)0xBCC0D8;
static void **	_iplInst = (void **)0xBCC0E0;	// [4096]
static void *	(*readIplInst)(char * line) = (void * (__cdecl *)(char *))0x538690;
static void		(*readIplZone)(char * line) = (void (__cdecl *)(char *))0x5B4AB0;
static void		(*readIplCull)(char * line) = (void (__cdecl *)(char *))0x5B4B40;
static void		(*readIplOccl)(char * line, char * filename) = (void (__cdecl *)(char *, char *))0x5B4C80;
static void		(*readIplAuzo)(char * line) = (void (__cdecl *)(char *))0x5B4D70;
static void		(*readIplGrge)(char * line) = (void (__cdecl *)(char *))0x5B4530;
static void		(*readIplEnex)(char * line) = (void (__cdecl *)(char *))0x5B8030;
static void		(*readIplPick)(char * line) = (void (__cdecl *)(char *))0x5B47B0;
static void		(*readIplCars)(char * line, int) = (void (__cdecl *)(char *, int))0x5B4740;
static void		(*readIplJump)(char * line) = (void (__cdecl *)(char *))0x5B45D0;
static void		(*readIplTcyc)(char * line) = (void (__cdecl *)(char *))0x5B81D0;

static DWORD (*_404DE0)(char * filename, int blockIndex, void ** insts) = (DWORD (__cdecl *)(char *, int, void **))0x404DE0;
static void	 (*_5B51E0)(DWORD) = (void (__cdecl *)(DWORD))0x5B51E0;
static void  (*_405110)(int blockIndex) = (void (__cdecl *)(int))0x405110;

/*
static void _readIplInst (char * line) {	// >> 538690
	// line parameters
	int		id;
	char	modelName[24];
	int		interior;	// 0 - outside
	float	x, y, z;	// TODO: define as vector
	float	rotatex, rotatey, rotatez, rotatew;	// define 
	int		lod;
	sscanf (line, "", &id, modelName, &interior, &x, &y, &z, &rotatex, &rotatey, &rotatez, &rotatew, &lod); 
	// NOTE: TODO: дальше начинается создание объектов, для которого должны быть описаны базовые 3D объекты
	// т.е., в зависимсти от модели, создается либо Building, либо Dummy
}
*/

static void loadIplFile (char * filename) {
	FILE * f = fopen (filename, "rb");
	if (!f)
		return ;

	*_iplInstNum = 0;

	DWORD tag = 0;
	while (char * line = getConfigLine (f)) {
		if ('#' == *line || '\0' == *line)
			continue;	// skip comment

		if (tag == 0) {	// tag type undefined. get new tag and skip to the new line
			tag = *(DWORD *)line;	
			continue;
		} 
		
		if (((*(DWORD *)line) & 0x00FFFFFF) == 0x646e65) {	// 'end' ?
			tag = 0;	// end of block
			continue;
		}
		if (0x68746170 == tag)	// 'path' 
			break;

		// TODO: отсортировать по частоте использования
		switch (tag) {			
		case 0x74736e69:	// 'inst' - 2
			_iplInst[(*_iplInstNum)++] = readIplInst (line);			
			break;
		case 0x746c756d:	// 'mult' - 3
			break;
		case 0x656e6f7a:	// 'zone' - 4
			readIplZone (line);
			break;
		case 0x6c6c7563:	// 'cull' - 5
			readIplCull (line);
			break;
		case 0x6c63636f:	// 'occl' - 6
			readIplOccl	(line, filename);
			break;
		case 0x65677267:	// 'grge' - 8
			readIplGrge (line);
			break;
		case 0x78656e65:	// 'enex' - 9
			readIplEnex (line);
			break;
		case 0x6b636970:	// 'pick' - 10
			readIplPick (line);
			break;
		case 0x73726163:	// 'cars' - 11
			readIplCars (line, 0);
			break;
		case 0x706d756a:	// 'jump' - 12
			readIplJump (line);
			break;
		case 0x63796374:	// 'tcyc' - 13 
			readIplTcyc (line);
			break;
		case 0x6f7a7561:	// 'auzo' - 14
			readIplAuzo (line);
			break;
		default:
			debug ("IPL: ERROR: unknown tag %08x\n", tag);
		}
	}
	int instCount = -1;
	if (*_iplInstNum > 0) {
		// inlined >> 404780 and 4047B0
		_iplInstBlocks[*_iplInstBlockNum] = new void *[*_iplInstNum];
		// end of inlined block
		memcpy (_iplInstBlocks[*_iplInstBlockNum], _iplInst, *_iplInstNum*sizeof (void *));
		instCount = (*_iplInstBlockNum)++;
	}
	_5B51E0 (_404DE0 (filename, instCount, _iplInst+(*_iplInstNum)));
	_405110 (instCount);
}

static void (*_405780)() = (void (__cdecl *)())0x405780;
static void (*_410EC0)() = (void (__cdecl *)())0x410EC0;
static void (*_410E00)() = (void (__cdecl *)())0x410E00;

void (*readTrackFiles)() = (void (__cdecl *)())0x6F7440;

void (*_openImgFile)(char *, bool) = (void (__cdecl *)(char *, bool))0x407610;

// static DWORD (*loadTxdFile)(char * filename) = (DWORD (__cdecl *)(char *))0x5B3860;
static DWORD loadTxdFile (char * filename) {
	RwStream *stream = RwStreamOpen (rwSTREAMFILENAME, rwSTREAMREAD, filename);
	if (stream) {
		DWORD txd = 0;
		if (RwStreamFindChunk (stream, 0x16, NULL, NULL)) 
			txdReadChunk (stream);
		RwStreamClose (stream, NULL);
		if (txd)
			return txd;
	}
	return RwTexDictionaryCreate ();
}

static RwTexture * addTextureCB (RwTexture * tex, void * param) { // 5B38F0 
	return (RwTexture *)RwTexDictionaryAddTexture ((DWORD)param, tex);
}


char messageBuffer[256];
static void showProgressMessage (char * text) {
	char * param = strchr (text, '\\');
	param = param ? param+1 : text;
	sprintf_s (messageBuffer, 256, "# Loading %s\n", param);	// TODO: remove '\n'
	// gtaLog ("Loading the game", param);
	debug (messageBuffer);
	// fflush (log);
}

// NOTE: see comment above readGameConfig in gtasaQueue.h
void readGameConfig (TCHAR * filename) {
	DWORD txd = RwTexDictionaryGetCurrent ();
	if (!txd) {
		txd = RwTexDictionaryCreate ();
		RwTexDictionarySetCurrent (txd);
	}

	debug ("readGameConfig: %s\n", filename);
	FILE * config = fopen (filename, "r");
	if (!config) {
		debug ("readGameConfig: unable to open file\n");
		return;	
	}

	bool modelsDone = false;

	while (char * line = getConfigLine (config)) {
		if ('#' == *line || '\0' == *line)
			continue;	// skip comment
		if (!strncmp (line, "EXIT", 4)) {	
			debug ("readGameConfig: EXIT \n");
			break;
		}
		if (!strncmp (line, "TEXDICTION", 10)) {
			debug ("readGameConfig: TEXDICTION (%s)\n", line+11);
			showProgressMessage (line);
			DWORD txdFile = loadTxdFile (line+11);
			// TODO: check errors?
			RwTexDictionaryForAllTextures ((const RwTexDictionary *)txdFile, addTextureCB, (void *)txd);
			RwTexDictionaryDestroy (txdFile);
			continue;
		}

		if (!strncmp (line, "IMG", 3)) {	// 'IMG'
			debug ("readGameConfig: IMG (%s)\n", line+4);
			if (strncmp (line+4, "MODELS\\GTA_INT.IMG", 19))
				_openImgFile (line+4, true);
			else 
				debug ("readGameConfig: GTA_INT.IMG skipped\n");
			continue;
		}

		if (!strncmp (line, "COLFILE", 7)) {
			debug ("readGameConfig: COLFILE (%s)\n", line+10);
			showProgressMessage (line+10);
			loadColFile (line+10, 0);
			continue;
		}

		if (!strncmp (line, "MODELFILE", 9)) {
			debug ("readGameConfig: MODELFILE (%s)\n", line+10);
			showProgressMessage (line+10);
			loadModelFile (line+10);
			continue;
		}

		/*	I'd tried to find any HIERFILE, but without success
		if (!strncmp (line, "HIERFILE", 8)) {
			debug ("readGameConfig: HIERFILE (%s)\n", line+9);
			showProgressMessage (line+9);
			loadHierFile (line+9);
			continue;
		}
		*/

		if (!strncmp (line, "IDE", 3)) {
			debug ("readGameConfig: IDE (%s)\n", line+4);
			showProgressMessage (line+4);
			loadIdeFile (line+4);
			continue;
		}
		
		if (!strncmp (line, "IPL", 3)) {
			debug ("readGameConfig: IPL (%s)\n", line+4);
			char tmpbuf[60];
			strcpy_s (tmpbuf, 60, line+4);

			if (!modelsDone) {
				setModelIndices ();
				showProgressMessage ("Object data");
				loadObjectDat ("data/object.dat", false);
				showProgressMessage ("Setup vehicle info data");
				setupVehicleInfo ();
				showProgressMessage ("Streaming init");
				setupImgStreaming ();				
				_590D00 ();
				showProgressMessage ("Collision");
				_4113D0 ();
				debug ("... setup models\n");
				for (int i = 0; i < 20000; i++)	// TODO: MODEL_COUNT
					if (modelPtrs[i])
						modelPtrs[i]->initIfp ();	// init ifp indices for all models (if applicable)
				modelsDone = true;
			}
			showProgressMessage (tmpbuf);
			loadIplFile (tmpbuf);
			continue;
		}

		if (!strncmp (line, "SPLASH", 6)) {
			debug ("readGameConfig: SPLASH (?) -- ignored\n");
			continue;
		}
		debug ("readGameConfig: unrecognized line '%s'\n", line);
	}
	fclose (config);
	RwTexDictionarySetCurrent (txd);
	if (modelsDone) {
		_405780 ();
		_410EC0 ();
		readTrackFiles ();
		_410E00 ();
	}
}

extern void injectFunction (DWORD, DWORD);
void configInject () {
	// TODO: models
	injectFunction (0x4C5940, (DWORD)getModelIndexByName);	
	injectFunction (0x537CE0, (DWORD)processCOL3);

	injectFunction (0x5B9030, (DWORD)readGameConfig);

//	injectFunction (0x, (DWORD));	
	debug ("sizeof CHandling = 0x%x\n", sizeof (CHandling));
	debug ("sizeof HandlingVehicle = 0x%x\n", sizeof (HandlingVehicle));
	debug ("sizeof HandlingBike = 0x%x\n", sizeof (HandlingBike));
	debug ("sizeof HandlingFlying = 0x%x\n", sizeof (HandlingFlying));
	debug ("sizeof HandlingBoat = 0x%x\n", sizeof (HandlingBoat));
	if (sizeof (CHandling) != 0xC624)
		exit (0);
}
