// ### OpenGTA3 ###
// RenderWare structures

#include "stdafx.h"
#include <stddef.h>
#include <stdio.h>
#include "ogMath.h"
#include "ogMemory.h"
#include "ogRW.h"
#include "ogTextures.h"
#include "ogModels.h"

#include "gtaInterface.h"

CAtomic * (*_5DA610)(CAtomic *) = (CAtomic * (__cdecl *)(CAtomic *))0x5DA610;
void (*_5D7F00)(CAtomic *) = (void (__cdecl *)(CAtomic *))0x5D7F00;

//=============================================================================
// RwObject/RwObjectHasFrame
void RwObjectHasFrame::setFrame(CFrame *frame) {
	releaseFrame ();
	parent = frame;
	if (frame) {	// add object to list		
		lFrame.insertAfter (&frame->objectList);
		frame->updateObject ();
	}
}

void RwObjectHasFrame::releaseFrame() {
	if (parent) {	// remove from object chain in the parent frame
		lFrame.prev->next = lFrame.next;
		lFrame.next->prev = lFrame.prev;
	}
}
//=============================================================================
// CAtomic
static RwObjectHasFrame * atomicSync(RwObjectHasFrame *object) {
	CAtomic * a = (CAtomic *)object;
	if (a->interpolator.flags & 2)
		_rtAtomicResyncInterpolatedSphere (a);
	a->privateFlags |= 1;
	return object;
}

static RwObjectHasFrame * _worldAtomicSync (RwObjectHasFrame * object) {
	CAtomic * atomic = (CAtomic *)object;

	if (!atomic->oldSync (object))
		return NULL;
	if (!atomic->world)
		return object;
		
	for (RwLLLink * cur = atomic->llWorldSectorsInAtomic.next; cur != &(atomic->llWorldSectorsInAtomic); cur = cur->next) {
		RpTie * tie = (RpTie *)((BYTE *)cur-12);	// offset of RpTie::lWorldSectorInAtomic
		if (!tie->atomic)
			continue;
		if (!tie->worldSector)
			continue;
		tie->lWorldSectorInAtomic.prev->next = tie->lWorldSectorInAtomic.next;
		tie->lWorldSectorInAtomic.next->prev = tie->lWorldSectorInAtomic.prev;
		tie->lAtomicInWorldSector.prev->next = tie->lAtomicInWorldSector.next;
		tie->lAtomicInWorldSector.next->prev = tie->lAtomicInWorldSector.prev;
		__tieAllocator->freeEntry ((BYTE *)tie);		
	}
	RwBBox bbox (RpAtomicGetWorldBoundingSphere (atomic));
	RpSector * sector = atomic->world->rootSector;
	
	int stackSize = 0;
	RpSector * sectorStack[64];
	do {
		if (sector->type >= 0) {	// plane sector ?
			RpPlaneSector * ps = (RpPlaneSector *)sector;
			// в зависимости от типа, берем минимум и максимум по соответствущей координате
			float left = *(((float *)&bbox.inf)+sector->type);
			float right = *(((float *)&bbox.sup)+sector->type);
	
			// проверяем, попадает ли атомик в этот сектор
			// TODO: проверить на </<=, >/>=
			if (left < ps->leftValue) {
				sector = ps->leftSubTree;
				if (right >= ps->rightValue)
					sectorStack[++stackSize] = ps->rightSubTree;				
			} else 
				sector = right > ps->rightValue ? ps->rightSubTree : sectorStack[stackSize--];			
		} else {
			RpWorldSector * ws = (RpWorldSector *)sector;
			RpTie * tie = (RpTie *)__tieAllocator->allocateEntry ();
			tie->atomic = atomic;
			tie->worldSector = ws;

			tie->lAtomicInWorldSector.prev = &(ws->collAtomicsInWorldSector);
			tie->lAtomicInWorldSector.next = ws->collAtomicsInWorldSector.next;
			ws->collAtomicsInWorldSector.next->prev = &(tie->lAtomicInWorldSector);
			ws->collAtomicsInWorldSector.next = &(tie->lAtomicInWorldSector);

			tie->lWorldSectorInAtomic.prev = &(atomic->llWorldSectorsInAtomic);
			tie->lWorldSectorInAtomic.next = atomic->llWorldSectorsInAtomic.next;
			atomic->llWorldSectorsInAtomic.next->prev = &(tie->lWorldSectorInAtomic);
			atomic->llWorldSectorsInAtomic.next = &(tie->lWorldSectorInAtomic);
			
			sector = sectorStack[stackSize--];
		}
	} while (stackSize >= 0);
	

	return object;
}

void * CAtomic::operator new (size_t) {
	return __atomicAllocator->allocateEntry ();
}

void CAtomic::operator delete (void * ptr) {
	__atomicAllocator->freeEntry ((BYTE *)ptr);
}

CAtomic::CAtomic () {	// << RpAtomicCreate >> 749C50 (6)	
	// debug ("CAtomic constructor\n");

	type = 1;		// ATOMIC
	subType = 0;
	parent = NULL;
	// ** overriden in worldAtomic plugin: sync = atomicSync;

	repEntry = NULL;
	flags = 5;
	privateFlags = 1;
	geometry = NULL;

	setFrame (NULL);

	boundingSphere.radius = boundingSphere.center.x = boundingSphere.center.y = boundingSphere.center.z = 0.0f;
	worldBoundingSphere.radius = worldBoundingSphere.center.x = worldBoundingSphere.center.y = worldBoundingSphere.center.z = 0.0f;
	renderCallBack = atomicDefaultRenderCallBack;

	interpolator.time = 1.0f;
	interpolator.recipTime = 1.0f;
	interpolator.startMorphTarget = 0;
	interpolator.endMorphTarget = 0;
	interpolator.position = 0.0f;
	interpolator.flags = 3;

	inClumpLink.next = inClumpLink.prev = NULL;
	llWorldSectorsInAtomic.next = llWorldSectorsInAtomic.prev = &llWorldSectorsInAtomic;

	clump = NULL;
	pipeline = NULL;

	// world atomic init
	world = NULL;
	(*engine)->renderFrame--;
	sync = worldAtomicSync;
	oldSync = atomicSync;

	skinData = NULL;		// atomic skin init
	
	visibilityData0 = -1;	// visibility init
	visibilityData2 = 0;
	
	matFXdata = NULL;		// material FX init
	reflectionData = NULL;	// reflection init
	pl2F3data = NULL;		// 253F2F3 init
}

CAtomic::CAtomic (CAtomic * a) {	// << RpAtomicClone >> 749E60 (18)
	type = a->type;		
	subType = a->subType;
	flags = a->flags;
	privateFlags = a->privateFlags | 1;
	parent = NULL;
	setFrame (NULL);

	repEntry = NULL;

	worldBoundingSphere.radius = worldBoundingSphere.center.x = worldBoundingSphere.center.y = worldBoundingSphere.center.z = 0.0f;
	renderCallBack = atomicDefaultRenderCallBack;

	inClumpLink.next = inClumpLink.prev = NULL;
	llWorldSectorsInAtomic.next = llWorldSectorsInAtomic.prev = &llWorldSectorsInAtomic;

	clump = NULL;

	// clone geometry
	// NOTE: в оригинале, здесь была RpAtomicSetGeometry. Поскольку у этого атомика еще не определен parent, конструкция упрощается на порядок
	geometry = a->geometry;
	if (geometry)
		geometry->addRef ();

	boundingSphere = a->boundingSphere;
	interpolator = a->interpolator;
	renderCallBack = a->renderCallBack;
	pipeline = a->pipeline;

	// plugin copy
	// world atomic - no copy, just init
	world = NULL;
	(*engine)->renderFrame--;
	sync = worldAtomicSync;
	oldSync = atomicSync;

	skinData = a->skinData;	// atomic skin copy
	visibilityData0 = a->visibilityData0;	// visibility copy
	visibilityData2 = a->visibilityData2;	
	matFXdata = a->matFXdata ? 1 : 0;	// meterial FX copy

	// reflection copy
	if (a->reflectionData) {
		(*envMapPipeAtmDataPool)->objectSize = 12;	// TODO: убрать после того, как этот пул будет создан самостоятельно
		reflectionData = (*envMapPipeAtmDataPool)->allocate ();
		if (reflectionData)
			memcpy (reflectionData, a->reflectionData, 12);	// copy reflection data
			// memcpy (reflectionData, a->reflectionData, 4);	// copy reflection data
	} else
		reflectionData = NULL;

	pl2F3data = a->pl2F3data;	// 253F2F3 copy
}

CAtomic::~CAtomic() { // << RpAtomicDestroy >> 749DC0 (26); << ClumpTidyDestroyAtomic >> 7496A0 (1)	
	// deinit plugins
	// 253F2F3 - no destructor	
	if (reflectionData)	// reflection data deinit
		(*envMapPipeAtmDataPool)->release ((BYTE *)reflectionData);	// TODO: delete reflectionData;	
	matFXdata = NULL;	// material FX deinit
	// visibility plugin - no destructor
	skinData = NULL;	// atomic skin deinit 

	// deinit world atomic
	// << WorldDeInitAtomicExt >> 7509B0
	RwLLLink * next = llWorldSectorsInAtomic.next;
	for (RwLLLink * cur = next; cur != &llWorldSectorsInAtomic; cur = next) {
		next = cur->next;
		RpTie * tie = (RpTie *)((BYTE *)cur-12);	// offset of RpTie::lWorldSectorInAtomic
		if (!(tie->atomic))
			continue;
		if (!(tie->worldSector))
			continue;
		tie->lWorldSectorInAtomic.prev->next = tie->lWorldSectorInAtomic.next;
		tie->lWorldSectorInAtomic.next->prev = tie->lWorldSectorInAtomic.prev;
		tie->lAtomicInWorldSector.prev->next = tie->lAtomicInWorldSector.next;
		tie->lAtomicInWorldSector.next->prev = tie->lAtomicInWorldSector.prev;
		__tieAllocator->freeEntry ((BYTE *)tie);		
	}

	// deinit atomic itself
	if (repEntry)
		RwResourcesFreeResEntry (repEntry);	// TODO: 

	if (geometry) {
		RpGeometryDestroy ((RpGeometry *)geometry);	// TODO: delete geometry;
		if (parent && world)			
			parent->updateObject ();		
	}
	releaseFrame ();
}

bool CAtomic::haveNightColors () {
	if (pl2F3data == 0x53F2009C || pl2F3data == 0x53F20098)
		return true;
	if (geometry->nightColors0 && geometry->preLitLum)
		return true;
	return false;
}

void CAtomic::setupLighting() {
	if (haveNightColors ()) 
		_5D7F00 (this);
	else if (pl2F3data == 0x53F2009A) 
		_5DA610 (this);
}

void CAtomic::setHAnimHierarchy(RpHAnimHierarchy * hh) {
	skinData = hh ? hh : parent->getHAnimHierarchy ();
}
//=============================================================================
//	CGeometry
void CGeometry::setAlphaForAllMaterials(BYTE alpha) {
	for (int i = 0; i < matList.numMaterials; i++)
		matList.materials[i]->color.a = alpha;
}

//=============================================================================
// CClump
static CClump * clumpCallBack (CClump * clump, void * data) {	// >> 74A260 (3)
	return clump;
}

static bool defaultClumpVisibilityCB (void * data) {	// >> 732E10
	return true;
}

void * CClump::operator new (size_t) {
	return __clumpAllocator->allocateEntry ();
}

void CClump::operator delete (void * ptr) {
	__clumpAllocator->freeEntry ((BYTE *)ptr);
}

CClump::CClump() {
	// init object
	type = 2;	// CLUMP
	subType = 0;
	flags = 0;
	privateFlags = 0;
	parent = NULL;
	
	// init clump itself
	atomicList.next = atomicList.prev = &atomicList;
	lightList.next = lightList.prev = &lightList;
	cameraList.next = cameraList.prev = &cameraList;
	inWorldLink.next = inWorldLink.prev = NULL;
	callback = clumpCallBack;

	// init plugins
	// 0x509 - World Clump
	world = NULL;
	pl0509_04 = ((CWorldObject *)(((BYTE *)*engine)+*worldObjOffset))->_f08;	// TODO: упростить конструкцию, после того, как будет полностью описан CEngine
	// 0x253F201 - Clump Visibility
	visibilityCB = defaultClumpVisibilityCB;
	visibilityDistance = 255;
	// 0x253F2FB - GtaHAnim
	plGtaHAnim = NULL;
	// 0x253F2FA - no constructor
}

CClump::CClump(CClump * src) {
	type = 2;	// CLUMP
	subType = 0;
	flags = 0;
	privateFlags = 0;
	parent = rwFrameCloneAndLinkClones (src->parent);
	if (!parent)
		return;	// TODO: throw

	// clone all atomics
	atomicList.prev = atomicList.next = &atomicList;
	for (RwLLLink * l = src->atomicList.prev; l != &(src->atomicList); l = l->prev) {	// по prev, чтобы атомики добавились в том же порядке
		CAtomic * as = (CAtomic *)(((BYTE *)l)-offsetof(CAtomic, inClumpLink));	
		CAtomic * a = new CAtomic (as);
		if (!a)
			return;	// TODO: throw
		a->setFrame (as->parent->root);
		a->privateFlags |= 1;
		addAtomic (a);	
	}
	rwFramePurgeClone (src->parent);
	
	lightList.next = lightList.prev = &lightList;
	cameraList.next = cameraList.prev = &cameraList;
	inWorldLink.next = inWorldLink.prev = NULL;
	callback = src->callback;

	// copy plugin data
	// world clump
	world = NULL;
	pl0509_04 = ((CWorldObject *)(((BYTE *)*engine)+*worldObjOffset))->_f08;	// TODO: упростить конструкцию, после того, как будет полностью описан CEngine
	if (src->world)
		RpWorldAddClump (src->world, this);
	// visibility
	visibilityCB = src->visibilityCB;
	visibilityDistance = 255;

	plGtaHAnim = NULL;
}	

CClump::~CClump() {
	// deinit plugins
	// 0x253F2FA - Collision - no destructor
	// 0x253F2FB - GtaHAnim
	gtaHAnimDestruct (this);	// TODO: inline it	
	// 0x253F201 - Clump Visibility - no destructor
	// 0x509 - World Clump - no destructor
	
	// deinit clump itself
	// destroy all atomics
	RwLLLink * next = atomicList.next;
	for (RwLLLink * cur = next; cur != &atomicList; cur = next) {
		next = cur->next;
		delete (CAtomic *)(((BYTE *)cur)-offsetof(CAtomic, inClumpLink));	
	}
	// destroy all lights
	for (RwLLLink * cur = next; cur != &atomicList; cur = next) {
		next = cur->next;
		CLight * light = (CLight *)(((BYTE *)cur)-offsetof(CLight, inClumpLink));
		
		// TODO: эти три строки должны быть в деструкторе CLight
		light->inClumpLink.remove ();
		light->inClumpLink.next = light->inClumpLink.prev = NULL;
		light->clump = NULL;
		
		RpLightDestroy (light);	// TODO: delete		
	}

	// destroy all cameras
	for (RwLLLink * cur = next; cur != &atomicList; cur = next) {
		next = cur->next;		
		CRwCamera * camera = (CRwCamera *)(((BYTE *)cur)-offsetof(CRwCamera, inClumpLink));

		// TODO: эти три строки должны быть в деструкторе CRwCamera
		camera->inClumpLink.remove ();
		camera->inClumpLink.next = camera->inClumpLink.prev = NULL;
		camera->clump = NULL;
		
		RwCameraDestroy (camera);
	}
	
	RwFrameDestroyHierarchy (parent);	// TODO: parent->destroyHierarchy ();
}

void CClump::addAtomic (CAtomic * atomic) {	// >> 74A490 (20)
	atomic->inClumpLink.insertAfter (&atomicList);
	atomic->clump = this;
}

void CClump::removeAtomic (CAtomic * atomic) { // >> 74A4C0 (7)
	atomic->inClumpLink.prev->next = atomic->inClumpLink.next;
	atomic->inClumpLink.next->prev = atomic->inClumpLink.prev;
	atomic->clump = NULL;
}

CAtomic * CClump::findFirstAtomicWith2dfx() {
	for (RwLLLink * l = atomicList.next; l != &atomicList; l = l->next) {
		CAtomic * a = (CAtomic *)(((BYTE *)l)-offsetof(CAtomic, inClumpLink));
		DWORD * p2dfx = a->geometry->pl2dfx;
		if (p2dfx && *p2dfx > 0)
			return a;
	}
	return NULL;
}

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

CAtomic * CClump::getFirstAtomic() {
	return atomicList.next == &atomicList ? NULL : (CAtomic *)(((BYTE *)(atomicList.next))-offsetof(CAtomic, inClumpLink));
}

RpHAnimHierarchy * CClump::getHAnimHierarchy () {
	return parent->getHAnimHierarchy ();
}
// установить hanim hierarchy: если передано нулевое значение, то для всех атомиков клампа устанавливается значение, взятое из фрэйма этого атомика
void CClump::atomicsSetHAnimHierarchy(RpHAnimHierarchy *hh) {
	if (hh) {
		if (atomicList.next != &atomicList)	{// проверка на то, что есть хотя бы один атомик
			CAtomic * a = (CAtomic *)(((BYTE *)(atomicList.next))-offsetof(CAtomic, inClumpLink));
			a->setHAnimHierarchy (hh);
		}
	} else 
		for (RwLLLink * l = atomicList.next; l != &atomicList; l = l->next) {
			CAtomic * a = (CAtomic *)(((BYTE *)l)-offsetof(CAtomic, inClumpLink));
			a->setHAnimHierarchy (hh);
		}
}
// назначить указанный render callback всем атомикам клампа
void CClump::atomicsSetRenderrCB (CAtomicCallBackRender	cb) {
	for (RwLLLink * l = atomicList.next; l != &atomicList; l = l->next) {
		CAtomic * a = (CAtomic *)(((BYTE *)l)-offsetof(CAtomic, inClumpLink));
		a->renderCallBack	= cb;
	}
}

void CClump::forAllAtomics(CAtomicCallback cb, void * data) {
	RwLLLink * next = atomicList.next;
	for (RwLLLink * cur = next; cur != &atomicList; cur = next) {
		next = cur->next;		
		cb ((CAtomic *)(((BYTE *)cur)-offsetof(CAtomic, inClumpLink)), data);
	}

}
//=============================================================================
// CLight

static RwObjectHasFrame * lightSync (RwObjectHasFrame * obj) {	// >> 7521A0
	return obj;
}

CLight::CLight(RpLightType lightType) {	// >> 752110 (5)
	type = 3;	// LIGHT
	subType = (BYTE)type;
	flags = 3;
	privateFlags = 1;
	parent = NULL;
	// sync = lightSync; // инициализируется в WorldPlugin
	
	// init light itself
	radius = 0.0f;
	color.r = color.g = color.b = color.a = 1.0f;
	minusCosAngle = 0.0f;
	WorldSectorsInLight.next = WorldSectorsInLight.prev = &WorldSectorsInLight;
	inWorld.next = inWorld.prev = NULL;
	lightFrame = (*engine)->lightFrame - 1;

	// init plugins
	// 0x0509 - world light
	world = NULL;
	sync = worldLightSync;
	savedSync = lightSync;
	// 0x0010 - clump light
	clump = NULL;
	inClumpLink.next = inClumpLink.prev = NULL;		
	// 0x0505 - d3d light
	d3dLight0 = -1;
	d3dLight4 = 1.0f;
	d3dLight8 = 0;
	d3dLightC = 5.0f;
}

CLight::~CLight () {
	// deinit plugins
	// TODO: 0x0505 - d3d light
	// 0x0010 - clump light - nothing to do
	// TODO: 0x0509 - world light
	
	// deinit light itself - nothing to do
}

void CLight::setRadius (float aRadius)  {	// >> 751A70 (2)
	radius = aRadius;
	if (parent)
		parent->updateObject ();
}
//=============================================================================
// CRwCamera
void CRwCamera::setNearClipPlane (float plane) {
	nearPlane = plane;
	updateZShuftScale ();
	if (parent)
		parent->updateObject ();
}

void CRwCamera::updateZShuftScale() {
	float n = nearPlane;
	float f = farPlane;
	float zfar = (*engine)->zBufferFar;
	float znear = (*engine)->zBufferNear;

	if (projectionType != 2) {
		n = 1.0f/n;
		f = 1.0f/f;
	}
	float d = (zfar-znear)/10000.0f;
	zfar  -= d;	
	znear += d;
	zScale = (zfar-znear)/(f-n);
	zShift = ((zfar+znear)-(zScale*(f+n)))/2.0f;
}
//=============================================================================
// CFrame
void * CFrame::operator new (size_t) {
	return __frameAllocator->allocateEntry ();
}
void CFrame::operator delete (void * ptr) {
	__frameAllocator->freeEntry ((BYTE *)ptr);
}

CFrame::CFrame () {	// >> 7F0410(28)/7F0360(2)
	type = 0;
	subType = 0;
	flags = 0;
	privateFlags = 0;
	parent = NULL;

	objectList.prev = objectList.next = &objectList;
	modelling.makeIdentityMatrix ();
	ltm.makeIdentityMatrix ();
	child = next = NULL;
	root = this;

	// plugin initialization
	hanimData0 = -1;
	hanimHierarchy = NULL;
	nodeName[0] = '\0';
	hierarchyId = 0;
}

CFrame::~CFrame () {
	// deinit plugins
	// visibility plugin - no destructor
	// node name plugin - no destructor
	// HAnim plugin (RpHAnimHierarchyDestroy)
	if (hanimHierarchy) {	
		for (int i = 0; i < hanimHierarchy->numNodes; i++)	
			hanimHierarchy->pNodeInfo[i].pFrame = NULL;	// На тот случай, если это суб-иерархия? 

		if (hanimHierarchy->parentFrame == this) {
			if (!(hanimHierarchy->flags & 1)) {	// This hierarchy is a sub-hierarchy?
				if (!(hanimHierarchy->flags & 2))
					free (hanimHierarchy->pMatrixArrayUnaligned);
				free (hanimHierarchy->pNodeInfo);
			}
			// hanimHierarchy->pMatrixArrayUnaligned = NULL;	// все равно сейчас эту структуру грохнут
			// hanimHierarchy->pMatrixArray = NULL;
			// hanimHierarchy->pNodeInfo = NULL;
			free (hanimHierarchy->currentAnim);
			__hanimHierAllocator->freeEntry ((BYTE *)hanimHierarchy);
		} 
		hanimHierarchy = NULL;
	}
	hanimData0 = -1;

	// deinit frame
	if (parent)
		removeFromParent ();
	if (privateFlags & 3) 
		inDirtyListLink.remove ();
	for (CFrame * c = child; c; c = c->next)
		c->parent = NULL;	
	// NOTE: здесь имеется ситуация, когда во всех child-ах этот фрэйм прописан как root. Сейчас мы его удалим. Это, вообще-то нормально?
}

void CFrame::destroyHierarchy() {
	parent = NULL;
	CFrame * n;
	for (CFrame * c = child; c; c = n)  {
		n = c->next;
		c->destroyHierarchy ();
	}
	child = NULL;
	delete this;	
}

void CFrame::updateObject() {
	if (!(root->privateFlags & 3)) {
		root->inDirtyListLink.insertAfter (&((*engine)->dirtyFrameList));
		///root->inDirtyListLink.next = (*engine)->dirtyFrameList.next;
		///root->inDirtyListLink.prev = &((*engine)->dirtyFrameList);
		///(*engine)->dirtyFrameList.next->prev = &(root->inDirtyListLink);
		///(*engine)->dirtyFrameList.next = &(root->inDirtyListLink);
	}
	root->privateFlags |= 3;
	privateFlags |= 0x0C;
}

void CFrame::addChild (CFrame * cf) {
	if (cf->parent)
		cf->removeFromParent ();

	cf->next = child;
	child = cf;
	cf->parent = this;
	cf->root = root;
	
	for (CFrame * cur = cf->child; cur; cur = cur->next)
		cur->setHierarchyRoot (root);

	if (cf->privateFlags & 3) {
		cf->inDirtyListLink.remove ();
		cf->privateFlags &= ~3;
	}

	updateObject ();
//	if (!(root->privateFlags & 3)) 
//		root->inDirtyListLink.insertAfter (&((*engine)->dirtyFrameList));
//	root->privateFlags |= 3;
//	privateFlags |= 0x0C;
}

void CFrame::removeFromParent () {	
	if (parent->child == this)
		parent->child = next;
	else 
		for (CFrame * cur = parent->child; cur; cur = cur->next)
			if (cur->next == this) {
				cur->next = next;
				break;
			}
	parent = NULL;
	next = NULL;
	setHierarchyRoot (this);
	updateObject ();
}
void CFrame::setHierarchyRoot (CFrame * _root) {
	root = _root;
	for (CFrame * f = child; f; f = f->next) 
		f->setHierarchyRoot (_root);
}


void (*_rwFrameSyncHierarchyLTM)(CFrame *) = (void (__cdecl *)(CFrame *))0x8097D0;	// TODO: inline into CFrame::getLTM ? make CFrame method ?
/* TODO: правильный метод: вот это заинлайнить, а syncHierarchyLTMRecurs - сделать методом
if (privateFlags & 4)
	ltm = modelling;
for (CFrame * f = child; f; f = f->next)
	syncHierarchyLTMResurse (f, privateFlags);
privateFlags &= 0xFA; // ~5
*/
RwMatrix * CFrame::getLTM() {
	if (root->privateFlags & 1)
		_rwFrameSyncHierarchyLTM (root);
	return &ltm;
}

RpHAnimHierarchy * CFrame::getHAnimHierarchy() {
	if (hanimHierarchy)
		return hanimHierarchy;
	for (CFrame * f = child; f; f = f->next) {
		RpHAnimHierarchy * hh = f->getHAnimHierarchy ();
		if (hh)
			return hh;
	}
	return NULL;
}

CFrame * CFrame::findByNodeName(char *name) {
	if (!_stricmp (nodeName, name))
		return this;
	for (CFrame * f = child; f; f = f->next) {
		CFrame * found = f->findByNodeName (name);
		if (found)
			return found;
	}
	return NULL;
}

CFrame * CFrame::findFrameFromNameWithoutId (char * name) {
	if (!hierarchyId && !_stricmp (name, nodeName))
		return this;
	for (CFrame * f = child; f; f = f->next) {
		CFrame * found = f->findFrameFromNameWithoutId (name);
		if (found)
			return found;
	}
	return NULL;
}

CFrame * CFrame::findFrameFromId(DWORD id) {
	if (id == hierarchyId)
		return this;
	for (CFrame * f = child; f; f = f->next) {
		CFrame * found = f->findFrameFromId (id);
		if (found)
			return found;
	}
	return NULL;
}

void flattenHierarchyRecurse (CFrame * f, CFrame * r) {
	CFrame * n;
	for (CFrame * c = f->child; c; c  = n) {
		n = c->next;
		flattenHierarchyRecurse (c, r);
	}

	RwLLLink * nl;
	for (RwLLLink * l = f->objectList.next; l != &f->objectList; l = nl) {
		nl = l->next;
		CAtomic * a = (CAtomic *)(((BYTE *)l)-offsetof (RwObjectHasFrame, lFrame));
		a->atomicSetFrame (r);
	}
	delete f;
}
void CFrame::flattenHierarchy (CFrame * newRoot) {
/*
	CFrame * n = child ? child->next : NULL;
	for (CFrame * f = child; f; f  = n, n = f->next) 
		f->flattenHierarchy (newRoot);

	for (RwLLLink * l = objectList.next; l != &objectList; l = l->next) {
		CAtomic * a = (CAtomic *)(((BYTE *)l)-offsetof (RwObjectHasFrame, lFrame));
		a->atomicSetFrame (newRoot);
	}
	if (newRoot != this)
		delete this;
*/
	CFrame * n;
	for (CFrame * f = child; f; f  = n) {
		n = f->next;
		flattenHierarchyRecurse (f, newRoot);
		// f->flattenHierarchy (newRoot);
	}

	if (newRoot != this) {
	}
}

void CFrame::setAtomicVisibilityFlags (WORD flags) {
	for (RwLLLink * l = objectList.next; l != &objectList; l = l->next) {
		CAtomic * a = (CAtomic *)(((BYTE *)l)-offsetof (RwObjectHasFrame, lFrame));
		a->visibilityData2 |= flags;
	}
}
//=============================================================================
//	CTexDictionary
//=============================================================================
//	CTexture
//=============================================================================
// CRwStream
void * CRwStream::operator new (size_t size) {
	return __streamAllocator->allocateEntry ();
}
void * CRwStream::operator new (size_t size, void * ptr) {
	return ptr;
}
void CRwStream::operator delete (void * ptr) {
	__streamAllocator->freeEntry ((BYTE *)ptr);
}

CRwStream::CRwStream () {
	type = rwNASTREAM;
	// accessType = rwSTREAMREAD;	не требуется
	owned = true;
}

struct chunkInfo {
	DWORD	type;
	int		length;
	DWORD	ver;
};

bool CRwStream::readChunkHeader (DWORD * type, int * length, DWORD * version, DWORD * build) {
	chunkInfo		header;

	if (read (&header, sizeof (chunkInfo)) != sizeof (chunkInfo))  {
		debug ("ERORR: CRwStream::readChunkHeader: read error\n");
		return false;
	}
	DWORD _build, _version;
	if (header.ver & 0xFFFF0000) {
		_version = ((header.ver >> 14) & 0x3FF00)+0x30000;
		_build = (header.ver >> 16)&0x3F;
		_version |= _build;
		_build &= 0xFFFF;
	} else {
		_build = 0;
		_version = header.ver;
	}

	if (header.type == 0x13) 
		debug ("DEBUG: UNICODE string found\n");

	if (type)
		*type = header.type;
	if (length)
		*length = header.length;
	if (version)
		*version = _version;
	if (build)
		*build = _build;
	return true;
}

bool CRwStream::readChunkHeaderInfo(ChunkHeaderInfo *info) {
	if (!readChunkHeader (&info->type, &info->length, &info->version, &info->buildNum))
		return false;	
	switch (info->type) {
	case 5:		// CAMERA
	case 6:		// TEXTURE
	case 7:		// MATERIAL
	case 8:		// MATLIST
	case 9:		// ATOMICSECT
	case 10:	// PLANESECT
	case 11:	// WORLD
	case 14:	// FRAMELIST
	case 15:	// GEOMETRY
	case 16:	// CLUMP
	case 18:	// LIGHT
	case 20:	// ATOMIC
	case 26:	// IMAGE
		info->isComplex = true;
		break;
	default:
		info->isComplex = false;
	}
	return true;
}

bool CRwStream::findChunk (DWORD ctype, int * outLength, DWORD * outVersion) {
	int _length;
	DWORD _type, _version, _build;
	while (readChunkHeader (&_type, &_length, &_version, &_build)) {
		if (_type == ctype) {
			if (_version < 0x34000 || _version > 0x36003) {
				debug ("ERROR: CRwStream::findChunk: unsupported version %x (type=%x)\n", _version, _type);
				return false;
			}
			if (outLength)
				*outLength = _length;
			if (outVersion)
				*outVersion = _version;
			return true;
		}
		if (!skip (_length))
			break;
	}
	return false;
}

// TODO: возможно, стоит ввести обработку исключений. Параметром должна передаваться строка с описанием ошибки для записи в лог
CClump * CRwStream::readClump () {
	struct ClumpStreamStruct {
		int	atomicCount;
		int lightCount;
		int cameraCount;
	} clumpStruct;

	CClump * c = NULL;

	try {	
		// locate 'CLUMP' chunk
		if (!findChunk (0x10, NULL, NULL))	// TODO: rwID_CLUMP
			throw CStreamError ("missing 'CLUMP' chunk");
	
		// locate 'STRUCT' chunk - first chunk in clump
		if (!findChunk (1, NULL, NULL))	// TODO: rwID_STRUCT, check length: 12 bytes
			throw CStreamError ("missing 'STRUCT' chunk (clump)");
		
		if (read (&clumpStruct, 12) != 12)
			throw CStreamError ("incomplete clump struct");

	// read framelist
	// TODO: deinitialize franelist on error
	RwFrameList fl;
	if (!findChunk (0x0E, NULL, NULL))	// TODO: rwID_FRAMELIST	
		return NULL;
	if (!_rwFrameListStreamRead (this, &fl))	// TODO: make stream method
		return NULL;
	
	// read geometrylist
	int geometryCount;	
	if (!findChunk (0x1A, NULL, NULL)) // TODO: rwID_GEOMETRYLIST
		return NULL;
	if (read (&geometryCount, 4) != 4)
		return NULL;
	RpGeometry ** geometries = geometryCount > 0 ? new RpGeometry *[geometryCount] : NULL;
	for (int i = 0; i < geometryCount; i++) {
		if (findChunk (0x0F, NULL, NULL))
			break;
		geometries[i] = RpGeometryStreamRead (this);
		if (!geometries[i])
			break;
	}
	// проверим, все ли прочиталось - если не прочиталось - очистим прочитанное
	if (geometries && geometries[geometryCount-1] == 0) {
		for (int i = 0; i < geometryCount; i++) 
			RpGeometryDestroy (geometries[i]);
		delete geometries;
		return NULL;
	}

	c = new CClump ();	// ну, вот мы и дошли до саомго интересного
	c->parent = fl.frames[0];

	// читаем все атомики
	for (int i = 0; i < clumpStruct.atomicCount; i++) {
		if (!findChunk (0x14, NULL, NULL))
			break;	
		CAtomic * a = ClumpAtomicStreamRead (this, &fl, geometries);	// TODO: realAtomic
		if (!a)
			break;
		a->inClumpLink.insertAfter (&(c->atomicList));
		a->clump = c;
	}
	// TODO: здесь надо проверить, все ли прочитано успешно

	// TODO: read lights
	for (int i = 0; i < clumpStruct.lightCount; i++) {
		if (!findChunk (1, NULL, NULL))
			break;
		DWORD frameIndex;
		if (read (&frameIndex, 4) != 4)
			break;
		if (!findChunk (0x12, NULL, NULL))	// TODO: rwID_LIGHT
			break;
		CLight * l = RpLightStreamRead (this);	// TODO: readLight
		if (!l)
			break;
		l->setFrame (fl.frames[frameIndex]);
		l->inClumpLink.insertAfter (&(c->atomicList));
		l->clump = c;
	}

	// TODO: read cameras
	for (int i = 0; i < clumpStruct.cameraCount; i++) {
		if (!findChunk (1, NULL, NULL))
			break;
		DWORD frameIndex;
		if (read (&frameIndex, 4) != 4)
			break;
		if (!findChunk (0, NULL, NULL))
			break;
		CRwCamera * cam = RwCameraStreamRead (this);
		if (!cam)
			break;
		cam->setFrame (fl.frames[frameIndex]);
		cam->inClumpLink.insertAfter (&(c->cameraList));
		cam->clump = c;
	}

	if (geometries) {
		for (int i = 0; i < geometryCount; i++)
			RpGeometryDestroy (geometries[i]);
		delete geometries;
	}
	// TODO: RwFrameListDeinitialize (&fl);
	// TODO: read plugin data

	} catch (CStreamError e) {	// release all resources on error
		debug ("Clump read error: %s\n", e.description);
		// TODO: cleanup
		if (c) {
			delete c;	// atomics, clumps and cameras will be released here
			c = NULL;
		}
	}
	
	return c;
}

CRwFilenameStream::CRwFilenameStream(char * filename) {
	type = rwSTREAMFILENAME;
	file.file = fopen (filename, "rb");
	if (!file.file)
		debug ("ERROR: CRwFilenameStream::CRwFilenameStream: unable to open file '%s'\n", filename);
}

CRwFilenameStream::~CRwFilenameStream () {
	if (file.file)
		fclose (file.file);
}

int CRwFilenameStream::read(void * buffer, int length) {
	if (!file.file)
		return 0;
	int l = fread (buffer, 1, length, file.file);
	if (l != length) {			
		if (feof (file.file)) 
			debug ("ERROR: CRwFilenameStream::read: premature end of file\n");
		else
			debug ("ERROR: CRwFilenameStream::read: file read error\n");		
	}
	return l;
}

bool CRwFilenameStream::skip(int bytes) {
	if (fseek (file.file, bytes, 1)) {
		debug ("RwStreamSkip: seek error\n");
		return false;
	}
	return true;
}

CRwMemoryStream::CRwMemoryStream(BYTE *_block, int _size) {
	type = rwSTREAMMEMORY; 
	memory.position = 0;
	memory.block = _block;
	memory.size = _size;
}

int CRwMemoryStream::read(void *buffer, int length) {
	int l = memory.size-memory.position;
	if (l < length) 
		debug ("RwStreamRead: ERROR: premature end of data\n");			
	else 
		l = length;
	memcpy (buffer, memory.block+memory.position, l);
	memory.position += l;
	return l;
}

bool CRwMemoryStream::skip(int bytes) {
	if (memory.position+bytes > memory.size) {
		debug ("RwStreamSkip: premature end of data\n");
		return false;
	}
	memory.position += bytes;
	return true;
}

void CRwMemoryStream::reinit(bool _owned, BYTE *_block, int _size) {
	owned = _owned;
	memory.block = _block;
	memory.size = _size;
	memory.position = 0;
}
//=============================================================================
// memory allocator (TODO: move to separate file)
// TODO: сделать объект со всеми аллокаторами. В идеале, это должен быть memory manager, но, в этом случае, от него побтребуется гораздо больше
CBlockAllocator * __allocatorsList = NULL;	// list of all allocators TODO: make static 

// CBlockAllocator	__atomicAllocator (sizeof (CAtomic), 128, 1, 4);	// TODO: выделить динамически
CBlockAllocator	* __atomicAllocator = NULL;
CBlockAllocator * __clumpAllocator = NULL;
CBlockAllocator * __tieAllocator = NULL;
CBlockAllocator * __streamAllocator = NULL;
CBlockAllocator * __txdAllocator = NULL;
CBlockAllocator * __textureAllocator = NULL;
CBlockAllocator * __frameAllocator = NULL;
CBlockAllocator * __hanimHierAllocator = NULL;

CBlockAllocator::CBlockAllocator(DWORD _entrySize, DWORD _entriesPerBlock, DWORD preallocateBlocks, DWORD _alignment) {
	alignment = (_alignment != 1 && _alignment != 4) ? 16 : _alignment;		// 
		 
	entrySize = (_entrySize + alignment - 1) & ~(alignment-1);
	entriesPerBlock = _entriesPerBlock ? _entriesPerBlock : 1;
	blocks = NULL;
	link = NULL;
	flags = 2;	// release blocks ASAP

	int mapSize = (entriesPerBlock+31) >> 5;	// element count in block map
	blockHeadSize = (sizeof(BlockHead)+(mapSize-1)*4 + alignment-1) & ~(alignment-1);
	
	debug ("CBlockAllocator created: entrySize = %d, entriesPerBlock = %d, alignment = %d, preallocated = %d\n",
		entrySize, entriesPerBlock, alignment, preallocateBlocks);

	while (preallocateBlocks-- > 0) 
		allocateBlock (); // TODO: check error!

	// TODO2: для многопоточной системы нужны блокировки!
	link = __allocatorsList;
	__allocatorsList = this;
}

CBlockAllocator::~CBlockAllocator() {
	while (blocks)
		freeBlock (NULL);

	if (__allocatorsList == this) 
		__allocatorsList = link;
	else 
		for (CBlockAllocator * ba = __allocatorsList; ba != NULL; ba = ba->link) 		
			if (ba->link == this) {
				ba->link = ba->link->link;
				break;
			}
	debug ("CBlockAllocator destroyed\n");
}

bool CBlockAllocator::allocateBlock() {
	BlockHead * b = (BlockHead *)new BYTE[blockHeadSize+entrySize*entriesPerBlock];
	memset (b, 0, blockHeadSize);
	b->next = blocks;	// add block to the block list
	blocks = b;
	return true;
}

void CBlockAllocator::freeBlock(CBlockAllocator::BlockHead *prevBlock) {
	if (flags & 0x10000) {
		debug ("CBlockAllocator::freeBlock - ERROR: attempt to free block while iterating\n");
		return;
	}
	if (prevBlock) {
		if (!prevBlock->next)
			return;	// bad parameter
		BlockHead * next = prevBlock->next->next;
		delete prevBlock->next;
		prevBlock->next = next;
	} else if (blocks) {	// release first block
		BlockHead * next = blocks->next;
		delete blocks;
		blocks = next;
	}
}

BYTE * CBlockAllocator::allocateEntry() {
	for (BlockHead * b = blocks; b; b = b->next) {
		if (b->allocated >= entriesPerBlock)
			continue;
		
		int entryCount = entriesPerBlock;
		int mapIndex = 0;
		while (entryCount > 0) {
			DWORD mask = entryCount > 32 ? 0xFFFFFFFF : (1 << entryCount)-1;
			if (b->usageMap[mapIndex] != mask) {
				for (int i = 0; i < 32; i++) 
					if (!(b->usageMap[mapIndex] & (1 << i))) {	// free entry found
						b->usageMap[mapIndex] |= (1 << i);
						b->allocated++;
						return ((BYTE *)b)+blockHeadSize+(mapIndex*32+i)*entrySize;
					}				
				// TODO: blocks integrity corrupted
					debug ("CBlockAllocator::allocateEntry: integrity error: usageMap and allocated count mismatch\n");
			}
			entryCount -= 32;
			mapIndex++;
		}
	}
	// all block allocated. allocate new block
	allocateBlock ();
	blocks->usageMap[0] = 1;	// allocate first entry
	blocks->allocated = 1;
	return ((BYTE *)blocks)+blockHeadSize;
}

void CBlockAllocator::freeEntry(BYTE *ptr) {
	BlockHead * prev = NULL;
	DWORD pp = (DWORD)ptr;
	for (BlockHead * b = blocks; b; prev = b, b = b->next) {		
		DWORD bp = ((DWORD)b)+blockHeadSize;
		if (pp >= bp && pp < bp+entrySize*entriesPerBlock) {
			int i = (pp-bp)/entrySize;
			// TODO: для отладки было бы неплохо проверить, что делится нацело.			
			// TODO:  а также, что allocated > 0
			if ((pp-bp)%entrySize || b->allocated == 0) {
				debug ("CBlockAllocator::freeEntry - memory integrity error: block = %08x, allocated = %d,  entry=%08x\n", bp, b->allocated, pp);
				return;
			}
			b->usageMap[i >> 5] &= ~(1 << (i&31));			
			b->allocated--;
			if ((flags & 2) && b->allocated == 0)
				freeBlock (prev);
			return;
		}
	}
	debug ("CBlockAllocator::freeEntry - error: entry %08x not found\n", pp);
}

void CBlockAllocator::forAllUsed (cbaCallback cb, void * param) {
	
	flags |= 0x10000;	// prevent block releasing

	int mapSize = (entriesPerBlock+31) >> 5;
	for (BlockHead * b = blocks; b; b = b->next) {		
		if (b->allocated == 0)
			continue;
		for (int i = 0; i < mapSize; i++) {
			DWORD m = b->usageMap[i];
			for (int j = 0; j < 32; j++) 
				if (m & (1<<j)) 
					cb (((BYTE *)b)+blockHeadSize, param);	// главное, чтобы здесь в цикле блоки не удалялись (это, конечно, можно учесть, но все же...)
		}
	}

	flags &= ~0x10000;
}

int CBlockAllocator::purge() {
	BlockHead * prev = NULL;
	BlockHead * cur = blocks;
	int purged = 0;
	while (cur) {
		if (cur->allocated) {
			prev = cur;
			cur = cur->next;
		} else {
			freeBlock (prev);
			cur = prev ? prev->next : blocks;
			purged++;
		}
	}
	return purged;
}

int CBlockAllocator::purgeAll() {
	int purged = 0;
	for (CBlockAllocator * cur = __allocatorsList; cur; cur = cur->link)
		purged += cur->purge();
	return purged;
}
