Сайт борца за чистоту кода

Главная

Стэнли Томас - Ваш сосед - миллионер

Информация о материале
Автор: gdever
Категория: Разное
Опубликовано: 20 февраля 2013
Просмотров: 12685

Продолжая рецензии на прочитанные книги, хочется остановиться на достаточно подробном и, на мой взгляд, объективном исследовании, которое было оформлено в виде книги "Ваш сосед - миллионер".

Хотя книга и написана максимально доступно, её научность и обилие цифр весьма сильно усложняют восприятие. Однако, если вы любите серьёзные книги, а не "жёлтое" чтиво на пару часов, то эта книга для вас. Здесь можно найти массу информации и сделать весьма интересные, а иногда и совсем неожиданные выводы.

Гораздо чаще богатство – результат всей жизни, проведенной в тяжелом труде, упорстве, планировании и, прежде всего, самодисциплине.

Кто становится богатым? Как правило, богатый человек – это бизнесмен, который всю свою взрослую жизнь живот в одном городе.

Здесь на самом деле написано следующее - не надо эмигрировать и лучшая инвестиция это свой бизнес.

Постоянные зрители телешоу недаром болеют за участников, и не случайно такие программы занимают вершину рейтинга. Отождествляя себя с участниками, люди с восторгом смотрят, как они выигрывают автомобили, яхты, ценные вещи и денежные призы.

Мало кто задумывается о фантастической популярности программы "Поле чудес". Ну вопервых в ней участвуют простые смертные которые близки подавляющему большинству телезрителей. Вовторых это сама концепция игры - угадать простейшее слово и получить приз. Нет много призов. Все это делает программу чрезвычайно живучей. Действительно редкая программа на тв смогла продержаться хотя бы пять лет. Не говоря уже про двадцатник.

Вы когда-нибудь по-настоящему обращали внимание на людей, которые ежедневно занимаются оздоровительным бегом? Они выглядят так, будто никакой бег им не нужен. Именно бегу, они обязаны здоровьем. Богатые люди заботятся о своем финансовом здоровье, а те, у кого состояние финансов оставляет желать лучшего, практически не пытаются улучшить его.

Воистину по человеку сразу видно занимается он спортом или "занимается". Ну и по тому как он хранит свои деньги в кошельке многое можно сказать и о самом человеке и о его отношениях с деньгами.

Составлять бюджет легче, если помнить о выгодах этого в долгосрочной перспективе.

Это как с тайм-менеджментом - кажется что человек завален работой а по факту получается что действительно работы на пару часов. Так и с бюджетом. Кажется что все расходы нужные а в реальности деньги уходят на всякое барахло и мелочевку. Большенство же людей живет от зарплаты до зарплаты, демонстрируя вопиющую инфантильность.

Трудная работа

Информация о материале
Автор: gdever
Категория: Посмеяться
Опубликовано: 30 января 2012
Просмотров: 4746

Текстурирование, создание текстурного менеджера

Информация о материале
Автор: gdever
Категория: DirectX 8
Опубликовано: 13 января 2012
Просмотров: 4010

    Доброго времени суток всем, кто решил заняться текстурированием. Данная тема очень простая, поэтому кроме описания приемов работы с текстурами я расскажу как создать менеджер текстур, который может динамически загружать/выгружать текстуры. Это может оказаться очень полезным при большом количестве текстур и маленьком объеме видеопамяти. Итак, приступим.

    Для начала рассмотрим класс текстуры, с помощью которого мы будем эти самые текстуры грузить, выгружать и делать разные интересные вещи.

class	cTexture{
public:
	LPDIRECT3DTEXTURE8	Texture;
	cTexture( void ){Texture=NULL;}
	cTexture( float ){Texture=NULL;}
	void		operator=( float ){}
	void		CreateTextureFromFile( LPDIRECT3DDEVICE8 Device , char *path );
	void		CreateTextureFromFile( LPDIRECT3DDEVICE8 Device , char *path , int MipLevels );
	void		GetLevelDesc( int level , D3DSURFACE_DESC *desc ){Texture->GetLevelDesc( level , desc );}
	
	void		CreateTextureFromFile( LPDIRECT3DDEVICE8 Device , char *path , int MipLevels , DWORD USAGE );
	void		ResetTexture( LPDIRECT3DDEVICE8 Device , int i ){if( Texture )Device->SetTexture( i , NULL );}
	void		SetTexture( LPDIRECT3DDEVICE8 Device , int i ){if( Texture )Device->SetTexture( i , Texture );}
	HRESULT		GetSurfaceLevel( UINT Level , IDirect3DSurface8 **ppSurfaceLevel )
				{return( Texture->GetSurfaceLevel( Level , ppSurfaceLevel ) );}
	HRESULT		CreateRenderTarget( LPDIRECT3DDEVICE8 Device , int w , int h )
				{return(Device->CreateTexture( w , h , 1 , D3DUSAGE_RENDERTARGET , 
				D3DFMT_X8R8G8B8 , D3DPOOL_DEFAULT , &Texture));}
	void		Merge( LPDIRECT3DDEVICE8 , cTexture& , int , int );
	void		SetTransparent( DWORD );
	void		Release( void ){if( Texture )Texture->Release();Texture=NULL;}
	void		CreateTextureFromMemory( LPDIRECT3DDEVICE8 D3DDevice , LPCVOID STR , long size )
				{D3DXCreateTextureFromFileInMemory( D3DDevice , STR , size , &Texture );}
};

    CreateTextureFromFile ( LPDIRECT3DDEVICE8 Device , char *path , int MipLevels , DWORD USAGE ) – с помощью этой функции можно загрузить текстуру из фала (путь к которому передается в параметре path), контролируя при этом количество мип-уровней. Каждый i-й мип-уровень меньше (i-1)-го в 2 раза, таким образом при генерации всех мип-уровней мы получаем двойной расход памяти, но получаем выигрыш в производительности – когда полигон находится далеко от камеры, нет нужды накладывать на него исходную текстуру, которая может быть очень большая. Вместо этого будет использован какой-то из сгенерённых мип-уровней. Это существенно ускорит рендерринг. Остальные функции CreateTextureFromFile действуют аналогично.

void				cTexture::CreateTextureFromFile( LPDIRECT3DDEVICE8 Device , char *path , 
									int MipLevels , DWORD USAGE ){
	HRESULT			RET_VAL( D3DXCreateTextureFromFileEx( Device , path , D3DX_DEFAULT ,
					D3DX_DEFAULT , 1 , USAGE , D3DFMT_UNKNOWN ,
					D3DPOOL_DEFAULT , D3DX_FILTER_LINEAR , D3DX_FILTER_LINEAR ,
					NULL , NULL , NULL , &Texture ) );
	if( RET_VAL != D3D_OK ){
		char			error_message[1000];
		strcpy( error_message , "Невозможно найти текстуру. derect path: " );
		strcat( error_message , path );
		MessageBox( 0 , error_message , "Ошибка" , 0 );
		exit( 0 );
	}
}

void				cTexture::CreateTextureFromFile( LPDIRECT3DDEVICE8 Device , char *path ){
	HRESULT			RET_VAL( D3DXCreateTextureFromFile( Device , path , &Texture ) );
	if( RET_VAL != D3D_OK ){
		char			error_message[1000];
		strcpy( error_message , "Невозможно найти текстуру. derect path: " );
		strcat( error_message , path );
		MessageBox( 0 , error_message , "Ошибка" , 0 );
		exit( 0 );
	}
}

void				cTexture::CreateTextureFromFile( LPDIRECT3DDEVICE8 Device , char *path , int MipLevels ){
	HRESULT			RET_VAL( D3DXCreateTextureFromFileEx( Device , path , 
					D3DX_DEFAULT , D3DX_DEFAULT , MipLevels , 0 , 
					D3DFMT_UNKNOWN , D3DPOOL_DEFAULT , D3DX_FILTER_LINEAR , 
					D3DX_FILTER_LINEAR , NULL , NULL , NULL , &Texture ) );
	if( RET_VAL != D3D_OK ){
		char			error_message[1000];
		strcpy( error_message , "Невозможно найти текстуру. derect path: " );
		strcat( error_message , path );
		MessageBox( 0 , error_message , "Ошибка" , 0 );
		exit( 0 );
	}
}

    GetLevelDesc( int level , D3DSURFACE_DESC *desc ) – позволяет определить параметры мип-уровня текстуры(например размеры).

    SetTexture( LPDIRECT3DDEVICE8 Device , int i ) – устанавливает текстуру в i-й уровень.

    ResetTexture( LPDIRECT3DDEVICE8 Device , int i ) – удаляет текстуру из i-й уровня.

    Merge( LPDIRECT3DDEVICE8 , cTexture & m_tex , int x, int y) – переписывает в текстуру *this текстуру m_tex, x и y – координаты верхнего левого угла прямоугольника в который будет осуществлено копирование текстуры m_tex. Если размеры текстуры m_tex таковы, что её правый нижний угол вылезает за границы целевой текстуры, появится соответствующее сообщение. Вот код:

void				cTexture::Merge( LPDIRECT3DDEVICE8 D3DDevice , cTexture &m_tex , int x , int y ){
	//описатели текстур
	D3DSURFACE_DESC		DstInfo , SrcInfo;

	//узнаём параметры текстур
	Texture->GetLevelDesc( 0 , &DstInfo );
	m_tex.GetLevelDesc( 0 , &SrcInfo );

	//как я и говорил – проверяем, что бы m_tex
	//целиком влезала в целевую текстуру
	if( x + SrcInfo.Width > DstInfo.Width || y + SrcInfo.Height > DstInfo.Height ){
		MessageBox( 0 , "Размеры целевой текстуры недостаточно велики" , 0 , 0 );
		exit( 0 );
	}

	D3DLOCKED_RECT		LockedRectDst , LockedRectSrc;

//лочим текстуры для копирования
    	Texture->LockRect( 0 , &LockedRectDst , 0 , 0 );
	DWORD		*p32Dst = ( DWORD* )LockedRectDst.pBits;
	
	m_tex.Texture->LockRect( 0 , &LockedRectSrc , 0 , 0 );
	DWORD		*p32Src = ( DWORD* )LockedRectSrc.pBits;

//устанавливаем указатель на пиксели целевой текстуры 
//в нужное положение координаты (x,y)
	p32Dst += 4 * DstInfo.Width * y + 4 * x;

	//копируем содержимое m_tex
	for( int i( 0 ) ; i < SrcInfo.Height ; i++ ){
		for( int j( 0 ) ; j < SrcInfo.Width ; j++ ){
			( *p32Dst ) = ( *p32Src );
			p32Dst++;p32Src++;
		}
		//одну строку m_tex скопировали
		p32Dst += DstInfo.Width - SrcInfo.Width;
	}
	//разлочиваем текстуры
	m_tex.Texture->UnlockRect( 0 );
	Texture->UnlockRect( 0 );
}

    Данная функция может пригодиться, если вы задумаете при создании интерфейса, рисовать кнопки непосредственно по текстуре фона (на мой взгляд, не самый удобный вариант).

    Release( void ) – удаляет текстуру

    CreateTextureFromMemory( LPDIRECT3DDEVICE8 D3DDevice , LPCVOID STR , long size ) – создает текстуру из памяти, на которую указывает PTR (размер этой памяти в байтах - size). Позднее Вы увидите, как пользоваться этой текстурой.

    Вот небольшой пример, как пользоваться этим классом:

//класс вершины – ничего особенного

#define		FVF_CD3DVERTEX			D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1

class						cD3DVertex:public cVector3{
public:
	DWORD					color;
	float					u,v;
	cD3DVertex( void ):cVector3(){color=0xffffff;}
	cD3DVertex( float f1 , float f2 , float f3 ):cVector3( f1 , f2 , f3 ){}
	cD3DVertex( float f1 , float f2 , float f3 , DWORD c ):cVector3( f1 , f2 , f3 ){ color = c;}
	cD3DVertex( float f1 , float f2 , float f3 , DWORD c , float f4 , float f5 )
		:cVector3( f1 , f2 , f3 ){ color = c ; u = f4 ; v = f5;}
	cD3DVertex				operator=( cVector4 v ){x = v.x;y = v.y;z = v.z;return(*this);}
};

//просто создаем белый квадрат
cD3DVertexBuffer	VB;
VB.AddEnd( cD3DVertex(0,0,0,0xffffff,0,1) );
VB.AddEnd( cD3DVertex(1,1,0,0xffffff,1,0) );
VB.AddEnd( cD3DVertex(0,1,0,0xffffff,0,0) );

VB.AddEnd( cD3DVertex(0,0,0,0xffffff,0,1) );
VB.AddEnd( cD3DVertex(1,0,0,0xffffff,1,1) );
VB.AddEnd( cD3DVertex(1,1,0,0xffffff,1,0) );

//создаем текстуру
cTexture	Texture;
Texture.CreateTextureFromFile( D3DDevice , “nvlogo.bmp” );

//где-то в функции рендерринга
//устанавливаем текстуру
Texture.SetTexture( D3DDevice , 0 );

//рендеррим содержимое буфера
VB.Render( D3DDevice , FVF_CD3DVERTEX );

//отключаем текстуру
Texture.ResetTexture( D3DDevice , 0 );

    Вот и всё!

    Я сразу приведу класс, который у меня получился, а затем будем потихонечку его ковырять.

DWORD		TextureThreadFunc( LPVOID );

struct			sTextureInfo{char		Info[256];};

int			TimeOut = 5;

class		cTextureManager{
	cThread					TextureThread;
	cMutex					QueueMutex;
public:
	cArray<cTexture>			Textures;
	cArray<cMutex>				TextureMutexes;
	cArray<cMsg>				Queue;
	cArray				Dinamic;
	cArray				Released;
	cArray<sTextureInfo>			TexturesInfo;
	cArray<sTextureInfo>			TexturePaths;
	cArray<time_t>				LastRequestTime;

	__forceinline void			Lock( void )
					{if( TextureThread != NULL )QueueMutex.EnterCriticalSection();}
	__forceinline void			Unlock( void )
					{if( TextureThread != NULL )QueueMutex.LeaveCriticalSection();}

	__forceinline void			Lock( int i )
					{if( TextureThread != NULL )TextureMutexes[ i ].EnterCriticalSection();}
	__forceinline void			Unlock( int i )
					{if( TextureThread != NULL )TextureMutexes[ i ].LeaveCriticalSection();}
	
	void					RunThread( void );
	void					LoadTextures( char* );

	cTexture				&operator[]( char * );
	cTexture				&operator[]( int i );
	void					SetTexture( LPDIRECT3DDEVICE8 , char * , int );
	void					ResetTexture( LPDIRECT3DDEVICE8 , char * , int );
	void					SetTexture( LPDIRECT3DDEVICE8 , int , int );
	void					ResetTexture( LPDIRECT3DDEVICE8 , int , int );

	int						GetTextureCursor( char * );

	void					ReleaseTexture( char * );
	void					ReleaseTexture( int );
	void					UploadTexture( char * );
	void					UploadTexture( int );

	void					Reload( LPDIRECT3DDEVICE8 );
	void					Release( void );

	void					FillRequestTime( void );

	~cTextureManager();

	int						GetSleepTime( int );
}; cTextureManager				TextureManager;

    Вот какие поля в этом классе
    Textures - массив текстур
    TextureThread - объект потока (подробнее об этом классе смотрите в одном из предыдущих уроков)
    QueueMutex - мьютекс для очереди сообщений
    TextureMutexes - массив мьютексов (по одному на каждую текстуру)
    Queue - очередь сообщений.
    Dinamic - массив флагов. Если true, то Textures[i] можно загружать/выгружать в потоке TextureThread.
    Released - если Released[i] == true, то текстура Textures[i] в данный момент выгружена.
    TexturesInfo - название текстуры.
    TexturePaths - путь к текстуре.
    LastRequestTime - время последнего обращения к текстуре.

    Прежде всего, нам надо все текстуры загрузить. Это делается методом LoadTextures( char* ). В качестве параметра он принимает путь в файле, в котором хранятся пути к текстурам. Вот как происходит загрузка:

void					cTextureManager::LoadTextures( char *path ){
	char		tmp_path[256];
	char		enum_path[256];
	memcpy( tmp_path , path , strlen( path ) + 1 );
	memcpy( enum_path , path , strlen( path ) + 1 );

	strcat( enum_path , "texture_enum.log" );

	FILE		*f_stream = fopen( enum_path , "rt" );
//проверяем, существует ли файл со списком текстур
	if( !f_stream ){
		MessageBox( 0 , "Невозможно загрузить описатель текстур." , "Ошибка" , 0 );
		exit( 0 );
	}

	char			directive[ 256 ];

	for( ; EOF != fscanf( f_stream , "%s" , directive ) ; ){
		if( !strcmp( directive , "set_dinamic" ) ){
			//читаем название текстуры
			fscanf( f_stream , "%s" , directive );
			//делаем её динамической
			Dinamic[ GetTextureCursor( directive ) ] = true;
		}
		else{
			//читаем название текстуры
			sTextureInfo		TextureInfo;
			memcpy( &TextureInfo , directive , 256 );
			TexturesInfo.AddEnd( TextureInfo );
			
			//все текстуры по умолчанию не подлежат
			//динамической загрузке/выгрузке
			Dinamic.AddEnd( false );
			//текстура загружена
			Released.AddEnd( false );

			//читаем название файла текстуры
			fscanf( f_stream , "%s" , directive );
			memcpy( enum_path , path , strlen( path ) + 1 );
			strcat( enum_path , directive );
			Textures[ Textures.Cursor++ ].CreateTextureFromFile( D3DDevice , enum_path );
			
			memcpy( &TextureInfo , enum_path , 256 );
			//сохраняем путь к текстуре
			TexturePaths.AddEnd( TextureInfo );
		}
	}
//заполнение массива LastRequestTime
	FillRequestTime();
}

    Вот пример загружаемого файла:

nvlogo				bmp/nvlogo.bmp
sunset				jpeg/sunset.jpg
logo				logo.bmp

set_dynamic			nvlogo

    Как Вы, наверное, поняли, в файле хранятся относительные пути. Берётся путь к папке, в которой лежит texture_enum.log и к нему прибавляется путь, указанный в файле. Так же хочу отметить такой нюанс – заполнение массива LastRequestTime происходит после того, как все текстуры загружены. Это нужно для того, что бы после загрузки всех текстур, текстуры, допускающие динамическую подгрузку/выгрузку, не были выгружены (загрузка всех текстур может длиться более одной минуты).

    Вот функция, которая заполняет массив LastRequestTime

void			cTextureManager::FillRequestTime( void ){
	for( int i( 0 ) ; i < Textures.Cursor ; i++ ){
		Lock( i );

		LastRequestTime.AddEnd( time() );

		Unlock( i );
	}
}

    Массив LastRequestTime нужно лочить, поскольку все функции, предназначенные для работы с текстурами, могут модифицировать элементы этого массива. Правда, я как ни старался, не мог придумать случай, когда незалоченный массив LastRequestTime мог вызвать серьёзный сбой… Просто будем считать, что Lock/Unlock это хороший тон.

    Теперь функции для работы с текстурами (установка/сброс):

void				cTextureManager::SetTexture( LPDIRECT3DDEVICE8 D3DDevice , int t_c , int s ){
	Lock( t_c );

	if( !Released[ t_c ] )
		( *this )[ t_c ].SetTexture( D3DDevice , s );
	else{
		UploadTexture( t_c );
	}

	LastRequestTime[ t_c ] = time();

	Unlock( t_c );
}

void				cTextureManager::ResetTexture( LPDIRECT3DDEVICE8 D3DDevice , char *t_name , int s ){
	int				t_c( GetTextureCursor( t_name ) );

	Lock( t_c );

	if( !Released[ t_c ] )
		( *this )[ t_c ].ResetTexture( D3DDevice , s );

	LastRequestTime[ t_c ] = time();

	Unlock( t_c );
}

void				cTextureManager::SetTexture( LPDIRECT3DDEVICE8 D3DDevice , int t_c , int s ){
	Lock( t_c );

	if( !Released[ t_c ] )
		( *this )[ t_c ].SetTexture( D3DDevice , s );
	else
		UploadTexture( t_name );

	LastRequestTime[ t_c ] = time();

	Unlock( t_c );
}

void				cTextureManager::ResetTexture( LPDIRECT3DDEVICE8 D3DDevice , int t_c , int s ){
	Lock( t_c );

	if( !Released[ t_c ] )
		( *this )[ t_c ].ResetTexture( D3DDevice , s );

	LastRequestTime[ t_c ] = time();

	Unlock( t_c );
} 

    Мьютексами необходимо “накрываться”, на случай если поток уничтожит текстуру, которую мы хотим установить. Так же не забываем обновлять LastRequestTime[ t_c ], иначе запущенный поток будет выгружать абсолютно все текстуры (которые этого допускают, естественно).

    Функция, возвращающая курсор текстуры по названию (так же ничего сложного):

int						cTextureManager::GetTextureCursor( char * t_name ){

	for( int i( 0 ) ; i < Textures.Cursor ; i++ ){
		if( !strcmp( t_name , TexturesInfo[ i ].Info ) ){
			return( i );
		}
	}
	char			error_message[1000];
	strcpy( error_message , "Обращение к несуществующей текстуре : " );
	strcat( error_message , t_name );
	MessageBox( 0 , error_message , "Ошибка" , 0 );
	exit( 0 );
}

    Функция, возвращающая текстуру (опять-таки не забываем лочить ресурсы мьютексом и обновлять LastRequestTime[ i ]):

cTexture				&cTextureManager::operator[]( int i ){

	Lock( i );

	LastRequestTime[ i ] = time();

	Unlock( i );

	return( Textures[ i ] );
}

cTexture				&cTextureManager::operator[]( char * name ){

	int			TextureCursor( GetTextureCursor( name ) );

	Lock( TextureCursor );

	LastRequestTime[ TextureCursor ] = time();

	Unlock( TextureCursor );

	return( Textures[ TextureCursor ] );
}

    Теперь осталось организовать функции для загрузки/удаления текстур и можно заниматься потоком:

void				cTextureManager::ReleaseTexture( char *t_name ){
	int				TextureCursor( GetTextureCursor( t_name ) );

	Lock();

	if( !Released[ TextureCursor ] )
		Queue.AddEnd( cMsg( T_RELEASE_TEXTURE , 0 , TextureCursor ) );
	else{
		MessageBox( 0 , "Вы пытаетесь удалить уже удаленную текстуру." , "Ошибкаю" , 0 );
		exit( 0 );
	}

	Unlock();
}

void				cTextureManager::ReleaseTexture( int i ){
	Lock();
	
	if( !Released[ i ] )
		Queue.AddEnd( cMsg( T_RELEASE_TEXTURE , 0 , i ) );
	else{
		MessageBox( 0 , "Вы пытаетесь удалить уже удаленную текстуру." , "Ошибкаю" , 0 );
		exit( 0 );
	}

	Unlock();
}

void				cTextureManager::UploadTexture( char *t_name ){
	int				TextureCursor( GetTextureCursor( t_name ) );

	Lock();

	if( Released[ TextureCursor ] )
		Queue.AddEnd( cMsg( T_UPLOAD_TEXTURE , 0 , TextureCursor ) );
	else{
		MessageBox( 0 , "Вы пытаетесь загрузить уже загруженную текстуру." , "Ошибкаю" , 0 );
		exit( 0 );
	}

	Unlock();
}

void				cTextureManager::UploadTexture( int i ){
	Lock();

	if( Released[ i ] )
		Queue.AddEnd( cMsg( T_UPLOAD_TEXTURE , 0 , i ) );
	else{
		MessageBox( 0 , "Вы пытаетесь загрузить уже загруженную текстуру." , "Ошибкаю" , 0 );
		exit( 0 );
	}

	Unlock();
}

    Как видите ни первая, ни вторая функции напрямую с текстурами не работают. Они лишь вешают сообщение в очередь и следят за тем, что бы некорректных сообщений не создавалось (загрузить второй раз уже загруженную текстуру, например).

    Из рутины остались только функции удаления, перезагрузки текстур и деструктор.

void				cTextureManager::Reload( LPDIRECT3DDEVICE8 D3DDevice ){
	for( int i( 0 ) ; i < Textures.Cursor ; i++ ){
		Lock( i );

		if( !Released[ i ] ){
			Textures[ i ].CreateTextureFromFile( D3DDevice , TexturePaths[ i ].Info );
		}

		Unlock( i );
	}
}

void				cTextureManager::Release( void ){
	for( int i( 0 ) ; i < Textures.Cursor ; i++ ){
		Lock( i );

		if( !Released[ i ] ){
			Textures[ i ].Release();
			Released[ i ] = true;
		}

		Unlock( i );
	}
}

cTextureManager::~cTextureManager(){
	Release();
	TextureMutexes.Release();
	Released.Release();
	Textures.Release();
	TexturesInfo.Release();
	TexturePaths.Release();
	Dinamic.Release();
}

    Теперь самое интересное – поток. Он запускается следующей функцией:

void				cTextureManager::RunThread( void ){
	cThreadParam	ThreadParam;
	//в поток передаем только указатель на наш менеджер
	ThreadParam.AddParamPtr( *this );
	TextureThread.CreateThread( TextureThreadFunc , ThreadParam );
	TextureThread.SetThreadPriority( THREAD_PRIORITY_NORMAL );
}

    Функция потока в моей интерпретации будет выглядеть так:

DWORD				TextureThreadFunc( LPVOID PTR ){
	cThreadParam		ThreadParam( PTR );
	cTextureManager		*t_man( ( cTextureManager * )ThreadParam.GetParami( 0 ) );
	cArray		TEXTURE;

	FILE				*f_stream;

	for(;;){
		//накрываемся мьютексом
		//берем сообщение
		t_man->Lock();
		int			Queue_Cursor( t_man->Queue.Cursor );
		int			Queue_wParam( t_man->Queue[ 0 ].wParam );
		int			Queue_Message( t_man->Queue[ 0 ].Message );
		t_man->Unlock();

		if( Queue_Cursor ){
			//обрабатываем сообщение
			switch( Queue_Message ){
			//подгрузка текстуры
				case( T_UPLOAD_TEXTURE ):
					t_man->Lock( Queue_wParam );
					
					//выгружена ли текстура?
					if( t_man->Released[ Queue_wParam ] ){
						//Открываем файл
						f_stream = fopen( t_man->TexturePaths[ Queue_wParam ].Info , "rb" );
						//смотрим длину файла
						fseek( f_stream , 0 , SEEK_END );
						int				f_length( ftell( f_stream ) );
						fseek( f_stream , 0 , SEEK_SET );
						//готовим буфер для чтения
						if( TEXTURE.Length < f_length )
							TEXTURE.SetLength( f_length );
						
						fread( TEXTURE.Array , 1 , f_length , f_stream );
//текстуру будем создавать из буфера в памяти, 
//мне почему-то кажется, что так будет быстрее, хотя я не проверял
						t_man->Textures[ Queue_wParam ].CreateTextureFromMemory( 
							D3DDevice , TEXTURE.Array , f_length );
					//сообщаем, что теперь текстура загружена
						t_man->Released[ Queue_wParam ] = false;

						fclose( f_stream );
					}

					t_man->Unlock( Queue_wParam );
				break;
				case( T_RELEASE_TEXTURE ):
					//удаление текстуры
					t_man->Lock( Queue_wParam );

					t_man->Textures[ Queue_wParam ].Release();
					t_man->Released[ Queue_wParam ] = true;

					t_man->Unlock( Queue_wParam );
				break;
			};
			//грохаем сообщение
			t_man->Lock();

			t_man->Queue.ShiftL( 0 , 1 );
			
			t_man->Unlock();

		}

		//можно расслабиться, если сообщений нет
		if( !t_man->Queue.Cursor ){
			//считаем сколько можно “спать”
			Sleep( 1000 * t_man->GetSleepTime( TimeOut ) + 1000 );
			time_t		start( time() );
			//проверяем, какие из текстур на данный момент можно удалить
			for( int i( 0 ) ; i < t_man->LastRequestTime.Cursor ; i++ )
				if( difftime( start , t_man->LastRequestTime[ i ] ) > TimeOut ){
					if( t_man->Dinamic[ i ] && !t_man->Released[ i ] )
						t_man->ReleaseTexture( i );
				}
		}
	}
	delete [] ( ( char * )PTR );
	return( 0 );
}

    Надеюсь после столь подробных комментариев, вопросов не осталось. На досуге можете подумать, как реализовать функцию GetSleepTime (возможно Вы сможете придумать что-то неординарное), я же сделал её такой:

int						cTextureManager::GetSleepTime( int TimeOut ){
	time_t				start( time() );
	int					dt( 0 );
	int					diff;
	int					e_time;
	for( int i( 0 ) ; i < LastRequestTime.Cursor ; i++ ){
		diff = difftime( start , LastRequestTime[ i ] );
		dt = ( diff > dt ) ? diff : dt;
	}
	e_time = ( TimeOut - diff < 0 ) ? 0 : TimeOut - diff;
	return( e_time );
}

    Вот собственно и все, что я хотел рассказать Вам в этой статье. Напомню, что нашей целью (на первое время) являются классы для создания менюшек. Мы с честью шли к этой цели, и вот нам осталось сделать последний шаг. Но сделаем мы его в другой статье. Удачи!

    PS: замечания, предложения, пожелания как всегда на Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.

    исходные коды

Введение в Direct3D

Информация о материале
Автор: gdever
Категория: DirectX 8
Опубликовано: 13 января 2012
Просмотров: 4317

    Доброго времени суток всем! Как я и обещал, эта статья будет посвящена инициализации Direct3d и вершинным буферам. Но прежде мы приступим к рисованию треугольников (никуда от них не деться), давайте задумаемся, что нам для этого нужно.

D3DDISPLAYMODE			DisplayMode;
D3DPRESENT_PARAMETERS		PresentParameters;

    D3DDISPLAYMODE – структура, хранящая формат режима (разрешение, частоту обновления и формат пикселей)
    D3DPRESENT_PARAMETERS – хранит более тонкие настройки системы

    Создание Direct3D объекта:

D3DObject = Direct3DCreate8( D3D_SDK_VERSION );
if( FAILED( D3DObject ) ){
	MessageBox( NULL , "Error cretaing D3DObject!" , "Error" , MB_OK );
	return(false);
}

    Устанавливаем параметры видеорежима

//ширина
DisplayMode.Width	= D3D_SCREEN_WIDTH;
//высота
DisplayMode.Height	= D3D_SCREEN_HEIGHT;
//частота обновления экрана
DisplayMode.RefreshRate	= 0;
//формат пикселей
DisplayMode.Format	= D3DFMT_X8R8G8B8;

    В приведенном коде строка DisplayMode.RefreshRate = 0 подразумевает, что будет выставлена частота адаптера по умолчанию (а не ноль Гц :) ).

    Теперь настраиваем девайс:

memset( &PresentParameters , 0 , sizeof( D3DPRESENT_PARAMETERS ) );
PresentParameters.Windowed		= FALSE;
PresentParameters.SwapEffect		= D3DSWAPEFFECT_DISCARD; 
PresentParameters.BackBufferCount	= 1;
PresentParameters.BackBufferFormat	= DisplayMode.Format;//(1)
PresentParameters.BackBufferWidth	= DisplayMode.Width;//(2)
PresentParameters.BackBufferHeight	= DisplayMode.Height;//(3)
PresentParameters.FullScreen_RefreshRateInHz	= D3DPRESENT_RATE_DEFAULT;
PresentParameters.EnableAutoDepthStencil	= TRUE;
PresentParameters.AutoDepthStencilFormat	= D3DFMT_D24S8;
PresentParameters.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

    В данном фрагменте мы установили полноэкранный режим (PresentParameters.Windowed), “попросили” Direct3D очищать front-буфер перед тем как поменять его местами с back-буфером (D3DSWAPEFFECT_DISCARD), уточнили, что у нас будет только один back-буфер (PresentParameters.BackBufferCount), подогнали параметры буфера кадра под параметры видео режима (строки (1)(2)(3)), установили частоту обновления экрана (D3DPRESENT_RATE_DEFAULT) и формат буфера глубины(D3DFMT_D24S8 – 24 бита под буфер глубины и 8 под стенсил), и наконец отключили синхронизацию D3DPRESENT_INTERVAL_IMMEDIATE.

    Осталось только создать девайс:

HRESULT hr = D3DObject->CreateDevice( D3DADAPTER_DEFAULT , 
	D3DDEVTYPE_HAL , hWnd , D3DCREATE_HARDWARE_VERTEXPROCESSING , &PresentParameters, &D3DDevice );
	if( FAILED( hr ) ){
		if( D3DERR_NOTAVAILABLE )MessageBox( NULL , "Error creating D3DDevice( Unavaiable Mode )" , "Error" , MB_OK );
		if( D3DERR_OUTOFVIDEOMEMORY )MessageBox( NULL , "Error creating D3DDevice( Out of memory )" , "Error" , MB_OK );
		return( false );
	}

    По минимуму настроили рендеринг:

	//отключили освещение (пока)
	D3DDevice->SetRenderState( D3DRS_LIGHTING , FALSE );
	//отключили отсечение нелицевых граней
	D3DDevice->SetRenderState( D3DRS_CULLMODE , D3DCULL_NONE);

    Осталось только установить проекционную матрицу:

	D3DXMATRIX projection_matrix;
	//будем использовать правую систему координат – так привычнее
	//с углом обзора 45 градусов
	//создали матрицу
	D3DXMatrixPerspectiveFovRH(&projection_matrix, D3DX_PI/4f, 
		(float)D3D_SCREEN_WIDTH/(float)D3D_SCREEN_HEIGHT, 0.1f, 1000000.0f );
	//установили матрицу
	D3DDevice->SetTransform( D3DTS_PROJECTION, &(D3DMATRIX)projection_matrix );

    Теперь нам нужен некоторый класс для представления вершины в пространстве + хотелось бы подвести под это хоть какой-то математический аппарат(я имею в виду удобный АПИ для работы с векторами и матрицами).

class						cVector3{
public:
	//координаты
	float					x,y,z;
	//конструкторы
						cVector3( void ){}
						cVector3( float f ){x=y=z=f;}
						cVector3( float f1 , float f2 , float f3 ){x=f1;y=f2;z=f3;}
	//вращение вокруг соответствующих осей
	void					fRx( float );
	void					fRy( float );
	void					fRz( float );
	//векторное сложение с присваиванием (сдвиг точки)
	void					operator+=( cVector3 );
	//векторное вычитание с присваиванием
	void					operator-=( cVector3 );
	//масштабирование вектора
	cVector3				operator*( float );
	friend cVector3				operator*( float , cVector3 );
	//векторная разность
	cVector3				operator-( cVector3 );
	//сложение векторов(сдвиг точки)
	cVector3				operator+( cVector3 );
	//унарный минус
	cVector3				operator-( void );
	//скалярное произведение
	float					operator%( cVector3 );
	//векторное произведение
	cVector3				operator*( cVector3 );
	//евклидова норма вектора
	float					Norm( void );
	//нормирование вектора
	void					Normize( void );
};

     введение в direct3d Поподробнее остановимся на поворотах вокруг координатных осей. Для простоты будем рассматривать двумерный поворот вокруг начала координат:
    а – начальное положение нашей точки.
    b – конечное положение точки.
    t2 – угол, на который будет совершаться поворот.
    Воспользуемся представлением этих точек в полярных координатах:

(1)
a.x = r * cos(t1)
a.y = r * sin(t1)

b.x = r * cos( t1 + t2 )
b.y = r * sin( t1 + t2 )

    Вспомнив формулы для разложения синусов и косинусов двойных углов, имеем:

(2)
b.x = r * cos(t1) * cos(t2) – r * sin(t1) * sin(t2)
b.y = r * cos(t1) * cos(t2) + r * sin(t1) * sin(t2)

    И, наконец, подставив (1) в систему (2), получим:

(3)
b.x = a.x * cos(t2) – a.y * sin(t2)
b.y = a.x * cos(t2) + a.y * sin(t2)

    Осталось только заметить что при вращении вокруг оси Z, сама координата z точки остаётся неизменной, следовательно, добавив к (3) выражение b.z = a.z, получим искомое вращение вокруг оси Z. Для оставшихся двух вращений рассуждения будут аналогичными.
    Теперь собственно сама вершина:

//описываем формат вершин (в Direct3D все вершину нужно будет таким образом описывать)
//здесь указаны три координаты, цвет, текстурные координаты
//на первое время должно хватить
#define						FVF_CD3DVERTEX			D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1

class						cD3DVertex:public cVector3{
public:
	DWORD					color;
	float					u,v;
	cD3DVertex( void ):cVector3(){color=0xffffff;}
	cD3DVertex( float f1 , float f2 , float f3 ):cVector3( f1 , f2 , f3 ){}
	cD3DVertex( float f1 , float f2 , float f3 , DWORD c ):cVector3( f1 , f2 , f3 ){ color = c;}
	cD3DVertex( float f1 , float f2 , float f3 , DWORD c , float f4 , float f5 )
		:cVector3( f1 , f2 , f3 ){ color = c ; u = f4 ; v = f5;}
};

    Хочу сразу оговориться: я использую наследование и перегрузку всего и вся (в разумных пределах, естественно). Поэтому даже не пытайтесь меня переубедить.

    cD3Dvertex простой класс, т.к. сам по себе ничего не рендерит, а всю необходимую математику он наследует от вектора.

     введение в direct3dТеперь собственно можно и камеру позиционировать. Вот класс:

float D3D_CAM_VELOSITY  = 0.01;
float D3D_CAM_DANGLE    = 0.03;

class cCam{
public:
	cVector3			target,norm,eye;
	//*********************************************************************
	void				D3DCameraReInit( void );
	void				D3DCameraInit( cVector3 , cVector3 , cVector3 );
};

    Значение полей target,norm,eye показано на рисунке.

    Создадим глобальный объект Camera и инициализируем его:

Camera.D3DCameraInit( cVector3( 0 , 0 , 1 ) , cVector3( 0 , 0 , -1 ) , cVector3( 0 , 1 , 0 ) );

D3DXMATRIX ViewMatrix;			// View Matrix
D3DXMatrixLookAtRH( &ViewMatrix ,	// Update View Matrix
		  &D3DXVECTOR3( eye.x , eye.y , eye.z ),	
		//здесь в качестве параметра используется точка на которую мы смотрим, а не направление взгляда
		  &D3DXVECTOR3( eye.x + target.x , eye.y + target.y , eye.z + target.z ),	
		  &D3DXVECTOR3( norm.x , norm.y , norm.z ) );
//устанавливаем видовую матрицу
D3DDevice->SetTransform( D3DTS_VIEW , &ViewMatrix );// Apply Changes To The View

    Теперь наконец можно заняться рендеррингом, но сначала как всегда класс:

template<class Type>class	cD3DVertexBuffer{
public:
	cD3DVertexBuffer(void);
	cD3DVertexBuffer(int){Vertexes.Cursor=0;}
	~cD3DVertexBuffer(){Vertexes.Release();}
	cArray<Type>		Vertexes;

	//добавление вершины
	__forceinline void	AddVertex( Type );

	//рендерринг
	void				Render( LPDIRECT3DDEVICE8 , DWORD );
	void				FlushVertexes( LPDIRECT3DDEVICE8 );

	//операции над всем массивом вершин – смещение по вектору и повороты
	void				TTransfer( cVector3 );
	void				fRy( float );
	void				fRz( float );
	void				fRx( float );

	//упростили себе доступ к вершинам
	Type				operator[]( int i ){return( Vertexes[ i ] );}
};

template<class Type>void	cD3DVertexBuffer<Type>::AddVertex( Type v ){
	Vertexes.AddEnd( v );
}

template<class Type>void	cD3DVertexBuffer<Type>::Render( LPDIRECT3DDEVICE8 D3DDevice , DWORD SHADER ){
	D3DDevice->SetVertexShader( SHADER );
	D3DDevice->DrawPrimitiveUP( D3DPT_TRIANGLELIST , Vertexes.Cursor / 3 , Vertexes.Array , sizeof( Type ) );
}

    Собственно это и не вершинный буфер никакой, просто мне не хотелось придумывать какое-то особенное название, это усложнило бы восприятие программы, да и лениво как-то было :). Интерес представляет, пожалуй, только функция Render. В качестве параметра она принимает девайс и формат вершины (в нашем примере это будет FVF_CD3DVERTEX). Этот класс представляет простейшие возможности по отрисовки вершин – он не поддерживает индексирования, выводит вершины в нулевой поток с одним текстурным уровнем. Сделан был только для того что бы вы имели возможность, без написания огромных кусков кода инициализации, сесть и протестировать то что Вам нужно (просто вершины покидали в массив и все) + это представление данных позволяет достаточно просто изменять данные, хотя ради этого и приходится жертвовать производительностью.

    Теперь настоящий ИНДЕКСИРУЕМЫЙ вершинный буфер:

template<class Type>class	cD3DIVertexBuffer{
public:
	//указатель на индексный буфер
	LPDIRECT3DINDEXBUFFER8	D3DIndexBuffer;
	//указатель на вершинный буфер
	LPDIRECT3DVERTEXBUFFER8 D3DVertexBuffer;
	//число полигонов
	int					NumberOfPolygons;
	//вершины
	cArray<Type>		Vertexes;
	//индексы
	cArray			Indexes;
	//добавить вершину с автоматическим индексированием
	void				AddIVertex( Type );
	//добавить вершину с ручными индексированием
	void				AddVertex( Type , int );
	//создать вершинный буфер в памяти(инициализация указателей 
	//D3DindexBuffer и D3DVertexBuffer)
	void				CreateVertexBuffer( LPDIRECT3DDEVICE8 , DWORD );
	//рендерринг
	void				Render( LPDIRECT3DDEVICE8 , DWORD );
						~cD3DIVertexBuffer();
	//с помощью этих функций лочим и анлочим буфферы
	void					Lock( void );
	void					Unlock( void );
};

template<class Type>void	cD3DIVertexBuffer<Type>::Unlock( void ){
	D3DVertexBuffer->Unlock();
	D3DIndexBuffer->Unlock();
	vrt = ind = NULL;
}

template<class Type>void	cD3DIVertexBuffer<Type>::Lock( void ){
	D3DVertexBuffer->Lock( 0 , 0 , ( BYTE** )&vrt , NULL );
	D3DIndexBuffer->Lock( 0 , 0 , ( BYTE** )&ind , NULL );
}

template<class Type>void	cD3DIVertexBuffer<Type>::Release( void ){

	if( D3DIndexBuffer )
		D3DIndexBuffer->Release();
	D3DIndexBuffer = NULL;

	if( D3DVertexBuffer )
		D3DVertexBuffer->Release();
	D3DVertexBuffer = NULL;

	Vertexes.Release();
	Indexes.Release();
}

template<class Type>void	cD3DIVertexBuffer<Type>::fRx( float Angle ){
	Lock();
	for( int i( 0 ) ; i < NumberOfVertexes ; i++ )
		( ( Type* )vrt )[ i ].fRx( Angle );
	Unlock();
}

template<class Type>void	cD3DIVertexBuffer<Type>::fRy( float Angle ){
	Lock();
	for( int i( 0 ) ; i < NumberOfVertexes ; i++ )
		( ( Type* )vrt )[ i ].fRy( Angle );
	Unlock();
}

template<class Type>void	cD3DIVertexBuffer<Type>::fRz( float Angle ){
	Lock();
	for( int i( 0 ) ; i < NumberOfVertexes ; i++ )
		( ( Type* )vrt )[ i ].fRz( Angle );
	Unlock();
}

template<class Type>void	cD3DIVertexBuffer<Type>::AddVertex( Type Vertex , int i ){
	Indexes.AddEnd( i );
	Vertexes.AddEnd( Vertex );
}

template<class Type>void	cD3DIVertexBuffer<Type>::AddIVertex( Type Vertex ){
	Indexes.AddEnd( Vertexes.Cursor );
	Vertexes.AddEnd( Vertex );
}

//передаем девайс и формат вершин
template<class Type>void	cD3DIVertexBuffer<Type>::Render( cD3DDevice &D3DDevice , DWORD VERTEX_FORMAT ){
	//устанавливаем поток для вывода
	D3DDevice->SetStreamSource( 0 , D3DVertexBuffer , sizeof( Type ) );
	D3DDevice->SetIndices( D3DIndexBuffer , 0 );
	//устанавливаем формат вершин
	D3DDevice->SetVertexShader( VERTEX_FORMAT );
	//отрисовываем вершины
	D3DDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST , 0 ,
                                   NumberOfVertexes , 0 , NumberOfPolygons );
}

template<class Type>void	cD3DIVertexBuffer<Type>::CreateVertexBuffer( LPDIRECT3DDEVICE8 D3DDevice , DWORD VERTEX_FORMAT ){
	//создание пустого вершинного буфера
	//D3DPOOL_DEFAULT – система сама решит куда лучше положить ваши  
	//вершины
	//в системную память или в память видеокарты
	if( FAILED( D3DDevice->CreateVertexBuffer( Vertexes.Cursor * sizeof( Type ) ,
		0 , VERTEX_FORMAT , D3DPOOL_DEFAULT, &D3DVertexBuffer ) ) ){
		MessageBox( 0 , "Ошибка создания индексируемого вершинного буффера." , "Ошибка." , 0 );
		exit(0);
	}

	void	*vVertexes;
	void	*vIndexes;
	//заполняем вершинный буфер вершинами
	//в Direct3D лочатся не только вершинные буфферы но и текстуры, запомните это или запишите ))
	//этим Direct3D существенно отличается от OGL, там вроде такого механизма нет
	if( FAILED( D3DVertexBuffer->Lock( 0 , Vertexes.Cursor * sizeof( Type ) , ( BYTE** )&vVertexes , D3DLOCK_READONLY ) ) ){
		MessageBox( 0 , "Ошибка залочивания индексируемого вершинного буффера." , "Ошибка." , 0 );
		exit(0);
	}
	memcpy( vVertexes , Vertexes.Array , Vertexes.Cursor * sizeof( Type ) );
	D3DVertexBuffer->Unlock();

	//создание пустого индексного буфера
	//D3DPOOL_DEFAULT – система сама решит куда лучше положить ваши индексы
	//в системную память или в память видеокарты
	//D3DFMT_INDEX16 – в качестве индекса будет использоваться 16 разрядное 
	//целое
	if( Indexes.Cursor ){
		if( FAILED( D3DDevice->CreateIndexBuffer( sizeof( unsigned long ) * Indexes.Cursor , 
				0 , D3DFMT_INDEX16, D3DPOOL_DEFAULT , &D3DIndexBuffer ) ) ){
			MessageBox( 0 , "Ошибка создания индексного буффера." , "Ошибка." , 0 );
			exit(0);
		}
	//заполняем индексный буфер индексами
		if( FAILED ( D3DIndexBuffer->Lock( 0 , 
			Indexes.Cursor * sizeof( unsigned long ) , ( BYTE** )&vIndexes , D3DLOCK_READONLY ) ) ){	
			MessageBox( 0 , "Ошибка залочивания индексного буффера." , "Ошибка." , 0 );
			exit(0);
		}
		memcpy( vIndexes , Indexes.Array , sizeof( unsigned long ) * Indexes.Cursor );
		D3DIndexBuffer->Unlock();
	}
	//заметьте, число полигонов рассчитывается исходя из числа индексов, а 
	//не вершин
	NumberOfPolygons = Indexes.Cursor / 3;

	//освобождаем память
	Vertexes.Release();
	Indexes.Release();
}

    Вот вроде и все. Осталось только сказать зачем нужны индексы: с их помощью можно существенно уменьшить объем вводимых данных. Например нам надо вывести квадрат (два треугольника с двумя смежными вершинами). Без индексов нам пришлось бы создавать буфер на 6 вершин, а используя индексы, в вершинном буфере будут лежать всего 4 вершины + 6 индексов в индексном буфере (по три на полигон). Вот эти индексы: 0,1,2; 2,1,3 (при условии, что 1 и 2 вершины общие для двух полигонов).

    Вот теперь действительно все.

    исходные коды

Семь раз отмерь

Информация о материале
Автор: gdever
Категория: Лирика
Опубликовано: 25 декабря 2011
Просмотров: 3725

Продолжая разговор про бестолковых людей.

У большинства людей вообще не принято думать перед тем как делать, потому что банальный вопрос "почему было сделано именно так?" неизменно ставит их в тупик. Нет они конечно начинают лепетать, что они мол думали что это сработает. Но ничего более конкретного. Я сам сравнительно недавно обзавёлся привычкой перед тем как сделать более менее важное действие подыскивать веские аргументы почему это надо сделать и почему это действие увенчается успехом. Один мой знакомый, надо сказать весьма преуспевающий и деловой, решил заработать продажей ссылок со свох сайтов. Ухнул кучу денег для создания нормальных (не говно-) сайтов и сел ждать фантастических доходов. Идея в общем-то здравая - заполучить источник пассивного дохода. Но когда он понял какого размера будет этот доход, он мягко говоря опечалился. Я, не скрою, сам размышлял над чем-то подобным, однако у меня все закончилось на этапе оценки стоимости одного сайта и периода, за который сайт окупится. Получив неудовлетворительные оценки и не потратив ни копейки, я успокоился и начал прорабатывать другие варианты зарабатывания денег.

  1. Матричные преобразования в OpenGL
  2. Камера в OpenGL (на кватернионах).
  3. Робин Гуд
  4. Нормальные формы баз данных ч.1

Страница 6 из 8

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8