Уроки Коммент.

Создание игр » DirectX 9, Featured, Теория » Иерархия объектов

Иерархия объектов

Иерархия объектовИерархия объектов очень удобна когда нам нужно задать какие-то трансформации объектов не относительно начала координат, а относительно друг-друга. Например, если создать иерархию из двух объектов: рука и туловище, то для того, что бы рука двигалась вместе с туловищем, нам не надо будет прилагать каких-либо усилий – при движении туловища трансформации всех объектов в иерархии (в нашем случае рука) будут пересчитаны в соответствии с движением объектов привязки (в нашем случае туловища), но при этом сами объекты могут трансформироваться относительно своего “предка” в иерархии. Чаще всего иерархии объектов применяются для создания и анимации механизированных агрегатов, таких как “механические руки”, подъёмные краны, двери (да-да, двери это наиболее частое применение иерархий объектов), разного рода выдвигающиеся откуда-то лестницы и многое другое. Все эти элементы игрового мира в большинстве случаев реализованы именно иерархиями объектов.

Реализация иерархии объектов

Реализация иерархии объектов достаточно проста. По большей части всё, что нам нужно сделать, это реализовать механизм наследования трансформаций от одного объекта к другому (или нескольким). Для этого каждый объект должен иметь собственную матрицу трансформации и, если для него задан “родительский” объект, он должен так же наследовать трансформацию согласно иерархии от этого родительского объекта. Для этого я дслелал отдельный класс, который назвал CEntity. Этот класс содержит в себе только информацию о положении и трансформации объекта (т.е. матрицу трансформации – она задаёт и положение и трансформацию), а так же информацию о “предке”, от которого, по иерархии, будут наследоваться трансформации объектов:

 
class CEntity
{
private:
	CEntity* m_pParent;
	D3DXMATRIXA16 m_matrix;
 
public:
	CEntity(void);
	virtual ~CEntity(void);
 
	const D3DXMATRIX GetMatrix() const;
	D3DXVECTOR3 GetPos() const;
 
	void SetMatrix(const D3DXMATRIX& mat);
	void SetRotation(const D3DXMATRIX& mat);
	void SetPos(const D3DXVECTOR3& pos);
	void SetParent(CEntity* pParent);
};

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

Иерархии объектов из 3д-моделей

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

 
class CEntityModel :
	public CEntity
{
private:
	CMeshPtr m_pMesh;
 
public:
	CEntityModel(const std::string& strFileName="");
	virtual ~CEntityModel(void);
 
	bool SetModel(const std::string& strFileName);
	void ReleaseModel();
 
	CMeshPtr GetMesh() const;
};

Наследования трансформаций в иерархии объектов

Само наследование трансформаций внутри иерархии объектов так же делается элементрно. Реализован этот функционал в одной из функций представленного выше класса:

const D3DXMATRIX CEntity::GetMatrix() const
{
	if (!m_pParent)
	{
		return m_matrix;
	}
	D3DXMATRIXA16 m = m_pParent->GetMatrix();
	D3DXMatrixMultiply(&m, &m_matrix, &m);
	return m;
}

Как видите, всё гениальное просто )))

Хочу заметить, что в данный момент я не ставлю перед собой цели как-то оптимизировать код – для наших с Вами целей быстродействие нашей программы и так будет более, чем достаточным.

Настройка иерархии объектов

Я не стал изголяться с конфигами и прочим и задал саму иерархию и зависимости объектов в самой программе – наш пример программы достаточно простой, потому сделать это оказалось совсем не сложно – я просто указал для каждого их объектов его положение (относительно предка) и задал самих предков ещё на этапе инициализации приложения:

	m_modelTop.SetModel("Data/top.GAMEMODEL");
	m_modelMiddle.SetModel("Data/middle.GAMEMODEL");
	m_modelBottom.SetModel("Data/bottom.GAMEMODEL");
 
	m_modelTop.SetPos(D3DXVECTOR3(0, 16, 0));
	m_modelMiddle.SetPos(D3DXVECTOR3(0, 15, 0));
	m_modelBottom.SetPos(D3DXVECTOR3(0, 0, 0));
 
	m_modelMiddle.SetParent(&m_modelBottom);
	m_modelTop.SetParent(&m_modelMiddle);

Анимация иерархических объектов

Поскольку внутри иерархии объекты наследуют трансформации, анимировать иерархии получается очень просто, даже проще, чем можно было себе представить… Тем более, что как раз для апдейта сцены у нас в классе CGameApplication предусмотрена отдельная функция под названием UpdateFrame(), то как раз её я и использовал:

void CTutorial3D::UpdateFrame()
{
	D3DXMATRIX m;
	static float a=0.0f;
	a+=.03f;
 
	D3DXMatrixRotationZ(&m, -cos(a) + cos(a*3)*.2f);
	m_modelTop.SetRotation(m);
 
	D3DXMatrixRotationZ(&m, cos(a));
	m_modelMiddle.SetRotation(m);
}

После чего всё заработало само-собой…

На этом я пока хочу закончить с разговором о иерархиях объектов. Думаю, исходный код, а так же готовая дема, помогут Вам разобраться что к чему. Если же нет – я, как обычно, жду ваших комментариев и постараюсь объяснить все непонятные или плохо мной раскрытые моменты…

Ещё по этой теме:




Раздел: DirectX 9, Featured, Теория · Теги: Direct3D, Создание движка

11 комментариев на "Иерархия объектов"
  1. Scripter пишет:

    хорошая и полезная статья

  2. ArchiDevil пишет:

    Великолепно! Отличная статья, я не знал как “связывать” объекты. Спасибо огромное!

  3. Вячеслав пишет:

    Спасибо за то, что читаете и поддерживаете! :-)

  4. Antony пишет:

    Жду выходных с нетерпением, чтобы разобрать исходники) Спасибо Вячеславу!

    Кстати, демка не запустилась (критическая ошибка), может чего забыли доложить? :)

    Кстати, ф-ия
    const D3DXMATRIX CEntity::GetMatrix() const
    получилось почти как рекурсивная, если иерархия большая. Понимаю, что сейчас не те цели, но в будущем очень был бы рад увидеть и оптимизированный вариант этого действия, если можно)

    1. Вячеслав пишет:

      Antony, спасибо )

      Вроде бы, в дему добавить ничего не забыл. Уже многие качали и проверяли – у всех всё работало. Попробуйте пересобрать проект у себя и запустить – может быть, вылезут какие-то ошибки, я их найти не смог :-(

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

      Если сейчас сделать оптимизацию – кода сразу станет в разы больше. Этот код нам потом придётся поддерживать и разваивать. Тем самым, мы в будущем затратим в разы больше усилий и времени на решение задач, что, с моей точки зрения, было бы не очень разумно.

      Данный подход вполне способен обрабатывать достаточно сложные (до десятка вложений) иерархии сотен объектов. Этого для наших целей (да и для большинства игр, вобщем-то) вполне достаточно, как мне кажется.

  5. Antony пишет:

    Ясно, благодарю за объяснения!

  6. Вячеслав пишет:

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

    Ссылку на старую демку заменил на новую, а апдейт исходников будет в следующем уроке.

  7. Antony пишет:

    Сейчас работает)
    Можно узнать эту причину?
    Чтобы самому избегать возможных проблем с запусками.

    1. Вячеслав пишет:

      Конечно можно. Проблема была в том, что иногда (так и не удалось выяснить по каким причинам) вообщение WM_PAINT приходило в WndProc минуя основной цикл обработки сообщений. Создавалось окно, отправлялось сообщение, рендер в это время ещё не был инициализирован, получался аксесс виолэйшен. В итоге я просто отключил рендер при поступлении WM_PAINT и проблема решилась сама-собой.

      Чем может быть вызыван такой баг – багами Windows или некоторой кривизной моих рук – судить не берусь :-)

  8. lds пишет:

    Неплохой у вас вышел движок, но процесс подачи на рендер можно было бы усовершенствовать. Сейчас у вас так:
    m_pVertexShaderPhong->SetMatrix(“mWorld”, &m_modelTop.GetMatrix());
    gr.Draw(m_modelTop.GetMesh());
    А было бы удобнее сделать в движке render queue и запихивать туда объекты типа mesh, каждый их которых хранил бы индекс буфера и свою матрицу. Перед собственно рендером движок бы сортировал объекты в render queuе, и заодно отсекал бы невидимую геометрию. Ну и можно было бы быть уверенным, что текстуры с альфа каналом нарисуются правильно.

    »crosslinked«

    1. Вячеслав пишет:

      lds, спасибо за комментарий )

      Касательно RQ – просто всему свое время, мы же только начали. Это ещё не движок и даже, можно сказать, ещё и не зачатки его, вобщем-то :-)

      “Нормальный” движок, который мы используем в своих проектах, содержит весь этот (и даже больший) функционал и при этом его исходники “весят” несколько мегабайт. Думаю, если бы я просто вывалил на этот блог несколько мегабайт исходников – от этого никому лучше бы не стало. По крайней мере понимания того, как это работает, точно не прибавилось бы особо ;-)

Оставить комментарий

*

Вы можете использовать это HTMLтеги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>