// Open SanAndreas
// (c) 2006-2008 Stanislav "listener" Golovin
// 
// Object Models

#include "stdafx.h"
#include "ogMath.h"
#include "ogMemory.h"
#include "ogRW.h"
#include "ogTextures.h"
#include "ogHud.h"
#include "ogModels.h"

#include "gtaInterface.h"

#include <assert.h>
#pragma warning(disable : 4996)	// sscanf/str[n]cpy

// TODO: mode to gtaInterface
CAtomicCallBackRender AtomicDefaultRenderCallBack = (CAtomicCallBackRender)0x7491C0;

CIfpFile * ifpFiles = (CIfpFile *)0xB5D4A0;	// TODO: CIfpFile ifpFiles[180];
int * ifpFilesAllocated =  (int *)0xB4EA30;
CIfpAnimations * ifpAnimations = (CIfpAnimations *)0xB4EA40; // TODO: CIfpAnimations ifpAnimations[2500];
int * animationsLoaded = (int *)0xB4EA2C;
CAnimationStyle * animationStyles = (CAnimationStyle *)0x8AA5A8;	// predefined array CAnimationStyle animationStyles[118];

void (*_4D6720)(CClump *) = (void (__cdecl *)(CClump *))0x4D6720; // (8)
void (*_4D4410)(CClump *, CIfpAnimations *, int, float) = (void (__cdecl *)(CClump *, CIfpAnimations *, int, float))0x4D4410;

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

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

// model components
ModelComponent * pedComponents = (ModelComponent *)0x8A6268;
ModelComponent ** carComponentsByType = (ModelComponent **)0x8A7740;
CHandling * handling = (CHandling *)0xC2B9C8;
ComponentLinks * componentLinks = (ComponentLinks *)0xB4E6D8;


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

CFrame ** carFrame = (CFrame **)0xB4E6B8;

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

// vehicle model helpers

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

typedef CAtomic   *(*CAtomicCB) (CAtomic * atomic, void *data);
static CAtomic * (*SetAtomicRenderCB_Train)	(CAtomic * a, void * d) = (CAtomicCB)0x4C7AA0;
static CAtomic * (*SetAtomicRenderCB_Boat)	(CAtomic * a, void * d) = (CAtomicCB)0x4C79A0;
static CAtomic * (*SetAtomicRenderCB_Heli)	(CAtomic * a, void * d) = (CAtomicCB)0x4C7870;
static CAtomic * (*SetAtomicRenderCB_Plane)	(CAtomic * a, void * d) = (CAtomicCB)0x4C7930;
static CAtomic * (*SetAtomicRenderCB)		(CAtomic * a, void * d) = (CAtomicCB)0x4C77E0;

extern CAtomic * (*_5DA610)(CAtomic *); // TODO: определен в ogRW.cpp, сделать методом CAtomic

RpMaterial * (*generateLicenseplateMaterial)(CClump * clump, char * text, DWORD flags) = (RpMaterial * (__cdecl *)(CClump *, char *, DWORD))0x6FE0F0;

//=============================================================================
//	collision data structures

ColLink * ColDataHeader::getColLink() {
	return (ColLink *)(((DWORD)ptr2 + numberOfFaces*10 + 3)&~3);
}

void ColDataHeader::unlink () {
	if (!ptr2)
		return;
	ColLink * link = getColLink ();

	link->prev->next = link->next;
	link->next->prev = link->prev;
	
	freeColLinks->prev->next = link;
	link->prev = freeColLinks->prev;
	link->next = freeColLinks;
	freeColLinks->prev = link;
}

// find maximum vertex index
DWORD ColDataHeader::calculateNumberOfShadowMeshVertices() {
	int num = 0;
	ColFace * face = (ColFace *)shadowMeshFaces;
	for (DWORD i = 0; i < numberOfShadowMeshFaces; i++, face++) {
		if (face->a > num)
			num = face->a;
		if (face->b > num)
			num = face->b;
		if (face->c > num)
			num = face->c;
	}
	return numberOfShadowMeshVertices = num+1;
}

// CColData
CColData::CColData() {	
	numberOfSpheres = numberOfBoxes = numberOfFaces = 0;
	number1 = 0;
	flags = 0;	// &= ~7;
	spheres = boxes = ptr1 = meshVertices = meshFaces = ptr2 = NULL;
	numberOfShadowMeshVertices = numberOfShadowMeshFaces = 0;
	shadowMeshVertices = shadowMeshFaces = NULL;
};

CColData::~CColData() {
	if (spheres)
		free (spheres);
	if (ptr1)
		free (ptr1);
	if (boxes)
		free (boxes);
	if (meshVertices)
		free (meshVertices);
	if (meshFaces)
		free (meshFaces);
	if (shadowMeshVertices)
		free (shadowMeshVertices);
	if (shadowMeshFaces)
		free (shadowMeshFaces);
	unlink ();
	// NOTE: здесь еще очищались все поля объекта. Поскольку, по выходу, объект будет освободжен - это не требуется
}

void CColData::initFromRawData (ColDataHeader * data) {
	// spheres
	if (data->numberOfSpheres == 0) {	
		// сферы отсутствуют. Очистим соответствующие поля
		numberOfSpheres = 0;
		if (spheres) {
			free (spheres);
			spheres = NULL;
		}
	} else {
		if (numberOfSpheres != data->numberOfSpheres) {
			// количество сфер не совпадает. Нужно выделить блок другого размера
			numberOfSpheres = data->numberOfSpheres;
			if (spheres)
				free (spheres);
			spheres = (BYTE *)malloc (numberOfSpheres*sizeof (ColSphere));
		}
		memcpy (spheres, data->spheres, numberOfSpheres*sizeof (ColSphere));
	}

	// ??
	if (data->number1 == 0) {
		number1 = 0;
		if (ptr1) {
			free (ptr1);
			ptr1 = NULL;
		}
	} else {
		if (data->flags & 1) {
			if ((number1 != data->number1) || !(flags & 1)) {
				number1 = data->number1;
				if (ptr1)
					free (ptr1);
				ptr1 = (BYTE *)malloc (number1*36);
			}
			memcpy (ptr1, data->ptr1, number1*36);
		} else {
			if ((number1 != data->number1) || (flags & 1)) {
				number1 = data->number1;
				if (ptr1)
					free (ptr1);
				ptr1 = (BYTE *)malloc (number1*32);
			}
			memcpy (ptr1, data->ptr1, number1*32);
		}		
	}
	BYTE f = (data->flags ^ flags) & 5;
	flags ^= f;

	// boxes
	if (data->numberOfBoxes == 0) {
		numberOfBoxes = 0;
		if (boxes) {
			free (boxes);
			boxes = NULL;
		}
	} else {
		if (numberOfBoxes != data->numberOfBoxes) {
			numberOfBoxes = data->numberOfBoxes;
			if (boxes)
				free (boxes);
			boxes = (BYTE *)malloc (numberOfBoxes*sizeof (ColBox));
		}
		// оригинально, здесь использовался ColBox::copyFrom. 
		// Поскольку выделенный блок не проинициализирован, все равно, как его копировать
		memcpy (boxes, data->boxes, numberOfBoxes*sizeof (ColBox));	
	}

	// mesh faces
	if (data->numberOfFaces == 0) {
		numberOfFaces = 0;
		if (meshFaces) {
			free (meshFaces);
			meshFaces = NULL;
		}
		if (meshVertices) {
			free (meshVertices);
			meshVertices = NULL;
		}
	} else {
		// mesh vertices
		// calculate vertex count
		ColFace * face = (ColFace *)data->meshFaces;
		DWORD maxVertice = 0;
		for (int i =  data->numberOfFaces; i > 0; face++, i--) {
			if (face->a > maxVertice) 
				maxVertice = face->a;
			if (face->b > maxVertice)
				maxVertice = face->b;
			if (face->c > maxVertice)
				maxVertice = face->c;
		}
		maxVertice++;

		if (meshVertices)
			free (meshVertices);
		meshVertices = (BYTE *)malloc (maxVertice*sizeof (ColVertice));
		memcpy (meshVertices, data->meshVertices, maxVertice*sizeof (ColVertice));

		// back to mesh faces
		if (numberOfFaces != data->numberOfFaces) {
			numberOfFaces = data->numberOfFaces;
			if (meshFaces)
				free (meshFaces);
			meshFaces = (BYTE *)malloc (numberOfFaces*sizeof (ColFace));
		}
		memcpy (meshFaces, data->meshFaces, numberOfFaces*sizeof (ColFace));
	}

	// shadow mesh
	if ((data->numberOfShadowMeshFaces == 0) || !(data->flags & 4)) {
		numberOfShadowMeshFaces = 0;
		numberOfShadowMeshVertices = 0;
		if (shadowMeshFaces) {
			free (shadowMeshFaces);
			shadowMeshFaces = NULL;
		}
		if (shadowMeshVertices) {
			free (shadowMeshVertices);
			shadowMeshVertices = NULL;
		}
	} else {
		// calculate shadow vertex count
		// TODO: use ColDataHeader::calculateNumberOfShadowMeshVertices;
		ColFace * face = (ColFace *)data->shadowMeshFaces;
		DWORD maxVertice = 0;
		for (int i =  data->numberOfShadowMeshFaces; i > 0; face++, i--) {
			if (face->a > maxVertice) 
				maxVertice = face->a;
			if (face->b > maxVertice)
				maxVertice = face->b;
			if (face->c > maxVertice)
				maxVertice = face->c;
		}
		maxVertice++;
		
		if (shadowMeshVertices) 
			free (shadowMeshVertices);
		shadowMeshVertices = (BYTE *)malloc (maxVertice * sizeof (ColVertice));
		memcpy (shadowMeshVertices, data->shadowMeshVertices, maxVertice*sizeof (ColVertice));
		numberOfShadowMeshVertices = data->numberOfShadowMeshVertices;	// TODO: возможно, максимальный номер можно было и не считать

		// back to shadow faces
		if (numberOfShadowMeshFaces != data->numberOfShadowMeshFaces) {
			numberOfShadowMeshFaces = data->numberOfShadowMeshFaces;
			if (shadowMeshFaces)
				free (shadowMeshFaces);
			shadowMeshFaces = (BYTE *)malloc (numberOfShadowMeshFaces * sizeof (ColFace));
		}
		memcpy (shadowMeshFaces, data->shadowMeshFaces, numberOfShadowMeshFaces*sizeof (ColFace));
	}
}
//=============================================================================
//	CColModel
void * CColModel::operator new (size_t) {
	return (*colModelPool)->allocate ();
}

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

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

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

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

void CColModel::releaseData () {
	if (colData) {
		if (flags & 2) {	// raw data
			colDataHeader->unlink ();
			free (colDataHeader);			
		} else 
			delete colData;
		colData = NULL;
	}
}

void CColModel::createColData() {
	flags &= ~2;
	colData = new CColData ();
}

void CColModel::createColDataFromRawData () {
	if (!(flags & 2))
		return;	// no raw data present
	CColData * cd = new CColData ();
	cd->initFromRawData (colDataHeader);
	free (colDataHeader);
	flags &= ~2;
	colData = cd;
}

// выделить блок данных для 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	
	ColDataHeader * dh = colDataHeader = (ColDataHeader *)malloc (size); // TODO: new[]
	if (!dh)
		return;	// TODO:
	BYTE * data = (BYTE *)dh;
	
	dh->numberOfSpheres = numSpheres;
	dh->number1 = 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->ptr2 = NULL;
	dh->flags = f ? 1 : 0;			
}

static CModelBase * collisionPlugin_currentModel;	// >> 9689E0 (2) модель, в которую читается colfile. TODO: сделать более адекватную конструкцию

void collisionPlugin_setCurrentModel (CModelBase * model) {
	collisionPlugin_currentModel = model;
}

static BYTE	colBuffer[32768];	// TODO: использовать alloca. Еще лучше - не использовать буфер в таком виде (выделить один раз, чтобы не было трех копирований)
CRwStream * collisionPlugin_read (CRwStream * stream, int length) {
	stream->read (colBuffer, length);
	CColModel	* colModel = new CColModel ();
	if (!colModel) {
		debug ("ColModel read error: Out of memory!\n");
		return NULL;
	}

	ColFileHeader * fileHeader = (ColFileHeader *)colBuffer;
	int dataLen = fileHeader->dataSize-(sizeof (ColFileHeader)-8);	// 8 = sizeof (fileHeader->fourcc) + sizeof (fileHeader->dataSize)

	debug ("Read Collision: %s\n", fileHeader->modelName[0] ? fileHeader->modelName : "-unnamed-");

	if (fileHeader->fourcc == 0x334C4F43)	// COL3
		processCOL3 (colBuffer+sizeof (ColFileHeader), dataLen, colModel, NULL);
	else if (fileHeader->fourcc == 0x324C4F43)	// COL2
		processCOL2 (colBuffer+sizeof (ColFileHeader), dataLen, colModel, NULL);
	else if (fileHeader->fourcc == 0x4C4C4F43)	// COLL
		processCOLL (colBuffer+sizeof (ColFileHeader), colModel, NULL);
	
	colModel->createColDataFromRawData ();
	collisionPlugin_currentModel->setColModel (colModel, true);
	collisionPlugin_currentModel->flags |= 0x800;
	return stream;
}

void loadColFile (char * filename) {	// >> 5B4E60 (1)	
	FILE * f = 	fopen (filename, "rb");
	if (!f) {
		debug ("ERROR: Unable to open colfile '%s'\n", filename);
		return;
	}

	ColFileHeader fh;
	while (fread (&fh, 1, sizeof (ColFileHeader), f) == sizeof (ColFileHeader)) {
		debug ("Reading COLMODEL '%s' from '%s'\n", fh.modelName, filename);
		int dataLen = fh.dataSize-(sizeof (ColFileHeader)-8);	// 8 = sizeof (fileHeader->fourcc) + sizeof (fileHeader->dataSize)
		fread (colBuffer, 1, dataLen, f);
		CModelBase * model = getModelIndexByName (fh.modelName, NULL);
		if (!model || !(model->flags & 0x80))
			continue;
		if (!model->colModel) 
			model->setColModel (new CColModel (), true);

		if (fh.fourcc == 0x334C4F43)	// COL3
			processCOL3 (colBuffer, dataLen, model->colModel, NULL);
		else if (fh.fourcc == 0x324C4F43)	// COL2
			processCOL2 (colBuffer, dataLen, model->colModel, NULL);
		else if (fh.fourcc == 0x4C4C4F43)	// COLL
			processCOLL (colBuffer, model->colModel, NULL);
		else
			debug ("ERROR: Bad COLMODEL FOURCC %08x\n", fh.fourcc); 

		model->colModel->_f28 = 0;		
	}
	fclose (f);
}

void processCOLL (BYTE * aData, CColModel * model, char * modelName) {
	// debug ("ERROR: Collision models v.1 (COLL) not supported yet\n");
	ColModelCOL1	* c1 = (ColModelCOL1 *)aData;
	model->sphere.radius = c1->radius;
	model->sphere.center = c1->center;
	model->bbox = c1->bbox;

	CColData * cd = model->colData = new CColData ();
	
	// последовательно разбираем блоки (фиксированной структуры нет)
	BYTE * dataPtr = aData+sizeof (ColModelCOL1);
	cd->numberOfSpheres = *(short *)dataPtr;
	dataPtr += 4;	// это не ошибка: используется два байта, а отведено четыре!
	if (cd->numberOfSpheres > 0) {
		cd->spheres = (BYTE *)malloc (cd->numberOfSpheres * sizeof (RwSphere));
		
		for (int i = 0; i < cd->numberOfSpheres; i++, dataPtr += sizeof (ColSphere1)) {
			ColSphere1 * s1 = (ColSphere1 *)dataPtr;
			ColSphere * s = (ColSphere *)(cd->spheres+i*sizeof (ColSphere)); 
			// ColSphere::set
			s->sphere.radius = s1->radius;
			s->sphere.center = s1->center;
			s->surface.material = s1->material;
			s->surface.flags = s1->flags;
			s->surface._f2 = s1->_f2;			
		}
	} 

	cd->number1 = *(char *)dataPtr;
	dataPtr += 4;
	cd->ptr1 = NULL;
	dataPtr += cd->number1 * 24;	// skip unknown block
	cd->number1 = 0;

	// boxes
	cd->numberOfBoxes = *(short *)dataPtr;
	dataPtr += 4;
	if (cd->numberOfBoxes > 0) {
		cd->boxes = (BYTE *)malloc (sizeof (ColBox)*cd->numberOfBoxes);
		for (int i = 0; i < cd->numberOfBoxes; i++, dataPtr += sizeof (ColBox)) {
			ColBox * src = (ColBox *)dataPtr;
			ColBox * dst = (ColBox *)(cd->boxes + i*sizeof (ColBox));
			// ColBox::set
			*dst = *src;
			dst->surface._f2 = src->surface.light;	// это поле лежит по-другому в старом формате
		}
	} 

	// vertices and faces not supported yet
	int numberOfVertices = *(int *)dataPtr;
	dataPtr += 4;
	if (numberOfVertices > 0) {
		cd->meshVertices = (BYTE *)malloc (numberOfVertices*sizeof (ColVertice));
		for (int i = 0; i < numberOfVertices; i++, dataPtr += 12) {
			float * f = (float *)dataPtr;
			ColVertice * v = (ColVertice *)(cd->meshVertices+i*sizeof (ColVertice));
			v->x = (short)(f[0]*128.0f);
			v->y = (short)(f[1]*128.0f);
			v->z = (short)(f[2]*128.0f);
		}
	}
	cd->numberOfFaces = *(int *)dataPtr;
	dataPtr += 4;
	if (cd->numberOfFaces > 0) {
		cd->meshFaces = (BYTE *)malloc (cd->numberOfFaces*sizeof (ColFace));
		for (int i = 0; i < cd->numberOfFaces; i++, dataPtr += sizeof (ColFace1)) {
			ColFace1 * src = (ColFace1 *)dataPtr;
			ColFace * dst = (ColFace *)(cd->meshFaces+i*sizeof (ColFace));
			dst->a = (WORD)src->a;
			dst->b = (WORD)src->b;
			dst->c = (WORD)src->c;
			dst->material = src->material;
			dst->light = src->light;
		}
	}
	cd->flags &= 0xFB;
	if (cd->numberOfBoxes || cd->numberOfSpheres || cd->numberOfFaces)
		model->flags |= 1;
}
void processCOL2 (BYTE * aData, int length, CColModel * model, char * modelName) {
	debug ("ERROR: Collision models v.2 (COL2) not supported yet\n");
}

// TODO: make CColModel method ?
// TODO: переработать после чистки ColDataHeader
void processCOL3 (BYTE * aData, int length, CColModel * model, char * modelName) {	// >> 537CE0 (4)
	ColModelCOL3 * header = (ColModelCOL3 *)aData;

	model->bbox = header->bbox;
	model->sphere = header->sphere;

	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), aData+sizeof (ColModelCOL3), length-sizeof (ColModelCOL3));
		ColDataHeader * dh = (ColDataHeader *)data;
		model->colDataHeader = dh;
		
		dh->numberOfSpheres = header->numberOfSpheres;
		dh->numberOfBoxes = header->numberOfBoxes;
		dh->numberOfFaces = (WORD)header->numberOfFaces;
		dh->number1 = (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->ptr2 = 0;
		model->flags |= 2;
	}
}
//=============================================================================
// handing
char * carNames = (char *)0x8D3978;	// TODO: char carNames[14][VEHICLE_COUNT];

int CHandling::findVehicleIndex2 (char *name) {
	char * ptr = carNames;
	for (int i = 0; i < VEHICLE_COUNT; i++, ptr += 14) 
		if (!strncmp (ptr, name, 14))
			return i;
	return VEHICLE_COUNT;	// TODO: -1 ?
}
//=============================================================================
// models
//-----------------------------------------------------------------------------
// CModelBase
// проинициализировать все поля модели
void CModelBase::init () {
	usageCount = 0;
	colModel = NULL;
	index2dfx = objectInfoIndex = txdIndex = -1;
	count2dfx = 0;
	drawDistance = 2000.0f;
	rwObject = NULL;
	flags = 0x00C0;
}

// освободить все ресурсы, занятые моделью
void CModelBase::clear () {
	deleteRwObject ();
	// unsetColModel ();
	if (colModel && !(flags & 0x80))	// TODO: что точно должно быть в этом флаге? 0x80 или !0x80 ?
		delete colModel;
	colModel = NULL;	
	flags |= 0x80;	
	index2dfx = objectInfoIndex = txdIndex = -1;
	count2dfx = 0;
}

void CModelBase::setTxd(char * txdName) {
	txdIndex = (*txdPool)->indexByNameAlways (txdName);	
}

void CModelBase::addRef () {
	usageCount++;
	(*txdPool)->addRef (txdIndex);
}

void CModelBase::release () {
	usageCount--;
	(*txdPool)->releaseTxd (txdIndex);
}

void CModelBase::setColModel(CColModel *col, bool initPairedModel) {	
	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))	// TODO: что точно должно быть в этом флаге? 0x80 или !0x80 ?
		delete colModel;
	colModel = NULL;	
}

extern void (__thiscall *  model_setComponentFlags)(CModelBase * _this, char * name) = (void (__thiscall *)(CModelBase *, char *))0x4C4570;
void CModelBase::setComponentFlags(char *componentName) {
	model_setComponentFlags (this, componentName);
}
//-----------------------------------------------------------------------------
// CModelObjs
void CModelObjs::deleteRwObject() {
	if (!rwObject)
		return;
	CAtomic * atomic = (CAtomic *)rwObject;	
	DWORD d2fx = atomic->geometry->pl2dfx ? *(atomic->geometry->pl2dfx) : 0;
	count2dfx -= (BYTE)d2fx;
	CFrame * frame = atomic->parent;
	delete atomic;
	delete frame;
	rwObject = NULL;
	(*txdPool)->releaseTxd (txdIndex);
	if (getIfpIndex() != -1) 
		ifpRelease (getIfpIndex ());
}

RwObject * CModelObjs::createInstanceWithMM (RwMatrix * mm) {
	if (!rwObject)
		return NULL;
	addRef ();
	CAtomic * a = new CAtomic ((CAtomic *)rwObject);
	CFrame * frame = new CFrame ();
	frame->modelling = *mm;
	a->atomicSetFrame (frame);	
	release ();
	return a;
}

RwObject * CModelObjs::createInstance () {
	if (!rwObject)
		return NULL;
	addRef ();
	CAtomic * a = new CAtomic ((CAtomic *)rwObject);	
	a->atomicSetFrame(new CFrame ());
	release ();
	return a;
}

void CModelObjs::setAtomic (CAtomic * a) {
	DWORD d2fx;
	if (rwObject) {		
		CAtomic * atomic = (CAtomic *)rwObject;
		d2fx = atomic->geometry->pl2dfx ? *(atomic->geometry->pl2dfx) : 0;
		count2dfx -= (BYTE)d2fx;
	}
	rwObject = a;
	d2fx = a->geometry->pl2dfx ? *(a->geometry->pl2dfx) : 0;
	count2dfx += (BYTE)d2fx;
	
	(*txdPool)->addRef (txdIndex);	
	if (getIfpIndex() != -1) 
		ifpAddRef (getIfpIndex ());

	a->setupLighting ();
	
	if ((!(flags & 0x8000)) && (flags & 0x7800) == 0x3000) {
		// inlined _49CE10
		debug ("*** TEST: attenpt to use pipeline at 0xA9AD78 \n");
		RpMaterial * mat = a->geometry->matList.materials[1];
		mat->pipeline = *(RxPipeline **)0xA9AD78;	// TODO: разобраться: этот адрес больше нигде не используется
		a->geometry->flags |= 0x40;
		a->visibilityData2 = 0;	// TODO: setAtomicVisibilityFlags
	}
	flags |= 1;
}
//-----------------------------------------------------------------------------
// CModelObjs2c
void CModelObjs2c::init() {
	CModelObjs::init ();
	rwObject2 = NULL;
}

void CModelObjs2c::deleteRwObject () {
	if (rwObject2) {
		CAtomic * atomic = (CAtomic *)rwObject2;
		CFrame * frame = atomic->parent;
		delete atomic;
		delete frame;
		rwObject2 = NULL;
	}
	CModelObjs::deleteRwObject ();
}

RwObject * CModelObjs2c::createInstanceWithMM (RwMatrix * mm) {
	if (*_A9B0B0) {
		if (!rwObject2)
			return NULL;	
		CAtomic * a = new CAtomic ((CAtomic *)rwObject2);
		CFrame * frame = new CFrame ();
		frame->modelling = *mm;
		a->atomicSetFrame (frame);
		return a;
	}
	return CModelObjs::createInstanceWithMM (mm);
}

RwObject * CModelObjs2c::createInstance () {
	if (*_A9B0B0) {
		if (!rwObject2)
			return NULL;
		CAtomic * a = new CAtomic ((CAtomic *)rwObject2);
		a->atomicSetFrame(new CFrame ());
		return a;
	}
	return CModelObjs::createInstance ();
}
//-----------------------------------------------------------------------------
// CModelTobj
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;
}

//-----------------------------------------------------------------------------
// CModelHier

static void (*setClumpVisibilityCallback)(CClump *, CModelHier *) =  (void (__cdecl *)(CClump *, CModelHier *))0x733750;

void CModelHier::deleteRwObject() {
	if (!rwObject)
		return;
	CClump * c = (CClump *)rwObject;
	CAtomic * a = c->findFirstAtomicWith2dfx ();
	if (a) {
		DWORD d2fx = *(a->geometry->pl2dfx);	// гарантировано ненулевое значение
		count2dfx -= (BYTE)d2fx;
	}
	delete c;	// RpClumpDestroy (clump);	
	rwObject = NULL;

	(*txdPool)->releaseTxd (txdIndex);
	if (getIfpIndex() != -1) 
		ifpRelease (getIfpIndex ());

	if (flags & 0x800) 
		unsetColModel ();
}
RwObject * CModelHier::createInstanceWithMM (RwMatrix * mm) {
	CClump * c = (CClump *)createInstance ();
	if (c)		
		c->parent->modelling = *mm;
	return c;
}

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

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

RwObject * CModelHier::createInstance() {
	if (!rwObject)
		return NULL;

	addRef ();
	CClump * c = new CClump ((CClump *)rwObject);
	CAtomic * a = c->getFirstAtomic ();
	if (a && a->geometry->skin && ! (flags&0x200)) {	
		RpHAnimHierarchy * hh = c->parent->getHAnimHierarchy ();
		c->atomicsSetHAnimHierarchy (hh);
		RtAnimInterpolatorSetCurrentAnim (hh->currentAnim, gtaAnimationCreate (hh));
		hh->flags = 0x3000;
	}

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

	release ();
	return c;
}

CClumpVisibilityCallback	vehicleVisibilityCB	= (CClumpVisibilityCallback)0x732A40;	// CVisibilityPlugins::VehicleVisibilityCB
CClumpVisibilityCallback	smallVehicleVisibilityCB = (CClumpVisibilityCallback)0x7336F0;	// CVisibilityPlugins::VehicleVisibilityCB_SmallVehicle

void CModelHier::setClump (CClump * c) {
	if (rwObject) {	
		CClump * clump = (CClump *)rwObject;
		CAtomic * a = clump->findFirstAtomicWith2dfx ();
		if (a) {
			DWORD d2fx = *(a->geometry->pl2dfx);	// гарантировано ненулевое значение
			count2dfx -= (BYTE)d2fx;
		}
	}

	rwObject = c;
	if (c) {
		CAtomic * a = c->findFirstAtomicWith2dfx ();
		if (a) {
			DWORD d2fx = *(a->geometry->pl2dfx);	// гарантировано ненулевое значение
			count2dfx += (BYTE)d2fx;
		}
	}
	
	// setClumpVisibilityCallback (>> 733750)
	// TODO: сделать виртуальный метод (чтобы не приводить типы лишний раз)?
	c->parent->hierarchyId = (DWORD)this;	// TODO: если это действительно так - объявить union hierarchyId/visibilityData
	if (getType() == 6) {	// CModelCars	
		int t = ((CModelCars *)this)->type;	// vehicle type
		c->visibilityCB = (t == 6 || t == 8) ? vehicleVisibilityCB : smallVehicleVisibilityCB;	
	}

	(*txdPool)->addRef (txdIndex);
	if (getIfpIndex () != -1)
		ifpAddRef (getIfpIndex ());

	void (*RpClumpForAllAtomics)(CClump *, DWORD, void *) = (void (__cdecl *)(CClump *, DWORD, void *))0x749B70; ///

	c->setupLighting ();
	// RpClumpForAllAtomics (c, 0x4C4F30, this);

	CAtomic * a = c->getFirstAtomic ();
	if (!a || !(a->geometry->skin))
		return;


	if (flags & 0x200) {
		c->atomicsSetHAnimHierarchy (NULL);
		return;
	}

	a->geometry->morphTarget->boundingSphere.radius *= 1.2f;
	RpHAnimHierarchy * hh = c->getHAnimHierarchy ();

	c->atomicsSetHAnimHierarchy (hh);
	/// RpClumpForAllAtomics (c, 0x4C4EF0, hh);	// setAtomicHAnimHierarchyCB

	// normalize vertex bone weights
	int n = a->geometry->numVertices;
	RwMatrixWeights * weights = RpSkinGetVertexBoneWeights (a->geometry->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;
	}
	hh->flags = 0x3000;
}

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;
	}
}

void CModelHier::initComponents(ModelComponent *components) {
	for (; components->name; components++) 
		if (!(components->flags & 1)) {
			CFrame * f = rwObject->parent->findFrameFromNameWithoutId (components->name);
			if (f) 
				f->hierarchyId = components->hierId;	// CVisibilityPlugins::SetFrameHierarchyId				
		}			
}
//-----------------------------------------------------------------------------
// CModelWeap
void CModelWeap::setClump(CClump *c) {
	CModelHier::setClump (c);

	c->atomicsSetRenderrCB (weaponRenderCB);
	
	CFrame * f = c->parent->findByNodeName ("gunflash");
	if (!f) 
		return ;
	CAtomic * a = (CAtomic *)f->getFirstObject ();
	if (!a)
		return;
	a->geometry->setAlphaForAllMaterials (0);	
	a->flags = 0;
	RpMaterial * m = a->geometry->matList.materials[0];
	m->surfaceProps.ambient = 16.0f;
}
//-----------------------------------------------------------------------------
// CModelPeds
CModelPeds::~CModelPeds () {
	if (pedColModel)
		delete pedColModel;
}

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

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

void CModelPeds::setClump (CClump * c) {
	CModelHier::setClump (c);
	initComponents (pedComponents);
	if (!pedColModel) 
		initPedColModel (c);
	c->atomicsSetRenderrCB (pedRenderCB);
	c->getHAnimHierarchy (); // TODO: а это еще зачем?
}

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

void * CVehicleStruct::operator new (size_t) {
	(*vehicleStructPool)->objectSize = sizeof (CVehicleStruct);	// TODO: убрать после того, как будет сделана инициализация этого пула
	return (*vehicleStructPool)->allocate ();
}

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

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++) {		
		CFrame * f = atomics[i]->parent;
		delete atomics[i];
		delete f;
	}
}

void ComponentLinks::setLink(WORD first, WORD second) {
	component1[componentCount] = first;
	component2[componentCount] = second;
	componentCount++;
}

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

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

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;
	}
}

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


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;
}

RwObject * CModelCars::createInstance () {	
	CClump * c = (CClump *)CModelHier::createInstance ();
	if (vehicleStruct->atomicCount > 0) {
		CFrame * f = NULL;
		if (type == 9 || type == 10 || type < 5) 
			f = c->parent->findFrameFromId (1);		
		if (!f)
			f = c->parent;
	
		int n = getCarVariation1 ();
		if (n != -1 && vehicleStruct->atomics[n]) {
			CAtomic * va = new CAtomic (vehicleStruct->atomics[n]);
			CFrame * vf = new CFrame ();
			RwFrameTransform (vf, &(vehicleStruct->atomics[n]->parent->modelling), 0);
			va->atomicSetFrame (vf);
			c->addAtomic (va);
			f->addChild (vf);			
		}
		*carCurrentVariation1 = n;
		n = getCarVariation2 ();
		if (n != -1 && vehicleStruct->atomics[n]) {
			CAtomic * va = new CAtomic (vehicleStruct->atomics[n]);
			CFrame * vf = new CFrame ();
			RwFrameTransform (vf, &(vehicleStruct->atomics[n]->parent->modelling), 0);
			va->atomicSetFrame (vf);
			c->addAtomic (va);
			f->addChild (vf);			
		}
		*carCurrentVariation2 = n;
	} else 
		*carCurrentVariation1 = *carCurrentVariation2 = -1;

	// TODO: сделать методом CClump (какая-то работа с MatFX)
	for (RwLLLink * l = c->atomicList.next; l != &(c->atomicList); l = l->next) {
		CAtomic * a = (CAtomic *)(((BYTE *)l)-offsetof(CAtomic, inClumpLink));
		_4C9410 (a, NULL);
	}

	return c;
}

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

	RpMaterialList	mlist;
	mlist.materials = (RpMaterial **)malloc (sizeof (RpMaterial *)*0x14);
	mlist.numMaterials = 0;
	mlist.space = 0x14;

	CClump * c = (CClump *)rwObject;
	// TODO: сделать метод в CClump
	for (RwLLLink * l = c->atomicList.next; l != &(c->atomicList); l = l->next) {
		CAtomic * a = (CAtomic *)(((BYTE *)l)-offsetof(CAtomic, inClumpLink));
		_4C8B60 (a, &mlist);
	}

	for (int i = 0; i < vehicleStruct->atomicCount; i++) 
		_4C8B60 (vehicleStruct->atomics[i], &mlist);	// add materials from the vehicle struct atomics

	_rpMaterialListDeinitialize (&mlist);
	
	// NOTE: похоже, что все, выше этого места, используется только для отладки
	for (RwLLLink * l = c->atomicList.next; l != &(c->atomicList); l = l->next) {
		CAtomic * a = (CAtomic *)(((BYTE *)l)-offsetof(CAtomic, inClumpLink));
		clearAtomicVisibilityFlagsCB (a, (void *)0x2000);
	}
}

// установить render callback для всех атомиков модели
void CModelCars::setVehicleRenderCallbacks () {
	CAtomicCB	cb;
	switch (type) {
	case 3:
		cb = SetAtomicRenderCB_Heli;
		break;
	case 4:
	case 8:
		cb = SetAtomicRenderCB_Plane;
		break;
	case 5:
		cb = SetAtomicRenderCB_Boat;
		break;
	case 6:	// Train
		cb = SetAtomicRenderCB_Train;
		break;
	default:
		cb = SetAtomicRenderCB;
	}

	CClump * c = (CClump *)rwObject;
	for (RwLLLink * l = c->atomicList.next; l != &(c->atomicList); l = l->next) {
		CAtomic * a = (CAtomic *)(((BYTE *)l)-offsetof(CAtomic, inClumpLink));
		cb (a, rwObject);
	}
}

void CModelCars::_4C8900 () {
	CClump * c = (CClump *)rwObject;
	for (RwLLLink * l = c->atomicList.next; l != &(c->atomicList); l = l->next) {
		CAtomic * a = (CAtomic *)(((BYTE *)l)-offsetof(CAtomic, inClumpLink));
		_5DA610 (a);
	}

	if (!*carFrame) {
		CFrame * f = *carFrame = new CFrame ();
		
		D3DVECTOR axis = { 1.0f, 0.0f, 0.0f };
		RwMatrixRotate (&f->modelling, &axis, 60.0f, 0);
		f->updateObject ();		
		f->getLTM ();	// sync hierarchy LTMs		
	}
}

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

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


// сгенерировать новый случайный номер 
static void generateLicenseplate (char * text, int length) {
	if (length < 4)
		return;
	text[0] = (char)(rand()/32768.0*23.0)+'A'; 
	text[1] = (char)(rand()/32768.0*23.0)+'A'; 
	text[2] = (char)(rand()/32768.0*9.0)+'0'; 
	text[3] = (char)(rand()/32768.0*9.0)+'0';

	int i;
	for (i = 4; length-i > 4; i += 4) {
		text[i  ] = ' ';
		text[i+1] = (char)(rand()/32768.0*9.0)+'0'; 
		text[i+2] = (char)(rand()/32768.0*23.0)+'A'; 
		text[i+3] = (char)(rand()/32768.0*23.0)+'A'; 
	}
	text[i] = '\0';	// нужно ли и для этого проверить длину?
}

void CModelCars::prepareLicenseplate () {
	licenseplateMaterial = NULL;
	licenseplateText[0] = 0;
	_f31 = 0xFF;

	char text[12];
	strcpy_s (text, 12, "DEFAULT");
	generateLicenseplate (text, 9);
	licenseplateMaterial = generateLicenseplateMaterial ((CClump *)rwObject, text, _f31);
}

void CModelCars::preprocessHierarchy () {
	_f4C = 0;

	CClump * c = (CClump *)rwObject;	// convert type
	CAtomic * ca = NULL;
	CAtomic * _v40 = NULL;
	CFrame * cf;

	for (ModelComponent * comp = carComponentsByType[type]; comp->name; comp++) {	// for each component 
		if (comp->flags & 0x20208) {
			cf = c->parent->findFrameFromNameWithoutId (comp->name);
			if (cf) {
				if (comp->flags & 8) {	
					D3DVECTOR * point = vehicleStruct->_f00+comp->hierId;
					*point = cf->modelling.pos;
					for (CFrame * pcf = cf->parent;	pcf && pcf->parent; pcf = pcf->parent)
						RwTransformPoints (point, point, 1, &pcf->modelling);
					delete cf;
				} else if (comp->flags & 0x20000) {	// _4C8F7B	
					vehicleStruct->_fB4[comp->hierId]._f0 = cf->modelling.pos;
					vehicleStruct->_fB4[comp->hierId].q.get (&cf->modelling);
					vehicleStruct->_fB4[comp->hierId]._f1C = cf->parent->hierarchyId;	// CVisibilityPlugins::GetFrameHierarchyId
				} else {	// _4C8FF6
					CAtomic * a = (CAtomic *)cf->getFirstObject ();
					c->removeAtomic (a);
					cf->removeFromParent ();
					setVehicleComponentFlags  (cf, comp->flags);
					vehicleStruct->atomics[vehicleStruct->atomicCount++] = a;
				}
			}
		}
		// _4C9042
		if (comp->flags & 0x110000) {	// clonable component
			cf = c->parent->findFrameFromId (comp->hierId);
			while (cf) {	// TODO: convert to for				
				CAtomic * o = (CAtomic *)cf->getFirstObject ();				
				if (o) {
					if (comp->flags & 0x10000)	// wheel ?
						ca = o;
					else
						_v40 = o;
					break;
				}								
				cf = cf->child;
			}
		}
		// _4C90C7
	}
	
	
	for (ModelComponent * comp = carComponentsByType[type]; comp->name; comp++) {	
		if (comp->flags & 0x20208) 
			continue;

		cf = c->parent->findFrameFromId (comp->hierId);
		if (!cf)
			continue;

		if (comp->flags & 0x10)
			_f4C++;
		
		if (comp->flags & 2) {
			cf->flattenHierarchy (cf);
			cf->updateObject ();

			CAtomic * dst = NULL;
			CAtomic * src = NULL;
			for (RwLLLink * l = cf->objectList.next; l != &(cf->objectList); l = l->next) {	// inlined: 4C7BD0 (TODO: вынести в CFrame ?)
				CAtomic * a = (CAtomic *)(((BYTE *)l)-offsetof (RwObjectHasFrame, lFrame));				
				if (a->visibilityData2 & 1)
					src = a;
				else if (a->visibilityData2 & 2) 
					dst = a;
			}			
			if (src && dst) {
				dst->renderCallBack = src->renderCallBack ? src->renderCallBack : AtomicDefaultRenderCallBack;
				vehicleStruct->_f310 |= 1 << comp->hierId;
			}
		}
		
		setVehicleComponentFlags  (cf, comp->flags);
		
		if (comp->flags & 0x10004) {
			if (!ca)
				continue;
			if (!(comp->flags & 0x10000)) {
				CAtomic * a = new CAtomic (ca);
				a->atomicSetFrame (cf);
				c->addAtomic (a);
				a->renderCallBack = AtomicDefaultRenderCallBack;
				if (comp->hierId == 2 || comp->hierId == 5 || !(handling->vehicleHandling[handlingIndex].modelFlags & 0x20000000))
					continue;

				a = new CAtomic (ca);
				CFrame * f = new CFrame ();
				a->atomicSetFrame (f);
				cf->addChild (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;
				c->addAtomic (a);
				a->renderCallBack = AtomicDefaultRenderCallBack;
			} else {				
				cf->flattenHierarchy (cf);
				cf->updateObject ();
				ca->renderCallBack = AtomicDefaultRenderCallBack;								
			}
		} else if ((comp->flags & 0x200000) && _v40) {	// 4C92F9
			CAtomic * a = new CAtomic (_v40);
			a->atomicSetFrame (cf);
			c->addAtomic (a);
			a->renderCallBack = AtomicDefaultRenderCallBack;
		}				
	}	
}

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

	if (flags & 0x1000) 
		f->setAtomicVisibilityFlags (0x80);		
	if (flags & 0x400000) 
		f->setAtomicVisibilityFlags (0x400);
	if (flags & 0x40000) 
		f->setAtomicVisibilityFlags (0x2000);

	WORD 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) 
		f->setAtomicVisibilityFlags (fl);

	if ((flags & 0x8000) && ((mf & 0x80000000) || (flags & 0x6000)))		
		f->setAtomicVisibilityFlags (0x8000);

	if (flags & 0x2000)
		f->setAtomicVisibilityFlags (0x100);
	else if (flags & 0x4000)
		f->setAtomicVisibilityFlags (0x200);
	
	if (flags & 0x400) 
		f->setAtomicVisibilityFlags (0x40);
}

//=============================================================================
// Model pools
template <class T, int SIZE> struct ModelStoreBase {
	ModelStoreBase () : count (0) {}
	T * allocate (int objectID) {
		objects[count].init ();
		modelPtrs[objectID] = objects+count;
		count++;
		return objects+count-1;
	}
	
	int count;
	T	objects[SIZE];
};

static struct ModelObjsStore : public ModelStoreBase<CModelObjs, 14000> {} modelObjsStore;
static struct ModelObjs2cStore : public ModelStoreBase<CModelObjs2c, 70> {} modelObjs2cStore;
static struct ModelTobjStore : public ModelStoreBase<CModelTobj, 169> {} modelTobjStore;
static struct ModelHierStore : public ModelStoreBase<CModelHier, 92> {} modelHierStore;
static struct ModelWeapStore : public ModelStoreBase<CModelWeap, 51> {} modelWeapStore;
static struct ModelPedsStore : public ModelStoreBase<CModelPeds, 278> {} modelPedsStore;
static struct ModelCarsStore : public ModelStoreBase<CModelCars, 212> {} modelCarsStore;

CModelObjs * CModelObjs::allocate (int objID) { return modelObjsStore.allocate (objID); }
CModelObjs2c * CModelObjs2c::allocate(int objID) { return modelObjs2cStore.allocate (objID); }
CModelTobj * CModelTobj::allocate(int objID) { return modelTobjStore.allocate (objID); }
CModelHier * CModelHier::allocate(int objID) { return modelHierStore.allocate (objID); }
CModelWeap * CModelWeap::allocate(int objID) { return modelWeapStore.allocate (objID); }
CModelPeds * CModelPeds::allocate(int objID) { return modelPedsStore.allocate (objID); }
CModelCars * CModelCars::allocate(int objID) { return modelCarsStore.allocate (objID); }
//=============================================================================
// helpers
int * lastModelFound = (int *)0xAAE948;	// TODO: используется еще в readCutsFile

CModelBase * getModelIndexByName (char * name, int * pIndex) {
	DWORD hash = crc32FromUpcaseString ((BYTE *)name);
	short 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;

	/// debug ("getModelIndexByName: %s = %d, hash = %08x\n", name, i);
	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;
}
//=============================================================================
// ### animations ###
void CAnimBlendSequence::setNumFrames (int num, BYTE _a4, BYTE _a8, void * frameData) {
	int size;
	if (_a4) {
		flags |= 3;
		size = _a8 ? num << 4 : num << 5;
	} else {
		flags |= 1;
		size = _a8 ? num * 10 : num * 20;
	}
	frames = frameData ? frameData : malloc (size);
	frameCount = num;
	if (_a8)
		flags |= 4;
}

CIfpFile * findIfpFileByName (char * name) {	// >> 4D3940
	for (int i = 0; i < *ifpFilesAllocated; i++)
		if (!stricmp (ifpFiles[i].name, name))
			return ifpFiles+i;
	return NULL;
}

int findAnimationStyleByBaseName (char * baseName) {	// >> 4D39B0 (3)
	for (int i = 0; i < 118; i++) // TODO: ANIM_STYLE_COUNT
		if (!stricmp (animationStyles[i].baseName, baseName))
			return i;
	return 118; // TODO: ANIM_STYLE_COUNT
}

void (__thiscall * animBlendSequence__setNumFrames)(CAnimBlendSequence * _this, int numFrames, int _a4, int _a8, int _aC) = 
(void (__thiscall *)(CAnimBlendSequence *, int, int, int, int))0x4D0CD0;
// TODO: функция не доделана!
// TODO: переделать на использование mmap (CRwMemoryStream)
// TODO: проверить все! Есть как минимум две ошибки
void readIfpFile (CRwStream * stream, BYTE _a4, char * animName) {	// >> 4D47F0 (4)
	struct IfpHeader {
		DWORD	fourcc;	// 'ANPK', 'ANP2', 'ANP3'
		DWORD	length;
	} fileHeader;

	BYTE buffer[256];	
	CIfpFile * file;
	int fileIndex;
	int lastAnimation = 0;

	stream->read (&fileHeader, sizeof (IfpHeader));
	if (fileHeader.fourcc == 0x33504E41 || fileHeader.fourcc == 0x32504E41) {	// 'ANP3'/'ANP2'
		int _v231 = fileHeader.fourcc == 0x33504E41 ? 1 : 0;	// is ANP3

		stream->read (buffer, 24);
		int animCount;
		stream->read (&animCount, 4);

		file = findIfpFileByName ((char *)buffer);
		if (!file) {
			file = ifpFiles+*ifpFilesAllocated;			
			(*ifpFilesAllocated)++;
			memcpy (file->name, buffer, 16);
			file->animationCount = animCount;
			file->startAnimation = *animationsLoaded;
			file->animationStyle = findAnimationStyleByBaseName (file->name);					
		} else if (file->animationCount == 0) {
			file->animationCount = animCount;
			file->startAnimation = *animationsLoaded;
		}

		fileIndex = file-ifpFiles;
		file->loaded = 1;
		
		lastAnimation = file->startAnimation;
		CIfpAnimations * anim = ifpAnimations+file->startAnimation;

		for (int i = 0; i < file->animationCount; i++) {
			lastAnimation++;
			CIfpAnimations * currentAnim = anim++;
			
			stream->read (buffer, 24);
			int seqCount;
			stream->read (&seqCount, 4);

			int _v1F0, _v1EC;
			BYTE * _v218;
			if (_v231) {	// два дополнительных поля для ANP3
				stream->read (&_v1F0, 4);	// data length
				stream->read (&_v1EC, 4);
				currentAnim->_fA = (BYTE)(_v1EC & 1);
				_v218 = (BYTE *)malloc (_v1F0);
			}
			currentAnim->hash = crc32FromUpcaseString (buffer);
			currentAnim->seqCount = seqCount;
			currentAnim->ifpFileIndex = fileIndex;
			currentAnim->_fB = 0;
			currentAnim->objects = new CAnimBlendSequence[seqCount];

			int _v20C, frameCount, frameType;
			for (int j = 0; j < currentAnim->seqCount; j++) {
				stream->read (buffer, 24);	// anim name
				stream->read (&_v20C, 4);	// 
				stream->read (&frameCount, 4);
				stream->read (&frameType, 4);

				CAnimBlendSequence * seq = currentAnim->objects+j;
				seq->hash = crc32FromUpcaseString (buffer);
				seq->frameCount = frameCount;
				if (frameType != -1) {
					seq->flags |= 4;
					seq->hash = (seq->hash & 0xFFFF0000)|(frameType & 0xFFFFF);
				}

				// ..................
			}
		}
	} else {	// 'ANPK'
		struct IfpChunkHeader {
			DWORD	fourcc;
			DWORD	length;
		} chunkHeader;

		fileHeader.length += 3;	// выравнивание
		fileHeader.length &= ~3;

		stream->read (&chunkHeader, sizeof (IfpChunkHeader));
		chunkHeader.length += 3;
		chunkHeader.length &= ~3;
		stream->read (buffer, chunkHeader.length);

		file = findIfpFileByName ((char *)buffer+4);
		if (!file) {	// allocate new file
			file = ifpFiles+*ifpFilesAllocated;			
			(*ifpFilesAllocated)++;
			memcpy (file->name, buffer+4, 16);
			file->animationCount = *(int *)buffer;
			file->startAnimation = *animationsLoaded;
			file->animationStyle = findAnimationStyleByBaseName (file->name);			
		} else if (file->animationCount == 0) {
			file->animationCount = *(int *)buffer;
			file->startAnimation = *animationsLoaded;
		}
		fileIndex = file-ifpFiles;
		file->loaded = 1;
		lastAnimation = file->startAnimation;
		CIfpAnimations * anim = ifpAnimations+file->startAnimation;

		for (int i = 0; i < file->animationCount; i++) {
			stream->read (&chunkHeader, sizeof (IfpChunkHeader));	// 'NAME'
			chunkHeader.length += 3;
			chunkHeader.length &= ~3;
			stream->read (buffer, chunkHeader.length);	// TODO: check length
			
			lastAnimation++;
			CIfpAnimations * currentAnim = anim++;
			currentAnim->hash = crc32FromUpcaseString (buffer);
			currentAnim->ifpFileIndex = fileIndex;

			// NOTE: предположительно, это проверка на то, что эта анимация какая-то стандартная (используется для катсцен)
			BYTE flag = _a4;			
			if (animName) {	// если передан список имен
				char * namePtr = animName;				
				while (*namePtr) {
					if (currentAnim->hash == crc32FromUpcaseString ((BYTE *)namePtr))
						flag = 0;
					namePtr += 32;
				}
			}
	
			currentAnim->_fA = flag;
			currentAnim->_fB = 0;

			int readBytes;	// количество прочитанных байт в данном фрагменте

			IfpChunkHeader dganHeader;
			readBytes = stream->read (&dganHeader, sizeof (IfpChunkHeader));	// 'DGAN'
			dganHeader.length += 3;
			dganHeader.length &= ~3;
						
			readBytes += stream->read (&chunkHeader, 8);	// 'INFO'
			chunkHeader.length += 3;
			chunkHeader.length &= ~3;
			stream->read (buffer, chunkHeader.length);	// TODO: check length
			
			currentAnim->seqCount = *(WORD *)buffer;
			currentAnim->objects = new CAnimBlendSequence[currentAnim->seqCount];

			for (int j = 0; j < currentAnim->seqCount; j++) {
				readBytes += stream->read (&chunkHeader, 8);	// 'CPAN' - empty chunk
				readBytes += stream->read (&chunkHeader, 8);	// 'ANIM'
				chunkHeader.length += 3;
				chunkHeader.length &= ~3;
				readBytes += stream->read (buffer, chunkHeader.length);	// TODO: check length

				CAnimBlendSequence * seq = currentAnim->objects+j;
				seq->hash = crc32FromUpcaseString (buffer);
				if (chunkHeader.length == 0x2C && *(DWORD *)(buffer+0x28) != 0xFFFFFFFF) {	// TODO: сделать структуру для этого чанка
					seq->flags |= 0x10;
					seq->hash = (seq->hash & 0xFF00)|*(WORD *)(buffer+0x28);
				}

				if (*(DWORD *)(buffer+0x1C) == 0)	// TODO: аналогично, структура
					continue;

				int _v231 = 0;
				int _v229 = 0;

				readBytes += stream->read (&chunkHeader, 8);	
				int frameCount = *(int *)(buffer + 0x1C);
				// NOTE: flag задает размер структуры. 0 он может быть только при чтении катсцены, если имя анимации найдено в перенном массиве
				// NOTE: рабочее предположение, что flag переключает точность данных между short и float
				// второй аргумент задает тип структуры: 0 - 10/20 байт, 1 - 16/32 байта
				if (chunkHeader.fourcc == 0x5354524B) {	// 'KRTS'
					_v231 = 1;
					seq->setNumFrames (frameCount, 1, flag, NULL);					
				} else if (chunkHeader.fourcc == 0x3054524B) {	// 'KRT0'
					_v229 = 1;
					seq->setNumFrames (frameCount, 1, flag, NULL);										
				} else if (chunkHeader.fourcc == 0x3030524B) 	// 'KR00'
					seq->setNumFrames (frameCount, 0, flag, NULL);

				for (int k = 0; k < seq->frameCount; k++) {
					if (_v231) {	// KRTS
						FrameDataKRTS	frame;
						readBytes += stream->read (&frame, sizeof (FrameDataKRTS));
						
						if (flag) {
							SAnimFrame2	* f = (SAnimFrame2 *)((BYTE *)(seq->frames)+k*sizeof (SAnimFrame2));	// 16 bytes
							f->_f0 = (short)(-frame._f0*4096.0f);
							f->_f2 = (short)(-frame._f4*4096.0f);
							f->_f4 = (short)(-frame._f8*4096.0f);
							f->_f6 = (short)(frame._fC*4096.0f);
							f->_f8 = (short)(frame._f28*60.0f+0.5f);
							f->_fA = (short)(frame._f10*1024.0f);
							f->_fC = (short)(frame._f14*1024.0f);
							f->_fE = (short)(frame._f18*1024.0f);
						} else {
							SAnimFrame4	* f = (SAnimFrame4 *)((BYTE *)(seq->frames)+k*sizeof (SAnimFrame4));	// 32 bytes
							f->_f0 = -frame._f0;
							f->_f4 = -frame._f4;
							f->_f8 = -frame._f8;
							f->_fC = frame._fC;
							f->_f10 = frame._f28;
							f->_f14 = frame._f10;
							f->_f18 = frame._f14;
							f->_f1C = frame._f18;
						}
					} else if (_v229) {	// KRT0
						FrameDataKRT0	frame;
						readBytes += stream->read (&frame, sizeof (FrameDataKRT0));

						if (flag) {
							SAnimFrame2	* f = (SAnimFrame2 *)((BYTE *)(seq->frames)+k*sizeof (SAnimFrame2));	// 16 bytes
							f->_f0 = (short)(-frame._f0*4096.0f);
							f->_f2 = (short)(-frame._f4*4096.0f);
							f->_f4 = (short)(-frame._f8*4096.0f);
							f->_f6 = (short)(frame._fC*4096.0f);
							f->_f8 = (short)(frame._f1C*60.0f+0.5f);
							f->_fA = (short)(frame._f10*1024.0f);
							f->_fC = (short)(frame._f14*1024.0f);
							f->_fE = (short)(frame._f18*1024.0f);
						} else {
							SAnimFrame4	* f = (SAnimFrame4 *)((BYTE *)(seq->frames)+k*sizeof (SAnimFrame4));	// 32 bytes
							f->_f0 = -frame._f0;
							f->_f4 = -frame._f4;
							f->_f8 = -frame._f8;
							f->_fC = frame._fC;
							f->_f10 = frame._f1C;
							f->_f14 = frame._f10;
							f->_f18 = frame._f14;
							f->_f1C = frame._f18;
						}
					} else {	// KR00
						FrameDataKR00	frame;
						readBytes += stream->read (&frame, sizeof (FrameDataKR00));
						
						if (flag) {
							SAnimFrame1 * f = (SAnimFrame1 *)((BYTE *)(seq->frames)+k*sizeof (SAnimFrame1));	// 10 bytes

							f->_f0 = (short)(-frame._f0*4096.0f);
							f->_f2 = (short)(-frame._f4*4096.0f);
							f->_f4 = (short)(-frame._f8*4096.0f);
							f->_f6 = (short)(frame._fC*4096.0f);
							f->_f8 = (short)(frame._f10*60.0f+0.5f);
						} else {
							SAnimFrame3 * f = (SAnimFrame3 *)((BYTE *)(seq->frames)+k*sizeof (SAnimFrame1));	// 20 bytes
							f->_f0 = -frame._f0;
							f->_f4 = -frame._f4;
							f->_f8 = -frame._f8;
							f->_fC = frame._fC;
							f->_f10 = frame._f10;
						}
					}
				}
			}
		}
	}

	if (lastAnimation > *animationsLoaded)
		*animationsLoaded = lastAnimation;
}