Модераторы: Rickert
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Использование OpenGL в Java-приложении, статья 
:(
    Опции темы
$tatic
Дата 27.6.2007, 20:12 (ссылка) |    (голосов:4) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 651
Регистрация: 28.1.2005

Репутация: 7
Всего: 22



Cтандартные классы JRE не поддерживают работу с OpenGL. Однако для Java уже можно насчитать множество библиотек для работы с трехмерной графикой. Среди них можно выделить такие как Java 3D, JOGL, LWJGL и sdljava. После некоторого исследования автору удалось выбрать среди них одну, которой и будет уделено внимание в статье. Но полезно было бы вкратце познакомиться и с остальными библиотеками.

Итак, Java 3D – старейшая библиотека для создания трехмерных приложений в Java. Она берет свое начало в далеком 1996 (!) году, когда такие монстры как Intel, Silicon Graphics, Apple и Sun в рамках проекта Fahrenheit (не путать с названием одноименной компьютерной игры) разработали API для описания сцен при помощи графов (scene graph-based API). В дальнейшем API было перенесено на платформу Java, а позже стало открытым. Java 3D – по сути платформонезависимый и высокоуровневый (рендеринг производится через OpenGL или DirectX), обладающий огромными возможностями мультимедийный интерфейс (многие вещи в DirectX даже не снились), – однако, и он не лишен многих недостатков. Во-первых, это его тяжеловесность, т.к. интерфейс библиотеки полностью абстрагирован от платформы. Отсюда вполне закономерная медленность. Кстати именно по этой причине Sun не рекомендует эту библиотеку для разработки игр. Во-вторых, вытекающая из первого недостатка трудность освоения – работа с библиотекой напоминает работу со вполне оформленным движком. И наконец, в-третьих, текущая версия (1.5), поскольку создана с отклонением от стандарта JSR-926, находится под лицензией JRL (Java Research License), что однозначно запрещает любую разработку программ на нем, как бесплатных, так и коммерческих. И если насчет первого типа программ еще можно «отмазаться» их созданием «для самообразования», то продать программу, использующую такую библиотеку, на законных основаниях нельзя. Конечно, как только Sun завершит стандартизацию версии 1.5, она перестанет быть ограниченной в использовании. Кстати надо упомянуть об аналогичной разработке Xith3D, позиционируемой как библиотека для создания игр, поскольку скорость ее работы значительно выше.

JOGL (Java bindings for OpenGL), как и Java 3D, была создана корпорацией Sun. Однако она не граф-ориентирована и гораздо более низкоуровнева. По сути, это java-обертка вокруг интерфейса OpenGL 2.0, а следовательно для изучения можно использовать все книги и справочники по OpenGL, а также многочисленные примеры, в т.ч. из NVIDIA и ATI SDK, тем более что портирование кода из C++ будет довольно полезно в плане изучения. Но JOGL также предоставляет удобные классы для работы с текстурами и шейдерами, а кроме того, позволяет использовать и функционал графической (Java2D) и интерфейсной (AWT/Swing) части платформы, что может облегчить создание утилит с графическим интерфейсом (прикладных программ с визуализацией, редакторов для игр и т.п.). Лицензия библиотеки (BSD) никаким образом не запрещает ее использование как в бесплатных, так и в коммерческих программах. А кроме того, примеры по реализации алгоритмов на C/C++ для OpenGL практически аналогичны реализации для JOGL, что значительно облегчает ее дальнейшее использование в проектах. Кроме того, ходят слухи, что JOGL будет внедрен в состав Java 7 SE, а потому был еще более прост в использовании, т.к. не приходилось бы прилагать к приложению все версии нативных компонентов.

LWJGL (LightWeight Java Game Library) – основной конкурент JOGL. В реальных приложениях LWJGL показывает даже несколько большую производительность, чем JOGL. Но у него есть очень неприятный минус – функции и константы OpenGL в нем искусственно разнесены на классы, поименованные согласно появлению функций в стандартах OpenGL (GL11, GL12, GL13 и т.д.) или его расширениях (ARBMultitexture, ARBFragmentProgram, EXTFramebufferMultisample и т.п.). В результате все функции и константы при их применении нуждаются в лишних и запутывающих префиксах, совершенно необоснованно перегружая код (например команда NVFramebufferMultisampleCoverage.glRenderbufferStorageMultsampleCoverageNV(...), а ведь здесь еще опущены параметры). В итоге вместо написания команд, программисту придется вспоминать, в каком стандарте была введена данная функция. Очевидно, что и чтение такого кода заметно усложняется. И второй недостаток библиотеки – для работы с текстурами (а конкретно, для загрузки файлов в буфер и из него в видеокарту) предполагается использовать стороннюю библиотеку DevIL (несмотря на то, что в поставку она входит). Однако на ее изучение тоже потребуется время.

И наконец, sdljava – решение, основанное на известной в мире Linux кроссплатформенной библиотеке SDL. Последняя в базовом варианте очень похожа на сочетание DirectDraw + DirectInput и очень широко используется при написании игр (кстати, автор библиотеки является не последним человеком в знаменитой фирме Blizzard). Однако sdljava довольно слаба в плане поддержки OpenGL – реализована только базовая часть версии 1.1 без всяких расширений. Это довольно весомый аргумент не в пользу sdljava, ведь самых новомодных графических «вкусностей» мы напрочь лишены.

Таким образом, складывая удобство, простоту, скорость и качество, можно прийти к выводу, что самой оптимальной библиотекой является JOGL, об использовании которой и будет идти речь далее.

На момент написания данной статьи на официальном сайте JOGL (https://jogl.dev.java.net) можно найти сборки текущего релиза 1.0.0 (JSR-231) и сборки версии 1.1.0. Кроме того доступны «ночные» сборки 1.1.1. Т.к. в версию 1.1.0 был внесен ряд дополнений, то разумнее всего было бы использовать именно ее. Скачать ее можно, перейдя по ссылке Archived release and pre-release builds в секции Downloads. Далее следует последовательно перейти в папки Release Builds 2007 (естественно в будущем появятся новые версии) и JSR-231 1.1.0 – April 22. Следует выбрать архив для подходящей платформы (в любом архиве находятся платформонезависимые jogl.jar и gluegen-rt.jar, а также нативные JNI-библиотеки для данной платформы). Кроме того, желательно скачать также архив jogl-1.1.0-docs.zip с javadoc-документацией. Исходные коды и примеры необязательны.

Все примеры по работе с библиотеками будут показаны для JDK-1.6.0 в среде разработки NetBeans IDE 5.5.

Архив с библиотеками следует распаковать в любую папку на диске. Затем в NetBeans, для удобства дальнейшей работы, создаем новую библиотеку классов. Для этого заходим в меню Tools -> Library Manager. В окне менедежера нажимаем на кнопку New Library... Для нашей библиотеки даем название JOGL (можно задать любое, это не принципиально) и тип Class Libraries. Для новой библиотеки на закладке Classpath надо добавить архивы jogl.jar и gluegen-rt.jar, также желательно (при наличии) добавить документацию на закладке Javadoc.
Теперь создадим новый проект типа Java Application. Дадим ему, для определенности, название jogl-example1; главный класс с точкой входа будет example.jogl1.GLFrame. В дереве созданного проекта (окно Projects) в узел библиотек Libraries добавляем используемую нами библиотеку JOGL (пункт Add Library... в контекстном меню).

Поскольку JOGL использует JNI для вызова функций OpenGL, то виртуальной машине, в общем случае, необходимо будет указать путь к нативным библиотекам. Для этого при запуске приложения необходимо установить параметр java.library.path с помощью ключа -D виртуальной машины. Так, если нативные библиотеки для Windows находятся в папке C:\jogl\win, то ключ будет -Djava.library.path=C:\jogl\win\. Аналогично путь указывается и на других платформах. При этом, что самое важное, при использовании командных файлов для запуска приложения, путь может быть относителен. В NetBeans, для удобства запуска и отладки проекта встроенными средствами, данный ключ задается в свойствах проекта (Project Properties) в категории Run (поле VM Options).

После проведения данных манипуляций можно приступать непосредственно к написанию программы.

В JOGL рендеринг на экран реализуется через компоненты GLCanvas и GLJPanel. Первый является подклассом java.awt.Canvas и рассчитан на аппаратную акселерацию, полноэкранный эксклюзивный режим и прочие оптимизации для более быстрого рендеринга. GLJPanel, в свою очередь, наследует javax.swing.JPanel и рассчитан на тесную интеграцию с пользовательским интерфейсом Swing, но в ущерб производительности.

Переопределим класс GLFrame как расширяющий java.awt.Frame и добавим в него код, создающий компонент контекста GL:
Код

package example.jogl1;

import java.awt.BorderLayout;
import java.awt.Frame;
import javax.media.opengl.GLCanvas;

public class GLFrame extends Frame {
    
    GLCanvas canvas;
    
    public GLFrame() {
        canvas = new GLCanvas();
        canvas.setIgnoreRepaint(true); // Отключаем перерисовку компонента
        canvas.setSize(640, 480);
        
        setTitle("Sample OpenGL Java application");
        setResizable(false);
        setLayout(new BorderLayout());
        add(canvas, BorderLayout.CENTER);
        setSize(getPreferredSize());
    }
}

Здесь идет код обычного AWT-приложения. Все довольно просто и понятно, поэтому не будем останавливаться. Отметим только, что отключение автоматической перерисовки компонента сделано для ускорения работы приложения.

Для управления рендерингом в JOGL использована событийная концепция. Интерфейс GLAutoDrawable, который реализуют GLCanvas и GLJPanel, позволяет подключить слушателя GLEventListener. Это довольно удобная концепция, позволяющая создать блочную модель рендеринга, а также легко применить при необходимости паттерн Model-View-Controller. Однако, для первого приложения ограничимся простым примером.

Создадим класс Renderer, который будет реализовывать интерфейс GLEventListener. Среда NetBeans в данном случае может автоматически сделать реализацию необходимых методов, но это можно сделать и вручную. В результате получим следующий код:
Код

package example.jogl1;

import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;

public class Renderer implements GLEventListener {
    
    public Renderer() {
    }

    public void init(GLAutoDrawable drawable) {
    }

    public void display(GLAutoDrawable drawable) {
    }

    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
    }

    public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {
    }
    
}

Сразу же добавим экземпляр этого слушателя для объекта canvas в конструктор фрейма:
canvas.addGLEventListener(new Renderer());

Методы полученного класса очень напоминают соответствующие функции при использовании GLUT в C/C++, разберем их подробнее.

init – метод, вызываемый сразу же после инициализации контекста GL. Таким образом, в нем можно выполнить начальную настройку контекста GL, например установку источников света, переключение параметров вывода и т.п. Однако, данный метод может быть вызван несколько раз в течение работы приложения, если произошло повторное создание контекста, например при удалении и повторном вводе в иерархию компонентов объекта GLCanvas.

display – основной метод, в котором, собственно, и проходит процесс рендеринга сцены (или ее части). Именно в нем размещаются все вызовы команд GL, отвечающие за отрисовку кадра. При этом методы display всех слушателей, добавленных в обработку событий, вызываются последовательно во время отрисовки каждого кадра, а после последнего метода автоматически вызывается смена буфера (если данное действие включено) для реализации двойной буферизации.

Метод reshape вызывается при первой отрисовке компонента, изменившего размер. Он может использоваться для настройки viewport'ов, при этом для удобства по умолчанию viewport устанавливается на весь экран (вызовом glViewport(x, y, width, height)), что упрощает работу разработчика.

Последний метод – displayChanged – вызывается при смене графических параметров (графического режима или устройства вывода). Следует, однако, отметить что его реализация необязательна согласно JSR-231; в данной реализации метод также не реализован.

В JOGL нет автоматического цикла рендеринга, а потому необходимо либо писать реализацию графического цикла вручную, либо использовать функционал стандартных утилитных классов Animator и FPSAnimator. Первый из них позволяет создать цикл рендеринга в отдельном потоке, причем между вызовами метода display интерфейса GLAutoDrawable (который в дальнейшем вызывает метод display слушателей GLEventListener) либо делается небольшая задержка, либо нет. Этим режимом управляет метод setRunAsFastAsPossible. Класс FPSAnimator отличается лишь тем, что позволяет задать частоту кадров при отрисовке, однако точное соблюдение этого значения не гарантируется.

Добавим переменную для объекта аниматора в класс фрейма:
Код

private Animator anim;

И создадим его экземпляр в конструкторе фрейма:
Код

anim = new Animator(canvas);
anim.setRunAsFastAsPossible(true);

И здесь же запустим его:
Код

anim.start();

Теперь перед написанием собственно графической части кода добавим в конструктор слушателя для события закрытия окна:
Код

addWindowListener(new WindowAdapter() {
    public void windowClosing(WindowEvent e) {
        anim.stop();
        dispose();
    }
});

Остановка аниматора обязательна, т.к. в противном случае поток с циклом рендеринга не будет завершен и приложение зависнет в памяти, несмотря на то, что окно будет закрыто.
Вот что должно получиться в результате (GLFrame.java):
Код

package example.jogl1;

import com.sun.opengl.util.Animator;
import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.media.opengl.GLCanvas;

public class GLFrame extends Frame {
    
    private GLCanvas canvas;
    private Animator anim;
    
    public GLFrame() {
        GLCanvas canvas = new GLCanvas();
        canvas.setIgnoreRepaint(true);
        canvas.setSize(640, 480);
        
        setTitle("Sample OpenGL Java application");
        setResizable(false);
        setLayout(new BorderLayout());
        add(canvas, BorderLayout.CENTER);
        setSize(getPreferredSize());
        
        canvas.addGLEventListener(new Renderer());
        
        anim = new Animator(canvas);
        anim.setRunAsFastAsPossible(true);
        
        anim.start();
        
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                anim.stop();
                dispose();
            }
        });

    }
    
    public static void main(String[] args) {
        GLFrame frame = new GLFrame();
        frame.setVisible(true);
    }

}

Теперь можно попробовать запустить проект из среды командой Run Main Project (F6). Если все было сделано правильно, должно появиться пустое черное окно, которое должно корректно закрываться.

Теперь попробуем нарисовать в нашем окне чайник. Перейдем к файлу Renderer.java и добавим в метод init настройку параметров GL, а в display отрисовку чайника. Получим в результате следующий код:
Код

package example.jogl1;

import com.sun.opengl.util.GLUT;
import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;

public class Renderer implements GLEventListener {
    
    public Renderer() {
    }

    public void init(GLAutoDrawable drawable) {
        GL gl = drawable.getGL();
        GLU glu = new GLU();
        gl.glClearColor(0f, 0f, 0f, 0f);
        gl.glClearDepth(1f);
        gl.glDepthFunc(GL.GL_LESS);
        gl.glEnable(GL.GL_DEPTH_TEST);
        gl.glShadeModel(GL.GL_SMOOTH);
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        glu.gluPerspective(45f, 4f/3f, 0.1f, 100f);
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glEnable(GL.GL_LIGHTING);
        gl.glEnable(GL.GL_LIGHT0);
        gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, new float[]{1f, 1f, 1f, 1f}, 0);
        gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, new float[]{0f, 0f, 1f, 0f}, 0); // исправлено, подробности в обсуждении статьи
  }

    public void display(GLAutoDrawable drawable) {
        GL gl = drawable.getGL();
        GLUT glut = new GLUT();
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
        gl.glLoadIdentity();
        gl.glTranslatef(0f, 0f, -5f);
        glut.glutSolidTeapot(1);
    }

    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
    }

    public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {
    }
    
}

Видно, что использование функций OpenGL абсолютно прозрачно при знании классической реализации на C/C++. Необходимо лишь учитывать следующие моменты:
  • Получение интерфейса GL необходимо производить каждый раз, т.к. при контекст при работе приложения может измениться по различным причинам, упомянутым выше.
  • Все функции GL, GLU и GLUT имеют классическую форму за некоторым исключением, так при передаче вектора добавляется параметр, указывающий на начало считываемого вектора в массиве. Подробнее о правилах привязки функций можно прочитать в документации в разделе API Binding Conventions.
  • Объекты GLU и GLUT имеют встроенный запрос контекста GL, а потому создаются обычным конструктором. Но на данный момент контекст должен уже существовать.
Если теперь запустить проект, то в результате нашим глазам должен предстать плод наших трудов: знаменитый трехмерный чайник с освещением smile

Это сообщение отредактировал(а) $tatic - 2.7.2007, 18:10
PM MAIL   Вверх
$tatic
Дата 27.6.2007, 20:32 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 651
Регистрация: 28.1.2005

Репутация: 7
Всего: 22



Данную статью просьба обсуждать здесь.
PM MAIL   Вверх
$tatic
Дата 30.6.2007, 19:49 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 651
Регистрация: 28.1.2005

Репутация: 7
Всего: 22



Очень полезной вещью при написании графической программы является счетчик кадров в секунду (FPS). Сейчас мы посмотрим, как можно сделать такой счетчик в идеологии «компонентной» модели JOGL. Надо сказать, что нам понадобится пример из первой части данной статьи.

Создадим новый класс FPSMeter, реализующий уже знакомый нам интерфейс GLEventListener. Мы разместим его в пакете example.jogl2, чтобы показать независимость от первого примера. 
Расчет FPS будем производить по следующему алгоритму: считаем кадры до тех пор, пока их суммарная продолжительность не составит секунду, запоминаем полученное число кадров в секунду, обнуляем число кадров и начинаем счет заново.

Добавим для реализации данного алгоритма в класс FPSMeter 4 поля:
Код

private long t0;
private long t1;
private int fps;
private int frames;

t0 и t1 – соответственно начальное и конечное время измеряемого отрезка в миллисекундах, frames – счетчик кадров, fps – полученное число кадров в секунду.

В метод display, который будет вызываться каждый кадр, добавим код нашего алгоритма:
Код

frames++;
t1 = System.currentTimeMillis();
if (t1 - t0 >= 1000) {
    fps = frames;
    t0 = t1;
    frames = 0;
}

Теперь нам нужно показать измеренное число кадров в секунду на экране. Для этого воспользуемся замечательным классом TextRenderer из пакета com.sun.opengl.util.j2d. Он позволяет выводить заданный текст как в двухмерном, так и в трехмерном пространстве любыми шрифтами, поддерживаемыми Java. Для повышения скорости вывода в данном классе используется технология кэширования отрендеренных строк и упаковывания разных строк в единую текстуру. При этом пользоваться данным классом очень легко.

Добавим в класс поле private TextRenderer textRenderer и добавим создание экземпляра данного класса в метод init:
Код

textRenderer = new TextRenderer(new Font("Default", Font.PLAIN, 20));

В данном случае текст будет выводиться шрифтом по умолчанию, 20 точек в высоту.

Теперь добавим в метод display код отрисовки текста в двухмерной системе координат:
Код

textRenderer.beginRendering(drawable.getWidth(), drawable.getHeight());
textRenderer.setColor(1f, 1f, 0f, 1f);
textRenderer.draw(String.valueOf(fps), 0, drawable.getHeight() - 20);
textRenderer.endRendering();

Весь код отрисовки должен находиться в «скобках» beginRendering и endRendering. При этом необходимо определить систему координат текста. Левый нижний угол окна (а точнее, графического компонента GLCanvas) определяется как (0, 0), а правый верхний определяется параметрами beginRendering. В нашем случае единица измерения по осям абсцисс и ординат будет 1 пиксель. Следует отметить, что в методе draw необходимо указать координаты левого нижнего угла выводимого текста. Поэтому для вывода строки в левом верхнем углу мы вычитаем из высоты окна 20 (высоту шрифта). Отрисовка ведется желтым цветом, при этом фон текста прозрачный (включается режим GL_MODULATE).

Вот что мы должны получить в результате:
Код

package example.jogl2;

import com.sun.opengl.util.j2d.TextRenderer;
import java.awt.Font;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;

public class FPSMeter implements GLEventListener {
    
    private long t0;
    private long t1;
    private int fps;
    private int frames;
    private TextRenderer textRenderer;
    
    public FPSMeter() {
    }

    public void init(GLAutoDrawable drawable) {
        textRenderer = new TextRenderer(new Font("Default", Font.PLAIN, 20));
    }

    public void display(GLAutoDrawable drawable) {
        frames++;
        t1 = System.currentTimeMillis();
        if (t1 - t0 >= 1000) {
            fps = frames;
            t0 = t1;
            frames = 0;
        }
        
        textRenderer.beginRendering(drawable.getWidth(), drawable.getHeight());
        textRenderer.setColor(1f, 1f, 0f, 1f);
        textRenderer.draw(String.valueOf(fps), 0, drawable.getHeight() - 20);
        textRenderer.endRendering();
    }

    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
    }

    public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {
    }
    
}

На этом собственно создание компонента можно считать законченным. Теперь попробуем его отобразить. Для этого в конструктор класса example.jogl1.GLFrame после строки
Код

canvas.addGLEventListener(new Renderer());

добавим
Код

canvas.addGLEventListener(new FPSMeter());

и не забудем проверить импорт example.jogl2.FPSMeter (NetBeans предложит сделать его автоматически).

Запускаем приложение и проверяем работу счетчика.

Следует отметить, что поскольку код счетчика кадров практически не зависит от основного кода отрисовки (у автора наблюдалась ошибочная работа отрисовки текста при включенном режиме GL_TEXTURE_RECTANGLE), то его можно легко применять без изменений в другом проекте. Получается своего рода графический компонент. Отключается он, как и любой слушатель, соответствующим методом removeGLEventListener.

С помощью нескольких классов, реализующих GLEventListener, можно разграничить код рендеринга (в случае игры это может быть основной рендер мира, рендеринг HUD и т.п.), что позволит полнее использовать объектно-ориентированный подход и паттерны проектирования.
PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Программирование игр, графики и искуственного интеллекта"
Rickert

НА ЗЛОБУ ДНЯ: Дорогие посетители, прошу обратить внимание что новые темы касающиеся новых вопросов создаются кнопкой "Новая тема" а не "Ответить"! Любые оффтопиковые вопросы, заданные в текущих тематических темах будут удалены а их авторы, при рецедиве, забанены.

  • Литературу, связанную с программированием графики, обсуждаем здесь
  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы связанные с программированием графики и мультимедии на языках С++ и Delphi
  • Вопросы по реализации алгоритмов рассматриваются здесь

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Rickert.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Программирование игр, графики и искусственного интеллекта | Следующая тема »


 




[ Время генерации скрипта: 0.0945 ]   [ Использовано запросов: 20 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.