Физический симулятор

Опубликовано 18.05.2009 в 20:55 в разделе ,
На правах рекламы: Сервис по ремонту автомобилеи audi ауди remont-audi.ru.

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

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

Кое-что из этого даже получилось!

Скриншот - Физический симулятор

Скриншот - Физический симулятор

Идея

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

Для реализации этой задачи мы выделим следующие её аспекты и детали.

  1. За основу физического процесса примем взаимодействие в закрытом ящике в невесомости упругих шаров одинаковой массы. Будем рассматривать их столкновения между собой и со стенками ящика. Дополнительно рассмотрим сопротивление среды, влияющее на шары.
    Если говорить проще, возьмём несколько летающих шариков, засунем их в ящик и заставим беспорядочно двигаться, сталкиваясь друг с другом.
  2. Наблюдатель получит возможность рассматривать сцену как со стороны, так и с позиции каждого из шаров, с возможностью «подтолкнуть» выбранный шар в выбранном направлении.
  3. Для реализации графики будем использовать систему OpenGL, как наиболее простую и удобную. Основной код программы напишем на языке С++ с помощью Visual Studio 2008.
  4. Взаимодействия шаров, движение шаров и рендеринг сцены должны идти независимо, используя синхронизацию, и работать в реальном времени. Это является основной задачей моделирования.

Эти принципы будут положены в основу нашей программы.

Реализация

Рассмотрим основные моменты реализации программы. За подоснову для написания движка возьмём уже опробованную на искусственном интеллекте систему работы с OpenGL, и два базовых объекта — world и iface.

Основой взаимодействия в мире будет служить объект шара — c_ball. На основе этого класса будут создаваться объекты, взаимодействующие в мире.

Описание класса c_ball.

typedef class c_ball {

public:

	// Routine
	c_ball	(int id);
	~c_ball ();

	// Basics
	int	id;		// Object ID
	int	active;		// Object is Active
	int	mutex;		// Locking Mutex
	int	material;	// Object Draw Color

	// Basic Properties
	vp_t	position;	// Position
	vp_t	speed;		// Moving Vector
	float	air;		// Air Resistance
	int	size;		// Object Size
	int	mass;		// Object Mass

	// Basic Processing Functions
	int	create		(void * vworld, vp_t position);
	int	destroy		(void * vworld);
	int	process		(void * vworld);
	int	render		(void * vworld);

	// Force The Vector
	int	vector		(vp_t force);

	// Process Collision (returns new vector of collide object)
	vp_t	collide		(vp_t position, vp_t speed, float mass);

	// Process Syncronization
	void	lock		();
	void	unlock		();

} c_ball_t, *c_ball_p;

Все методы класса отвечают за реализацию базовых объектных функций. Стандартные методы конструктора и деструктора, а также их расширения (create и destroy), отвечают за первичное заполнение информации об объекте, такой как его идентификатор, активность, положение, размер и масса. Вывод объекта на экран осуществляется методом render, как и во всех аналогичных классах.

Основные задачи взаимодействия реализованы в трёх методах — process, vector и collide.

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

Метод vector изменяет текущий вектор скорости объекта, накладывая на него дополнительно силу, переданную в параметре force. Мы используем этот метод, чтобы «толкнуть» шарик.

Метод collide прорабатывает взаимодействия шариков при столкновении. Он берёт на вход данные о текущей позиции шара, его скорости и массе, и возвращает в результате новую скорость шара после взаимодействия, изменяя при этом и скорость шара-объекта. Обсчёт столкновений ведётся с помощью векторных операций. При взаимодействии учитывается масса объектов, для нахождения новых векторов движения используется закон сохранения импульса в векторной форме. Априори учитывается, что при взаимодействии сохраняется 10% от предыдущего вектора движения, и новый вектор накладывается только на 90% — примем это как коэффициент упругости.

Обработку синхронизации потоков мы реализуем с помощью системы мьютексов. У каждого обекта имеется мьютекс состояния, и две функции синхронизации — lock и unlock. Функция lock «закрывает» объект — то есть ожидает, пока объект не освободится, и занимает его, устанавливая мьютекс в положительное значение. Таким образом, обрабатывать данные объекта может только текущий процесс. После взаимодействия объект сбрасывает значение мьютекса функцией unlock.

Объект мира также претерпел некоторые изменения по сравнению со своим стандартным видом. Мы выделили отдельно две функции обработки (процессинга) — основную и функцию столкновений. Это сделано для реализации многопоточной обработки объектов.

Рассмотрим вблизи функции обработки объектов. Основным и наиболее важным отличием их от используемых мной стандартных функций обработки является применение двух важных процедур — lock и unlock. С помощью этих процедур решается задача о синхронизации о поочердном использовании ресурсов объектов. При каждом обращении к объекту функция вызывает метод lock и закрывает объект от сторонних процессов. Как только обработка заканчивается, вызывается функция unlock и объект возвращается в свободное состояние.

Обе эти функции вызываются из основного файла с помощью специальных «потоковых» функций, которые реализуют отдельные потоки обработки объектов. Это функции thread_process и thread_collider.

Они практически идентичны, рассмотрим для примера одну из них.

void __cdecl thread_process ( void *param ) {
	while (work_status) {
		if (world == null) break;
		DWORD start_time = GetTickCount();
		world->process ();
		while((GetTickCount() - start_time) < DRAW_TIMEOUT) Sleep(2);
	}
	_endthread ();
}

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

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

_beginthread ( thread_process,  null, null );
_beginthread ( thread_collider, null, null );

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

Логика работы программы проста. На старте происходит инициализация OpenGL и приложения Windows, задаются базовые параметры окна и трёхмерной среды. Перед запуском основного цикла вызываются две нити обработки объектов. Нить процессинга отвечает за базовые перемещения объекта. Последовательно обрабатываются все объекты, у каждого вызывается функция процессинга. Нить столкновений обрабатывает каждую из пар объектов. Если расстояние между объектами подходит для взаимодействия, то вызывается функция столкновения объектов. Основная нить программы обрабатывает перемещения камеры и выводит на экран объекты и каркасную модель ящика. После окончания работы программы устанавливается переменная выхода, нити взаимодействия завершаются и объект мира уничтожается.

Результаты

Скомпилировав и запустив программу мы можем насладиться результатом!

Скриншот - Физический симулятор

Скриншот - Физический симулятор

Чтобы осмотреться, используем клавиши курсора и Page Up/Down чтобы приблизить/удалить камеру. Нажав F2 переключимся в вид от лица шарика. Здесь курсор всё так уже управляет обзором, Page Up/Down помогают выбрать шарик, а пробел даёт шарику ускорение в направлении взгляда. Вернуться назад в режим обзора можно клавишей F1.

Файлы

Если Вас заинтересовала эта программа, Вы легко можете скачать исходные коды и повозиться с ней на досуге. Все материалы доступны здесь:

По любым вопросам, связанным с реализацией, обращайтесь: me@av13.ru

Автор

Автором представленного материала является Антон Резниченко aka AlterVision.