СтатьиВеб-разработка

Внедрение Ogre3D в веб-страницы
n0xi0uzz
28 октября 2009 13:21



Теги: Ogre, 3D, Java, JNLP, SWT

С помощью функциональности JNLP-апплетов в Java, SWT и Ogre4J, стало возможным внедрение Ogre в веб-страницы.

Вступление



Со времен пришествия Flash, браузерные игры всегда были популярны. Легкость в распространении и установке сделали их быстро доступными и идеально подходящими для тех, кто ищет небольшое развлечение.

Более поздние 3D-игры пошли по этому же пути в сторону браузеров. Такие плагины, как Shiva, Unity и Torque принесли в веб последние достижения в сфере 3D. Эти решения полны возможностей, но являются дорогими. Существует несколько 3D-движков на Flash, большинство из которых бесплатны, но их возможности ограничены по производительности.

Ogre — это популярный кросс-платформенный 3D-движок на C++, с привязками к нескольким другим языкам, включая Java через проект Ogre4J. С помощью Next Generation Java Plugin, довольно просто создавать Java-апплеты, которые используют собственные библиотеки, благодаря поддержке JNLP. А с помощью SWT и, в частности, классу SWT_AWT, стало возможным отображать собственное окно в апплете, а затем производить рендеринг к этому окну с помощью Ogre. В результате получаем Ogre, внедренный и эффективно работающий в веб-странице.

Шаг 1
Скачайте отсюда и разархивируйте Java-библиотеку SWT.

Шаг 2
Скачайте отсюда и разархивируйте библиотеку Ogre4J.

Шаг 3
Скачайте отсюда и установите Ogre SDK.

Убедитесь, что вы скачиваете Ogre SDK, который соответствует релизу Ogre4J. На момент написания этой статьи, последняя версия Ogre4J поддерживает Ogre 1.6.2, в то время как последний релиз Ogre под версией 1.6.4.

Шаг 4
Класс EngineManager создаст Java-апплет, элементы SWT GUI и инициализирует движок Ogre. Следующий код вы также найдете в архиве, содержащем весь исходный код (ссылка на который дана в конце статьи).
public void init()
{
   Thread thread = new Thread(new Runnable()
   {
      public void run()
      {
         try
         {
            loadSystemLibraries();
            setupGUI();
            setupOgre();
            loadResources();
            setupScene();
            enterRenderLoop();
         }
         catch (Exception ex)
         {
         }
      }
   });
   thread.start();
}

Функция init вызвана для инициализации Java-апплета. Здесь мы создаем новый поток и производим вызов разных функций, производящих инициализацию апплета. Мы запускаем Ogre в отдельном потоке, поэтому апплет отвечает на запросы, даже когда мы находимся в цикле рендеринга Ogre.
public void stop()
{
   if (display != null && !display.isDisposed())
   {
      display.syncExec(new Runnable()
      {
         public void run()
         {
           if (swtParent != null && !swtParent.isDisposed())
               swtParent.dispose();
           swtParent = null;
           display.dispose();
           display = null;
         }
      });
      remove(awtParent);
      awtParent = null;
   }
}

Функция stop вызывается при закрытии апплета. Мы используем функцию syncExec для ликвидации оконной системы SWT. Для взаимодействия с системой SWT из потока отдельно от того, который её создал (помните, что GUI устанавливается в новом потоке в функции init) вам необходимо использовать либо функцию syncExec, либо функцию asyncExec.
protected void loadSystemLibraries()
{
   System.loadLibrary("OgreMain");
   System.loadLibrary("RenderSystem_GL");
}

В функции loadSystemLibraries мы загружаем нативные библиотеки (для Windows это DLL-файлы), которые нужны нам для движка Ogre 3D.
protected void setupGUI()
{
   setLayout(new java.awt.GridLayout(1, 1));
   awtParent = new java.awt.Canvas();
   add(awtParent);
   display = new org.eclipse.swt.widgets.Display();
   swtParent = org.eclipse.swt.awt.SWT_AWT.new_Shell(display, awtParent);
   swtParent.setLayout(new org.eclipse.swt.layout.FillLayout());
   canvas = new Canvas(swtParent, SWT.NO_BACKGROUND);
   swtParent.open();
   setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
   validate();
}

В функции setupGUI мы создаем GUI, в который Ogre будет производить рендеринг:
1. Сначала мы создаем стандартный AWT-канвас.
2. Затем мы создаем SWT-объект Display, который обрабатывает взаимодействие с операционной системой.
3. Объекту Display мы добавляем Shell, который через SWT_AWT класс является окном. Класс SWT_AWT позволяет к управлению SWT добавлять управление AWT, и это будет прослойкой между нашим, базированным на AWT апплетом и окном, базированным на SWT, которая нам нужно для отображения результатов работы Ogre.
4. Затем создается SWT-объект Canvas. Объект Canvas имеет свойство handle, который мы можем передать к рендеру Ogre в качестве внешнего обработчика окна.
protected void setupOgre() throws Exception
{
   root = new Root("", "", this.getClass().getName() + ".log");
   root.loadPlugin("RenderSystem_GL");
   IRenderSystemList list = root.getAvailableRenderers();
   if (list == null || list.size() == 0)
   {
     throw new Exception("No RenderSystem loaded!");
   }
   root.setRenderSystem(list.at(0));
   try
   {
     root.initialise(false, "", "");
   }
   catch (NullPointerException e)
   {
   }
   INameValuePairList params = new NameValuePairList();
   StringPointer windowHandlePointer =
       new StringPointer(Integer.toString(canvas.handle));
   params.insert("externalWindowHandle", windowHandlePointer);
   params.insert("vsync", new StringPointer("true"));
   renderWindow = root.createRenderWindow("", 
       canvas.getBounds().width, 
       canvas.getBounds().height, false, params);
   params.delete();
   windowHandlePointer.delete();
   sceneManager = root.createSceneManager(0, "Default");
   IColourValue ambientColour = 
                   new ColourValue(0.5f, 0.5f, 0.5f, 0);
   sceneManager.setAmbientLight(ambientColour);
   ambientColour.delete();
   // Создаем камеру
   camera = sceneManager.createCamera("Main Camera");
   camera.setPosition(0, 0, 500);
   camera.lookAt(0, 0, 0);
   camera.setNearClipDistance(0.1f);
   IViewport viewport = renderWindow.addViewport(camera, 0, 0.0f,
                                            0.0f, 1.0f, 1.0f);
   IColourValue backgroundColour = new ColourValue(0.3f, 0.3f,
                                                  0.3f, 1.0f);
   viewport.setBackgroundColour(backgroundColour);
   backgroundColour.delete();
   viewport.setOverlaysEnabled(false);
   viewport.setCamera(camera);
   camera.setAspectRatio((float) canvas.getBounds().width /
                         (float) canvas.getBounds().height);
   renderWindow.setActive(true);
   canvas.addPaintListener(new PaintListener()
   {
      public void paintControl(PaintEvent e)
      {
        sceneManager.getRootSceneNode().rotate(
             new Vector3(0, 1, 0),
             new Radian(new Degree(0.1f)),
             Node.TransformSpace.TS_LOCAL);
        int height = canvas.getBounds().height;
        int width = canvas.getBounds().width;
        if (height == 0)
        {
            height = 1;
        }
        if (width == 0)
        {
            width = 1;
        }
        // перерисовка окна Ogre
        if (isDisposing == true || camera == null 
               || renderWindow == null)
        {
             return;
        }
        camera.setAspectRatio((float) width / (float) height);
        renderWindow.windowMovedOrResized();
        root.renderOneFrame();
      }
   });
   root.renderOneFrame();
}

Функция setupOrge инициализирует движок Ogre. Этот код основан на примере HelloWorld Ogre4J (функция setup).
 protected void loadResources()
{
   try
   {
     InputStream in = this.getClass().getResourceAsStream("media.zip");
     if (in != null)
     {
       int i = 0;
       String tempDir = System.getProperty("java.io.tmpdir");
       File file = null;
       do
       {
         ++i;
         file = new File(tempDir + "media" + i + ".zip");
       } while (file.exists());
       filename = file.getPath();
       OutputStream out = new FileOutputStream(filename);
       int c;
       byte[] buffer = new byte[READ_BUFFER_SIZE];
       while ((c = in.read(buffer)) != -1)
       {
         out.write(buffer, 0, c);
       }
       in.close();
       out.close();
       System.out.println("addZipFileArchive: " + file);
       ResourceGroupManager.getSingleton().addResourceLocation(file.getPath(), "Zip", "General", false);
     }
     else
     {
       System.out.println("addZipFileArchive: media.zip");
       ResourceGroupManager.getSingleton().addResourceLocation("media.zip", "Zip", "General", false);
     }
     ResourceGroupManager.getSingleton().initialiseAllResourceGroups();
   }
   catch (Exception ex)
   {
   }
}

Функция loadResources принимает zip-файл, в котором находится jar-файл, скачанный с сайта, и распаковывает его во временную директорию. Вы можете поддаться искушению просто загрузить ресурсы из jar-файла напрямую, так как jar-файл действительно просто zip-файл, а Ogre может загружать zip-файлы. Как бы то ни было, последние версии Java препятствуют нахождению расположения скачанных jar-файлов, но пока вы можете найти их, если знаете, где Java хранит свои временные файлы, будет проще просто скопировать их из jar-файла в определенную директорию.
protected void setupScene()
{
   IEntity entity = sceneManager.createEntity("dragon", "dragon.mesh");
   this.sceneManager.getRootSceneNode().attachObject(entity);
}

Функция setupScene предоставляет удобную функцию, которая может быть перегружена, для отображения 3D-объектов, создающих сцену. В данном случае, мы загружаем дракона.
protected void enterRenderLoop()
{
   while (!swtParent.isDisposed())
   {
     canvas.redraw();
     display.readAndDispatch();
   }
}

Наконец, мы входим в цикл рендеринга. В отличие от функции stop, функция enterRenderLoop вызывается из того же потока, где был создан SWT GUI, поэтому нет необходимости передавать в неё вызовы через syncexe.

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

Шаг 5
Добавьте скомпилированный файл EngineManager.class и файл media.zip, содержащий данные, которые Ogre будет отображать, в jar-файл с помощью следующей команды:
jar.exe cf ogre.jar media.zip *.class

Шаг 6
Так как мы загружаем нативные библиотеки, каждый jar-файл должен быть подписан. Для того чтобы это сделать, прежде всего, вам следует создать хранилище ключей с помощью следующей команды:
keytool -genkey -keystore myKeystore -alias myself

Шаг 7
Затем создадим сертификат:
keytool -selfcert -alias myself -keystore myKeystore

Шаг 8
Запакуйте все файлы dll, которые требуются Ogre4J для запуска, в файл с названием native.jar. Он должен включать в себя все dll-файлы из директории bin/release в Ogre SDK, а также файл ogre4j.dll из Ogre4J SDK.

Шаг 9
Каждый jar-файл должен быть подписан. В том числе:
• ogre.jar, содержащий класс EngineMaster и файл media.zip, созданный на шаге 5.
• Файл org.ogre4j_1.6.2.jar из Ogre4J SDK (название файла зависит от версии).
• Файл swt.jar из SWT SDK.
• Файл native.jar, содержащий все файлы DLL, указанные на шаге 8.

Следующая команда подпишет jar-файлы из сертификата в хранилище ключей, который вы создали в шагах 6 и 7.
jarsigner.exe -keystore myKeystore -storepass password jarfile.jar myself

Шаг 10
<?xml version="1.0" encoding="UTF-8"?>
<jnlp href="ogre.jnlp">
<information>
<title>Ogre Demo</title>
<vendor>Matthew Casperson</vendor>
<offline-allowed />
</information>
<resources>
<j2se version="1.6+" href="http://java.sun.com/products/autodl/j2se" />
<jar href="ogre.jar" main="true" />
<jar href="../commonjar/org.ogre4j_1.6.2.jar" />
</resources>
<resources os="Windows">
<nativelib href="../commonjar/swt.jar"/>
<nativelib href="../commonjar/native.jar"/>
</resources>
<applet-desc
name="Ogre Demo"
main-class="EngineManager"
width="640"
height="480">
</applet-desc>
<security>
<all-permissions/>
</security>
</jnlp>

Этот JNLP-файл определяет Java-апплет. Вы можете прочитать о формате JNLP здесь. Тем не менее, я объясню два раздела.

Первым будет тег <all-permissions> в <security>. Запрос доступа для всех (который включает функцию System.loadLibrary) требует использования подписанного jar-файла и позволяет приложению загружать нативные файлы dll.

Вторым — тег <nativelib href="native.jar"> в <resources os="Windows">. Ссылаясь на файл native.jar в теге nativelib мы сообщаем WebStart о необходимости извлечь DLL-файлы, которые содержатся в native.jar в директорию, которая будет доступна функции System.loadLibrary.

Вы также заметите, что файлы native.jar, swt.jar и org.ogre4j_1.6.2.jar сгруппированы в общей директории под названием commonjar. Это позволяет им быть доступными нескольким проектам Ogre, экономя время пользователей на их загрузку.

Шаг 11
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<title>Ogre3D</title>
</head>
<body style="margin: 0">
<applet width="640" height="480" archive="../commonjar/defaultapplet.jar" code="DefaultApplet.class">
<param name="jnlp_href" value="ogre.jnlp">
<param name="separate_jvm" value="true" />
</applet>
</body>
</html>

Это минимальный HTML-файл с внедренным Java-апплетом. Вы заметите, что мы используем и старые атрибуты archive и code в теге applet, и ссылаемся на JNLP-файл с помощью тега param. Плагин Java нового поколения будет игнорировать эти старые атрибуты и вместо них использовать JNLP-файл, в то время как старый плагин Java будет игнорировать JNLP-плагин и использовать атрибуты archive и code. В этом случае мы можем отобразить простой апплет, содержащийся в файле defaultapplet.jar, который предупредит пользователя об использовании старого плагина Java. Это особенно важно, так как новый плагин Java на данный момент поддерживает только IE и Firefox.

Присвоение параметру separate_vm значения true также решает проблему, когда между двумя Display от SWT возникает конфликт при запуске одной и той же Java VM (как будто у вас открыто два окна), перемещая каждое приложения Ogre4J в из собственную виртуальную машину.

Заключение


Возможность внедрять Ogre в веб-страницы означает, что новейшие 3D-приложения могут теперь распространятся в вебе с минимальными усилиями и без больших затрат на движки Unity или Torque. Хотя это не так просто, как с коммерческими решениями, но создав и подписав единожды необходимые jar-файлы и создав соответствующие HTML- и JNLP-файлы, в дальнейшем будет гораздо проще помещать Ogre-приложения в браузере.

Исходный код к статье


Теги: Ogre, 3D, Java, JNLP, SWT

Статьи с такими же тегами:

Введение в O3D от Google