В этом уроке будет продемонстрирована возможность создания фигур и их движений из внешнего текстового файла. Все объяснения будут производиться для среды Visual Studio Professional 2010. Для человека, имеющего некоторый опыт работы с Visual Studio, не составит труда разобраться во всем этом независимо от версии от 7-й и выше.
Откройте в директории LessonsGLCode_2010 урок X5 LessonX5 проект BoxMan, файл X5_BoxManDrawInit.cpp.
Как во всех предыдущих уроках, все начинается с InitGL:
int InitGL_X5(GLvoid) // All Setup For OpenGL Goes Here
{
CheckMyDir();
glShadeModel(GL_SMOOTH); // Enable Smooth Shading
glClearColor(0.0f, 0.0f, 0.0f, 0.5f); //Black Background
glClearDepth(1.0f); //Depth Buffer Setup
glEnable(GL_DEPTH_TEST); // Enables Depth Testing
glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations
quadric=gluNewQuadric(); // Создаем указатель на квадратичный объект // Lesson 18
gluQuadricNormals(quadric, GLU_SMOOTH); // Создаем плавные нормали // Lesson 18
gluQuadricTexture(quadric, GL_TRUE); // Создаем координаты текстуры // Lesson 18
//TestMessageBox("InitGL_X5\n1\n");
m_pCurCamera = new CMyCamera;
m_pCurCamera->SetCamera(Vector_3D(0, 3,20), Vector_3D(0, 0, 0), Vector_3D(0, 1, 0));
LightPosition[0]= 10;
LightPosition[1]= 100;
LightPosition[2]= 50;
glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);
IitLight1();
OpenGLSceneFmTxtFile("BoxData/Ring.txt");
OpenGLSceneFmTxtFile("BoxData/BoxMan.txt");
CMyGLObject * pGl = GetGLObjectOfNameGL("BoxMan");
if(pGl != NULL)
{
m_pBoxMan = pGl;
CMyAction * pA = new CMyAction;
pA->m_Name = "Step";
for(int i = 0; i < 4; i++)
pA->AppendSceneFmFile("BoxData/Step_" + strFormat("%02d.txt",i+1));
pGl->m_actionList.AddTail(pA);
pGl->m_pCurAction = pA;
pA = new CMyAction;
pA->m_Name = "rStraight";
for(int i = 0; i < 2; i++)
pA->AppendSceneFmFile("BoxData/rStraight_" + strFormat("%02d.txt",i+1));
pGl->m_actionList.AddTail(pA);
pA = new CMyAction;
pA->m_Name = "lStraight";
for(int i = 0; i < 2; i++)
pA->AppendSceneFmFile("BoxData/lStraight_" + strFormat("%02d.txt",i+1));
pGl->m_actionList.AddTail(pA);
pA = new CMyAction;
pA->m_Name = "lKick";
for(int i = 0; i < 3; i++)
pA->AppendSceneFmFile("BoxData/lKick_" + strFormat("%02d.txt",i+1));
pGl->m_actionList.AddTail(pA);
pA = new CMyAction;
pA->m_Name = "rKick";
for(int i = 0; i < 3; i++)
pA->AppendSceneFmFile("BoxData/rKick_" + strFormat("%02d.txt",i+1));
pGl->m_actionList.AddTail(pA);
}
scaleValue = 1.0;
return TRUE; // Initialization Went OK
}
Новое здесь начинается с открытия текстовых файлов BoxData/Ring.txt и BoxData/BoxMan.txt процедурой OpenGLSceneFmTxtFile.
Вы можете открыть и просмотреть структуру этих файлов с помощью любого текстового редактора, начиная с «Блокнота». Вот фрагмент файла BoxData/BoxMan.txt, в котором описана его левая рука:
<GLObject>
<style>SPHERE_OBJ
<name> <Left_Shoulder_Joint> Левый плечевой сустав
<pos> 0.00 2.30 -1.50
<ang> 4.00 0.00 4.00
<angMin> -40.00 0.00 0.00
<angMax> 180.00 0.00 180.00
<cfnt> 1.00 1.00 1.00
<lbh> 0.50 0.50 0.50
<Material>
<ambient> 0.25 0.25 0.25 0.80
<diffuse> 0.50 0.50 0.00 1.00
<specular> 0.00 0.00 0.00 0.00
<emission> 0.20 0.20 0.20 0.00
<shininess> 1.00
<numChilds> 1
<GLObject>
<style>CYLINDER_OBJ
<name> <Forearm> Предплечье
<pos> 0.00 -1.30 0.00
<ang> 0.00 90.00 0.00
<angMin> 0.00 0.00 0.00
<angMax> 0.00 0.00 0.00
<cfnt> 1.00 1.00 1.00
<lbh> 0.40 1.50 1.50
<Material>
<ambient> 0.25 0.25 0.25 0.80
<diffuse> 0.50 0.50 0.00 1.00
<specular> 0.00 0.00 0.00 0.00
<emission> 0.20 0.20 0.20 0.00
<shininess> 1.00
<numChilds> 1
<GLObject>
<style>SPHERE_OBJ
<name> <Ulnar_Joint> Локтевой сустав
<pos> 0.00 -0.70 0.00
<ang> 0.00 0.00 0.00
<angMin> -150.00 0.00 -150.00
<angMax> 0.00 0.00 0.00
<cfnt> 1.00 1.00 1.00
<lbh> 0.50 0.50 0.50
<Material>
<ambient> 0.25 0.25 0.25 0.80
<diffuse> 0.50 0.50 0.00 1.00
<specular> 0.00 0.00 0.00 0.00
<emission> 0.20 0.20 0.20 0.00
<shininess> 1.00
<numChilds> 1
<GLObject>
<style>CYLINDER_OBJ
<name> <Arm> Рука
<pos> 0.00 -1.30 0.00
<ang> 0.00 0.00 0.00
<angMin> 0.00 0.00 0.00
<angMax> 0.00 0.00 0.00
<cfnt> 1.00 1.00 1.00
<lbh> 0.40 1.50 1.50
<Material>
<ambient> 0.25 0.25 0.25 0.80
<diffuse> 0.50 0.50 0.00 1.00
<specular> 0.00 0.00 0.00 0.00
<emission> 0.20 0.20 0.20 0.00
<shininess> 1.00
<numChilds> 1
<GLObject>
<style>SPHERE_OBJ
<name> <Fist> Кулак
<pos> 0.00 -1.00 0.00
<ang> 0.00 90.00 0.00
<angMin> 0.00 0.00 0.00
<angMax> 0.00 0.00 0.00
<cfnt> 1.00 1.00 1.00
<lbh> 0.70 0.70 0.70
<Material>
<ambient> 0.25 0.25 0.25 0.80
<diffuse> 0.25 0.00 0.00 1.00
<specular> 0.00 0.00 0.00 0.00
<emission> 0.20 0.20 0.20 0.00
<shininess> 1.00
<numChilds> 0
</GLObject><Fist>Кулак
</GLObject><Arm>Рука
</GLObject><Ulnar_Joint>Локтевой сустав
</GLObject><Forearm>Предплечье
</GLObject><Left_Shoulder_Joint>Левый плечевой сустав
Я думаю, из этого фрагмента структура файла уже понятна.
Далее с помощью процедуры AppendSceneFmFile считываются все возможные варианты действий объекта. Например, удар правой ногой записан в 3-х файлах BoxData/rKick_01.._03.
Вот фрагмент файла BoxData/ rKick_02.txt, в котором описанo положение его левой руки на второй стадии удара правой ногой:
<name> <Left_Shoulder_Joint> Левый плечевой сустав
<pos> 0.00 2.30 -1.50
<ang> 23.00 0.00 23.00
<numChilds> 1
<name> <Forearm> Предплечье
<pos> 0.00 -1.30 0.00
<ang> 0.00 90.00 0.00
<numChilds> 1
<name> <Ulnar_Joint> Локтевой сустав
<pos> 0.00 -0.70 0.00
<ang> -150.00 0.00 0.00
<numChilds> 1
<name> <Arm> Рука
<pos> 0.00 -1.30 0.00
<ang> 0.00 0.00 0.00
<numChilds> 1
<name> <Fist> Кулак
<pos> 0.00 -1.00 0.00
<ang> 0.00 90.00 0.00
<numChilds> 0
Т.е. из предыдущего фрагмента оставлена информация только о позиции и угле поворота каждого элемента относительно предыдущего элемента.
Как во всех предыдущих уроках, изображение осуществляется процедурой DrawGLScene:
int DrawGLScene_X5(GLvoid) // Here's Where We Do All The Drawing
{
glDisable(GL_LIGHTING);
glTranslatef(0, 0, -30); // Move Into The Screen 30.0
int nCount = 0;
for(POSITION pos = m_glObjList.GetHeadPosition(); pos != NULL; nCount++)
{
CMyGLObject * pGl = (CMyGLObject*)m_glObjList.GetNext(pos);
pGl->drawGLObject();
}//for(POSITION pos = m_glObjList.GetHeadPosition(); pos != NULL;)
if(m_bRot) yrot+=0.2f; // Lesson 06
return TRUE; // Everything Went OK
}
Здесь m_glObjList является объектным списком графических элементов, описываемых классом CMyGLObject. Вот фрагмент его описания с основными элементами класса:
class CMyGLObject : public CObject // Графический объект
{
public:
CMyGLObject();
virtual ~CMyGLObject();
Vector_3D pos; // положение объекта относительно родительского объекта
Vector_3D vel; // скорость относительно родительского объекта
Vector_3D ang; // углы поворота относительно родительского объекта
Vector_3D angMin; // мин. углов поворота относительно родительского объекта
Vector_3D angMax; // макс. углов поворота относительно родительского объекта
Vector_3D rot; // угловые скорости относительно родительского объекта
Vector_3D lbh; // размер объекта длина ширина высота
Vector_3D cfnt; // коэффициенты сжатия объекта
GLUquadricObj *m_pQuad; // ссылка на квадратич. объект
CPlaneBox * m_pBx; // ссылка на объект, заданный плоскостями
int m_style; // характеристика объекта (сфера, диск, цилиндр…)
CMaterial material; // материал
int m_frame; // здесь не используется
BOOL m_bHit; // здесь не используется
int m_nTick; // счетчик тиков таймера
CString m_NameGL; // Название по-английски
CString m_NameGLrus; // Название по-русски
Matrix m_glMatrix; // текущая графическая матрица объекта
CMyGLObject * m_pParentGL; // родительский графический объект
CObList m_glObjectList; // объектный список дочерних объектов
CObList m_actionList; // объектный список возможных действий
CObList m_actionStack; // объектный список для запоминания текущего действия
CMyAction * m_pCurAction; // ссылка на текущее действие
int m_nScene; // порядковый номер сцены в текущем дайствии
………
Главный фокус здесь в отрисовке объекта по принципу «кусая себя за хвост»:
void CMyGLObject::drawGLObject( void)
{
glMatrixMode( GL_MODELVIEW );
glPushMatrix();
BOOL B_DEPTH_TEST = glIsEnabled(GL_DEPTH_TEST);
BOOL B_LIGHTING = glIsEnabled(GL_LIGHTING);
BOOL B_AUTO_NORMAL = glIsEnabled(GL_AUTO_NORMAL);
BOOL B_BLEND = glIsEnabled(GL_BLEND);
BOOL B_TEXTURE_2D = glIsEnabled(GL_TEXTURE_2D);
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);
glEnable(GL_AUTO_NORMAL);
glEnable(GL_NORMALIZE);
::glTranslatef( pos.x, pos.y, pos.z);
::glRotatef(ang.y , 0.0f, 1.0f, 0.0f);
::glRotatef(ang.x, 1.0f, 0.0f, 0.0f);
::glRotatef(ang.z , 0.0f, 0.0f, 1.0f);
glScalef(cfnt.x, cfnt.y, cfnt.z);
glColor3f(1,1,1);
material.SetMaterial(GL_FRONT_AND_BACK);
::glGetFloatv(GL_MODELVIEW_MATRIX, &m_glMatrix.x[0][0]);
drawObjectOfType();
B_BLEND ? glEnable(GL_BLEND): glDisable(GL_BLEND) ;
B_DEPTH_TEST ? glEnable(GL_DEPTH_TEST): glDisable(GL_DEPTH_TEST) ;
B_LIGHTING ? glEnable(GL_LIGHTING): glDisable(GL_LIGHTING) ;
B_AUTO_NORMAL ? glEnable(GL_AUTO_NORMAL): glDisable(GL_AUTO_NORMAL) ;
B_TEXTURE_2D ? glEnable(GL_TEXTURE_2D): glDisable(GL_TEXTURE_2D) ;
for(POSITION pos = m_glObjectList.GetHeadPosition(); pos != NULL;)
{
CMyGLObject * pGl = (CMyGLObject *)m_glObjectList.GetNext(pos);
pGl->m_pParentGL = this;
pGl->drawGLObject();
}//for(position pos = m_glObjectList.GetHeadPosition(); pos != NULL;)
glMatrixMode( GL_MODELVIEW );
glPopMatrix();
}
Процедуры ::glTranslatef, ::glRotatef, glScalef производят перемещение, вращение и растяжение отображаемого элемента, а затем для каждого из дочерних элементов из объектного списка снова вызывается эта же процедура CMyGLObject::drawGLObject. Способы отображения объекта в OpenGL как нельзя лучше приспособлены для такой процедуры.
Движение достигается сменой кадров в процедуре Update_X5:
void Update_X5(DWORD milliseconds)
{
if(!m_Cadr)
for(POSITION pos = m_glObjList.GetHeadPosition(); pos != NULL; )
{
CMyGLObject * pGl = (CMyGLObject*)m_glObjList.GetNext(pos);
pGl->NextGLScene();
}//for(POSITION pos = m_glObjList.GetHeadPosition(); pos != NULL;)
m_Cadr++;
m_Cadr %= MAX_CADR;
for(POSITION pos = m_glObjList.GetHeadPosition(); pos != NULL; )
{
CMyGLObject * pGl = (CMyGLObject*)m_glObjList.GetNext(pos);
pGl->UpdateCadr();
}//for(POSITION pos = m_glObjList.GetHeadPosition(); pos != NULL;)
}
Промежуток между двумя сценами делится на 25 кадров (MAX_CADR) , для каждого из которых рассчитывается соответствующее промежуточное положение, оно и выводится на экран.
Таким образом, в этом уроке имеется возможность создания фигур и их движений во внешних текстовых файлах, причем пользователю не нужно знать программистских деталей, достаточно только пространственного воображения. Можете попытаться изменить значения параметров в текстовых файлах в директории BoxData, и посмотреть, что из этого получится, вновь запустив программу BoxMan.
Кроме того, если вы усвоили предыдущий урок X4, то вы можете создать копию этого проекта и модифицировать все по своему усмотрению.
Код к уроку
LessonsCode_2010.7z (5.09 Mb)
© Владимир Петров
petrov@msun.ru
Моя страница