С помощью функциональности 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-приложения в браузере. Исходный код к статье |
Статьи с такими же тегами: — Введение в O3D от Google |