Программируем трехмерную графику с Irrlicht . Часть 5

August 5, 2007

Эффективное программирование 3d-приложений с помощью Irrlicht и jython. Часть 5



Весь предыдущий материал нас подготавливал к этой главе. Сегодня мы попробуем применить полученные нами знания в языке python, вызывая графические функции irrlicht. Сначала займемся настройкой и установкой, аккуратно пройдите все шаги.

Подготовка и установка


 1. Вы скачали с сайта sun инсталляционный пакет jdk и установили его. 
 Предположим, что, как и у меня на компьютере, это папка: <strong>E:\Program_Files_2\j2dk1.6.0</strong>
 2. Вы скачали с сайта jython.org инсталляционный пакет jython 
 и установили его (инсталляционный пакет – это файл с расширением jar 
 – исполнимый файл для java-машины), также предположим, что это 
 была папка: <strong>E:\Program_Files_2\Jython</strong>
 3. Вы скачали с сайта <a href="http://irrlicht.sourceforge.net">http://irrlicht.sourceforge.net</a> непосредственно 3d пакет, 
 и установили в папку: <strong>E:\Program_Files_2\irrlicht-1.1</strong>
 4. Вы скачали с сайта <a href="http://jirr.sourceforge.net">http://jirr.sourceforge.net</a> биндинги для связи irrlicht 
 и java и установили его в папку: <strong>E:\Program_Files_2\jirr_0.8</strong>
 5. Теперь скопируйте в папку <em>E:\Program_Files_2\jython</em> файлы '''Irrlicht.dll, irrlicht_wrap.dll,
 3dx9.dll, d3dx8.dll<em>' из папки </em>E:\Program_Files_2\jirr_0.8\bin.'' 
 Для мотивации и увеличения энтузиазма попробуйте запустить находящиеся в этой же папке 
 файлы с расширением bat – это примеры возможностей irrlicht+jirr.
 6. создайте копию файла <strong>E:\Program_Files_2\Jython\jython.bat</strong>. Я его назвал 
 <strong>E:\Program_Files_2\Jython\jython_jirr.bat</strong>. 
Содержимое файл нужно исправить по аналогии с примером ниже:
  1. @echo off
  2. set ARGS=
  3. :loop
  4. if [%1] == [] goto end
  5.     set ARGS=%ARGS% %1
  6.     shift
  7.     goto loop
  8. :end
  9.  
  10. "E:\Program_Files_2\j2dk1.6.0\jre1.6.0\bin\java.exe" 
  11. -Dpython.home="E:\Program_Files_2\Jython" -classpath "E:\Program_Files_2\Jython\jython.jar;%CLASSPATH%;
  12. E:\Program_Files_2\jirr_0.8\lib\jirr08.jar;
  13. E:\Program_Files_2\jirr_0.8\lib\basicplayer\basicplayer2.3.jar;
  14. E:\Program_Files_2\jirr_0.8\lib\basicplayer\commons-logging-api.jar;
  15. E:\Program_Files_2\jirr_0.8\lib\basicplayer\jl1.0.jar;
  16. E:\Program_Files_2\jirr_0.8\lib\basicplayer\jogg-0.0.5.jar;
  17. E:\Program_Files_2\jirr_0.8\lib\basicplayer\jorbis-0.0.12.jar;
  18. E:\Program_Files_2\jirr_0.8\lib\basicplayer\mp3spi1.9.2.jar;
  19. E:\Program_Files_2\jirr_0.8\lib\basicplayer\tritonus_share.jar;
  20. E:\Program_Files_2\jirr_0.8\lib\basicplayer\vorbisspi1.0.jar" org.python.util.jython %ARGS%
Последняя строка, начиная с "E:\Program_Files_2\j2dk1.6.0\jre1.6.0\bin\java.exe" и до конца файла, – это одна большая сплошная строка, без абзацев. В ней я перечислил пути к библиотекам из поставки jirr. Вообще говоря, если вы хотите в среде jython использовать средства некоторой библиотеки java, то вам необходимо добавить путь к ней в секцию –classpath.

При первом запуске файла E:\Program_Files_2\Jython\jython_jirr.bat jython сообщит, что нашел новые библиотеки, выведя множество строк вида: “*sys-package-mgr*: processing new jar”, а тут непосредственно имя файла jar – библиотеки java. Затем появится знакомое по python приглашение командной строки, но уже машины jython. Попробуйте наш пример “здравствуй мир”
  1. >>> print 'hello world from java - jirr - jython'
Как видите, ничего не поменялось. Конечно, на столь простых примерах как этот, отличий jython от python вы не найдете, но ведь и мы в серьезные возможности python в статьях ранее не углублялись, а все то с чем познакомились должно работать как часы.

Далее идет пример, в котором создается окно для 3d-рендеринга, в нем выводится надпись “hello world” и пока все. Важное примечание, что по-умолчанию java и соответственно jython читают данные в кодировке windows-1251 (это поведение конечно можно изменить, но все же после), а пока сохраняйте файлы именно в windows-1251.
  1. import java
  2. import net.sf.jirr
  3. from net.sf.jirr import dimension2di
  4. from net.sf.jirr import recti
  5. from net.sf.jirr import SColor
  6.  
  7. java.lang.System.loadLibrary ('irrlicht_wrap')
  8. device = net.sf.jirr.Jirr.createDevice(
  9. net.sf.jirr.E_DRIVER_TYPE.EDT_DIRECT3D9, 
  10. dimension2di(640, 480), 32)
  11. device.setWindowCaption("Первая программа - irrlicht это круто");
  12. driver = device.getVideoDriver()
  13. smgr = device.getSceneManager()
  14. guienv = device.getGUIEnvironment()
  15. guienv.addStaticText("Привет мир, hello world",  recti(10,10,260,22), 1)
  16.  
  17. while device.run():
  18.     driver.beginScene(1, 1, SColor(255,100,101,140))
  19.     smgr.drawAll()
  20.     guienv.drawAll()
  21.     driver.endScene()
  22.  
  23. device.drop
Для запуска перейдите в каталог, где у вас установлен jython и запустите следующую команду:

E:\Program_Files_2\Jython>jython_jirr.bat "f:\kolya\programming\jirro_pojecto\src\b2.py"

Единственный аргумент, передаваемый запускаемому bat файлу jython, это путь к файлу с исходным кодом jython приведенным выше.

Результат проявился в виде окна размером 640*480 с надписью в левом верхнем угле. Да, скажем прямо, с русскими буквами не все удалось, но это мелочи, которые мы победим позже. Теперь анализ кода. В первой строке примера я подключил модуль java. Затем модуль net.sf.jirr. Следующие команды import отличаются от ранее вам знакомых. Я подключаю следующие классы из библиотеки jirr, которые наиболее часто используются.

После чего, я получаю возможность использовать их имена без указания предшествующего имени модуля в котором они объявлены. Эти классы: dimension2di – служит для задания пар двух целых чисел, recti – задает прямоугольник с целочисленными координатами, и SColor – цвет. Многие функции irrlicht-jirr получают в качестве параметров координаты объектов, размеры, цвета.

Самое главное это выполнить загрузку библиотеки irrlicht_wrap, той самой которую вы скопировали из каталога jirr в каталог jython. Обратите внимание на то, что в имени файла нет расширения irrlicht_wrap.dll. Дело в том, что java и, следовательно, jython являются кроссплатформенными технологиями так, что написанный вами код должен заработать и под тот же linux. В нем же расширения dll не приняты для библиотек в отличие от windows.

Поэтому вы указываете базовое имя библиотеки, а java сама добавляет нужное расширением и загружает ее. Следующий шаг создание устройства, куда будет выводиться графика. За это отвечает вызов функции createDevice – минимально необходимыми параметрами для нее являются указание используемого микроядра для рендеринга, в примере directx9 (под linux – это уже не работает, но есть альтернативные варианты рендеринга opengl и software, когда возможности аппаратного ускорения графики не применяются).

Затем необходимо указать размер создаваемого окна – он задается в виде пары целых чисел, затем идет глубина цвета – здесь 32 бита на пиксель. Затем получаем из созданного устройства ссылки на видеодрайвер, менеджер сущностей 3d-мира, и менеджер gui. Последний служит для создания надписей, текстовых полей, кнопок и всего прочего, что создает интерфейс для взаимодействия пользователя с созданным 3d-миром. В примере всего лишь создалась текстовая надпись в углу с координатами 10,10 (отсчет как уже принято идет от левого верхнего угла) и размером 260,22.

Следующий шаг – организация цикла обработки запросов, который выполняется до тех пор, пока окно не будет закрыто. В теле цикла начинается новая сцена с фоновым цветом задаваемым с помощью SColor. Этой функции мы подали на вход 4-е параметра. Первый из них значение alpha-канала (степень прозрачности), остальные три – стандартные RGB доли цвета. После начала сцены мы отрисовали все сущности в 3d мире с помощью вызова smgr.drawAll, затем отрисовали все элементы управления с помощью guienv.drawAll(). Наконец сцена была завершена и цикл повторен заново. После окончания цикла остается только уничтожить графическое окно и освободить все захваченные ресурсы памяти, чем и занимается device.drop.

Следующий шаг это работа со средствами 2d графики.



Для примера мы нарисуем картинку “новый год пришел” с шагающим по лесу Дедом Морозом и падающими снежинками. Разумеется, мы не сможем сделать так, чтобы Дед Мороз по-настоящему замаршировал. В примере будет статическое изображение фона леса и двигающийся от левого края экрана к правому Дед Мороз. Сверху на него при этом будут падать снежинки.

Также будут по ходу движения Деда Мороза размещены 4 елки. В сети я взял первую попавшуюся картинку леса. Для нее никаких подготовительных работ не требуется. Запомните только ее размер, у меня это изображение размером 800*600. Точно такого размера нам потребуется создать 3d-устройство с помощью createDevice. Формат изображения может быть любым: bmp|jpg|tga|png.

Изображения Деда Мороза, елки и снежинок также были взяты первые попавшиеся из internet. Но важно, что перед использованием их следует подготовить. Так фигурки должны быть размещены на прозрачном фоне. Если мы этого не сделаем, то при движении Деда Мороза вместе с ним будет перемещаться и часть фона оригинального изображения. Для того чтобы разместить Деда Мороза на прозрачном фоне есть целых две стратегии.

Вы знаете, что существует два формата хранения изображений поддерживающих альфа-канал: gif, png. Увы, но поддержки gif в irrlicht нет. А вот png поддерживается, в том числе и с прозрачными областями. Второй вариант основан на использовании любого не прозрачного изображения, но после его загрузки вы должны сказать irrlicht что некоторый его цвет будет считаться за прозрачный. Я для примера сделал Деда Мороза в формате jpg на черном фоне, а изображение елки в формате png с прозрачностью. В первом случае вам ничего кроме mspaint не потребуется, во втором нужен более серьезный графический редактор, например photoshop или что там вам нравится, лично я использовал PaintDotNet.
  1. import java
  2.  
  3. import net.sf.jirr
  4. from net.sf.jirr import dimension2di
  5. from net.sf.jirr import position2di
  6. from net.sf.jirr import recti
  7. from net.sf.jirr import SColor
  8.  
  9. java.lang.System.loadLibrary ('irrlicht_wrap')
  10. device = net.sf.jirr.Jirr.createDevice(
  11. net.sf.jirr.E_DRIVER_TYPE.EDT_DIRECT3D9, 
  12. dimension2di(800, 600), 32)
  13. device.setWindowCaption("Ded Moroz 1.0");
  14. driver = device.getVideoDriver()
  15. smgr = device.getSceneManager()
  16. guienv = device.getGUIEnvironment()
  17.  
  18. base_dir = "f:\\kolya\\programming\\dedo_morozitto\\"
  19. image_les = driver.getTexture(base_dir + "les.jpg");
  20. image_ded_moroz = driver.getTexture(base_dir + "ded_moroz_black.tga");
  21. driver.makeColorKeyTexture(image_ded_moroz, position2di(0,0));
  22. # важно указать какой цвет исходного изображения явлется прозрачным
  23. image_elka = driver.getTexture(base_dir + "elka.png");
  24. # указывать как в прошлом случае какой цвет является прозрачным не надо
  25. image_sneginka = driver.getTexture(base_dir + "sneginka.png");
  26.  
  27. time_at_start = device.getTimer().getRealTime()
  28. # запоминаем время на начало 
  29. while device.run():
  30.     driver.beginScene(1, 1, SColor(255,100,101,140))
  31.     smgr.drawAll()
  32.     guienv.drawAll()
  33.  
  34.     driver.draw2DImage(image_les, position2di(0,0) )
  35.     # рисуем фоновую картинку леса
  36.  
  37.     # дед мороз должен двигаться
  38.     # поэтому его координаты - функция времени
  39.     # скажем скорость деда мороза 1 секунда = 10 пикселей
  40.  
  41.     time_now_ms = device.getTimer().getRealTime()
  42.     # теперь узнаем, сколько времени прошло с начала запуска программы в миллисекундах
  43.     delta_ms = (time_now_ms -time_at_start) / 1000.0
  44.     # теперь delta_ms хранит интервал прошедшего времени в секундах
  45.     # раз скорость движения Деда Мороза 5 пикселей в секунду, значит  весь лес
  46.     # он должен был пройти за примерно (800 + ширина_картинки_дедушки) / 10 = 90 секунд
  47.     # следовательно когда время более чем 90 секунд то дед мороз
  48.     # должен выйти снова выйти с левого края леса
  49.     while delta_ms > 90:
  50.         delta_ms = delta_ms - 90
  51.     ded_moroz_x = -96 + delta_ms*10
  52.     driver.draw2DImage(image_ded_moroz, position2di(int(ded_moroz_x),600 - 95),
  53. 	    recti(0,0,96,95), None,
  54. 	    SColor(255,255,255,255), True
  55.     )
  56.     i = 0
  57.     while i < 4:
  58.         driver.draw2DImage(image_elka, position2di(int(i * 140 + 70),600 - 111),
  59. 	        recti(0,0,87,111), None,
  60.     	    SColor(255,255,255,255), True
  61.         )# елки равномерно распределены с шагом 140
  62.         i = 1 + i # по всей ширине сцены
  63.     i = 10 # количество снежинок
  64.     while i > 0:
  65.         driver.draw2DImage(image_sneginka,
  66.             position2di(20 + i * 130 , 10 + ((time_now_ms / 500 + i*19)  % 20)* 35),
  67.             recti(0,0,126,138), None,
  68.             SColor(255,255,255,255), True )
  69.         i = i - 1
  70.  
  71.     fps = driver.getFPS()
  72.     device.setWindowCaption("FPS: " + str(fps))
  73.  
  74.     driver.endScene()
  75. device.drop
Вначале когда я писал данный скрипт, то загрузку изображений я сделал так:
  1. image_elka = driver.getTexture("elka.png");
При этом все файлы картинок находились в той же папке, что и скрипт (у меня он называется ded_moroz.jy). При запуске сценария таким способом:

E:\Program_Files_2\Jython\jython_jirr.bat f:\kolya\programming\dedo_morozitto\ded_moroz.jy

Я получил сообщение об ошибке: “irrlicht не мог найти ни одного изображения”.

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

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

Пока же, почему я при записи пути использовал символы двойных слэшей. Дело в том, что в python|c|c++|java символ слэша в строке зарезервирован для пометки специальных символов, например, \n – переход на новую строку, \t – знак табулятора. Если бы я не написал двойных слэшей, то jython решил бы что \p, \j, \j, \k, \p, \d, \d – также являются специальными символами, а не первыми буквами каталогов пути к картинкам. При указании имени файла всегда следите за регистром букв. Файл snow.PNG в windows тоже самое, что и snow.png, но для linux – это не так.

Вызов функции: driver.makeColorKeyTexture(image_user, position2di(0,0)) необходим для того, чтобы при отрисовке изображения driver знал какой именно цвет используется как прозрачный. Здесь это цвет, находящийся в точке (0,0). Такой прием я использовал только для картинки Деда Мороза, его формат tga (jpg я не решился использовать из за плохого качества).

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

Внутри главного цикла рисуются первым фон – картинка леса. Затем идет расчет координат Деда Мороза. Основная сложность – заставить его двигаться плавно. Для этого я определяю, сколько времени прошло от момента запуска программы до текущего момента. Это разница показаний возвращаемых функцией device.getTimer().getRealTime() до начала главного цикла и каждый раз в начале его тела. Получившаяся величина хранит количество прошедших миллисекунд.

Полагая, что скорость Деда Мороза 10 пикселей за секунду я могу по школьной формуле расстояние = скорость*время вычислить координаты Деда Мороза в любой момент времени. Внимание следует обратить только на то, что по истечению 90 секунд, Дед Мороз должен уйти за правую границу экрана и появиться уже слева. Для этого и служил цикл отбрасывающий значения 90 секунд.

Для отрисовки любых картинок необходимо вызывать метод:
  1. driver.draw2DImage(image_user, position2di(координата_x,координата_y),
  2. recti(координата_x,координата_y, ширина_картинки, высота_картинки), 
  3. None, SColor(255,255,255,255), True   )
Здесь первый параметр – непосредственно рисуемое изображение. Второй это координаты области, куда оно будет выведено. Третий задает размеры исходного изображения, так вы можете рисовать не всего Деда Мороза, а только его часть, просто задав ширину менее чем 96. Четвертый параметр – задает прямоугольник отсечения (в примере он не используется, поэтому вместо параметра находится специальное ключевое слово None - нет). Пятый параметр – это цвет, которым будет закрашен прямоугольник местоназначения изображения (специальное значение, когда все компоненты цвета равны 255, говорит, что никакой расцветки выполнять не следует). И, наконец, шестой, тот ради которого были указаны все предыдущие аргументы (дело в том, что существует несколько версий функции draw2DImage отличающихся собственно количеством параметров, так в простейшем случае вы могли бы указать только изображение и точку, куда его надо вывести на экран).

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

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