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