Докуметация Cтарт Статьи Форум Лента Вход
Не официальное русскоязычное сообщество
Главная
    Анимация
        Анимация

Andry

Опубликованно: AndryAndry14.11.2018, 17:07
Последняя редакция, Andry: 16.11.2018 19:04

Поддержка анимации на основе динамической обработки CSG не является одной из целей моего дизайна. Я всегда ожидал серьезного падения производительности из-за сопоставления всех поверхностей в одну фигуру в сравнении со всеми поверхностями в некоторых других заданных фигурах. Я предполагал использовать CSG во время операции загрузки, что бы потери производительности шли в рамках начальной настройки. Если бы анимация была желательной, я бы допускал, что некоторое N количество кадров должно будет построено-заранее.

Но по-прежнему забавно проверять, что CSG может делать динамически. Достаточно простой тестовый вариант состоит в том, чтобы иметь две фигуры с общим центром, вращающиеся в противоположных направлениях. Затем отобразите их пересечение. С помощью простых кубов (12 треугольников в кубе) вы получаете довольно хороший результат. Замените один из этих кубов на, до некоторой степени гладкий цилиндр, и частота кадров снижается. Вы можете сделать опрос смешанной фигуры после регенерации, чтобы получать количество наносекунд, необходимых для создания фигуры.

Настройка выполняется напрямую. Создайте две фигуры и соедините их с третьей. В цикле обновления примените вращение к двум примитивам, и затем регенерируйте смешанную фигуру. Код CSG перестроит смешение с нуля при каждом вызове регенерации.
скачать видео (которое идёт рывками из-за активной записи, но это происходит довольно плавно в ​​реальной жизни)

Прогрессивная Перестройка

Более практичная форма полуавтоматически применяет прогрессивные изменения к одной целевой фигуре. Эти изменения могут быть вызваны действием пользователя или могут быть непрерывными, двигаясь с любой скоростью, которую может обеспечить ваше железо. CSG структурирована так, чтобы вы могли легко выполнять прогрессивные изменения. Ключевым в этом является то, что возвратом вызова .regenerate() является CSGShape. Эта фигура является единственным результатом предыдущего смешивания и готова к использованию.

Типичный шаблон проектирования заключается в том, чтобы сохранить результат первоначальной .regenerate() в переменной «priorResult». Действие обновления состоит в том, чтобы сначала удалить все фигуры из вашей цели, смешать в файле priorResult, смешать любые новые изменения и затем сохранить результат этого нового .regenerate() в priorResult. Этот цикл обновления можно запустить повторно, включив любые новые изменения в каждый цикл.

Например, CSGTestM начинается с кубического блока «сыр». Цикл обновления — случайное перемещение сферической «мыши» в пределах одного квадранта блока, а затем вычитание сферы из куба. Результат станет следующей отправной точкой, так как петля продолжается, все больше и больше сыра грызут мышь. Несмотря на то, что анимация не является достаточно гладкой, она по-прежнему является эффективным дисплеем.
скачать фильм

Многопоточность

Для улучшения поддержки анимации и прогрессивной перестройки процесс сборки CSG стал известен. Регенерация была разбита на два отдельных этапа. Первый включает в себя весь тяжелый подъем анализа фигур смешивания, разрешения конфликтов и принятия решения о структуре конечного результата. Второй шаг — применить окончательную форму к активной визуальной сцене.

По умолчанию .regenerate () выполняет оба действия. Но вы можете использовать:

 myCSGSpatial.deferSceneChanges( true );

который впоследствии заставляет регенерировать выполнять только первый шаг. Изменения проводятся внутри внутри пространственного до явного применения:

myCSGSpatial.applySceneChanges();

который обновляет базовую сцену. .regenerate () и .applySceneChanges () управляются потоком и правильно синхронизируются. Это означает, что вы можете генерировать регенерацию (шаг интенсивного процессора) из фонового потока и вызывать .applySceneChanges () из стандартного потока jme3 .update (). .applySceneChanges () возвращает true, если работа была выполнена, false, если обновления не были применены.
Как часть управления потоком, основные внутренние циклы обработки контролируют Thread.interrupted (). Если такое прерывание обнаружено, процесс регенерации прерывается и генерируется исключение CSGConstructionException.

Обратите внимание, что CSGConstructionException является RuntimeException, поэтому вызывающему не требуется явно кодировать обработку исключений. java.lang.InterruptedException не используется процессом CSG.

Переведено для jmonkeyengine.ru, оригинал
Автор перевода: Andry

Я начал трудится над CSG, чтобы удовлетворить свои потребность в программных инструментах построения, которые я могу использовать в других экспериментах. Для достижения этой цели пришлось поддержке CSG несколько выйти за минимальные рамки потребности в объединение/вычитании/пересечении фигур. Ниже перечислены только некоторые возможности, реализованные для удовлетворения моих других личных требований.

Освещение

Я не ожидал, что использование Освещения будет локально привязано к примитивным фигурам, которые смешиваются вместе. В исходном коде не было предусмотрено никаких положений для моего случая, и нужное мне освещение было блаженно проигнорировано. Но я начал работать больше с внутренними пространствами, которые были результатом вычитания меньшего объекта из середины более крупного. Я знаю, что глобально действующее освещение влияет на все поверхности одинаково, без особого знания о внутреннем пространстве. Чтобы получить правильное освещение закрытого/внутреннего пространства, необходимо включить обработку теней.

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

Так как локальное освещение применяются только к элементам в пределах данного Spatial, я понял, что могу создать некоторый уровень освещения только для интерьера, если я смогу создать узел как часть процесса смешивания. Поддержка нескольких Материалов уже знает, как связать поверхность конечного продукта с исходной примитивной фигурой. Эта процедура была легко расширена, чтобы отслеживать локальное освещение, а также материалы. Конечный результат включает в себя Узел, который содержит локальное освещение, и все элементы поверхности Сетки(Mesh), которые освещаются таким освещением.

Например, если я вычитаю цилиндр из куба, я могу реализовать освещение на сам цилиндр. Это освещение распространяется только на конечные элементы внутренней поверхности, и более нигде. Хотя, конечно, это не комплексное решение того как правильно будут тени, это позволяет мне создавать некоторые хорошие эффекты. Это также хорошо работает с группами под-фигур. Чтобы создать коридор в форме арки, я группирую вместе половину-высоты Куба для дна и Цилиндр для верха. Затем я могу вычесть эту группу из внешней Куба. Добавление света в группу приводит к приятному внутреннему освещению. Добавление локального освещения к внешнему кубу означает, что я могу осветить его снаружи, без просачивания света внутрь.

CorridorLights

По умолчанию jme3-поведение состоит в том, что все освещения расположены в мировом пространстве, даже если они реализованы локально. Целью CSG является создание сложной фигуры из простых примитивов. Когда добавляются освещения, можно было бы ожидать, что преобразование в окончательную фигуру сохранит правильное освещение. Но если освещения расположены глобально, преобразование в окончательную фигуру означает, что свет больше не освещает там, где можно было бы ожидать. Jme3 решает эту проблему через LightControl, который может изменить положение Освещения, поскольку связанный с ним Spatial преобразуется. По умолчанию CSG создаст и назначит соответствующий Control всем локальным освещениям, встретившимся во время смешивания. Он использует настраиваемый CSGLightControl, который лучше поддерживает базовую структуру CSG. Но вы можете отключить эту функцию или предоставить любой другой Control для управления освещением.

Физика

Я не ожидал, что обработка Физики станет неотъемлемой частью CSG в стиле, очень похожем на Освещения. Но по мере того, как я проработал больше тестов, я сделал ставку на требование реализовывать Физику на уровне примитивов, а не пытаться добавить ее извне. В общем, гораздо проще добавить реализации Физики через XML на правильном уровне, а затем система позаботится обо всем этом для вас.

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

2D-Поверхности

Основным направлением CSG является смешивание твердых тел. Но я столкнулся с ситуацией, когда захотел поместить твёрдое тело на своем 2D полу без смешивания пола. Разумеется, нет необходимости собирать пол как твердое тело, и я надеялся использовать jme3 Поверхность(Surface) и Ландшафт(Terrain). Обработчик IOB отлично справляется с обнаружением наложения и выбором правильных треугольников для окончательной смешанной фигуры. Вычитание твердого тела и пола дает мне хорошее отверстие, в которые должно пройти твердое тело. Пересечение твердого тела и пола показывает мне, где именно происходит наложение. И объединение дает мне броские фигуры, я не уверен, что это очень полезно (но выглядит корректно).

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


Переведено для jmonkeyengine.ru, оригинал
Автор перевода: Andry

Общий

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

Простые проверки

LWJGL выполняет несколько проверок во всех привязках и внутреннем коде. Эти проверки сосредоточены главным образом на обнаружении общих ошибок, которые могут привести к сбою процесса работы JVM. Примеры:

  • Если вызывается необязательная функция (например, функция расширения OpenGL), LWJGL проверяет и выдает исключение NullPointerException, если оно недоступно.
  • Если параметр указателя функции или член структуры никогда не должен быть равен NULL, LWJGL проверяет и выдает исключение NullPointerException, если оно есть.
  • Если параметр буфер функции или член структуры должен иметь определенный минимальный размер, LWJGL проверяет и выдает исключение IllegalArgumentException, если оно есть.

Эти проверки, как правило, оказывают низкую нагрузку на систему и потому не должны оказывать заметного влияния на производительность, поэтому они включены по умолчанию, и их отключение не рекомендуется, особенно во время разработки. Для обеспечения оптимальной производительности в конечных сборках их можно отключить с помощью -Dorg.lwjgl.util.NoChecks=true или Configuration.DISABLE_CHECKS.set(true).

Отключенные LWJGL проверки не оказывают нагрузку во время выполнения. Но, судя по Java утверждениям (assertion), они увеличивают размер байт-кода окружающих методов. В редких случаях это может повлиять на принятие решений JVM и в конечном итоге повлиять на производительность. Если такая проблема была обнаружена, то в этом случае LWJGL можно собрать с удалением всех проверок, задав binding.DISABLE_CHECKS в config/build-bindings.xml в true.

Режим отладки

LWJGL выполняет дополнительные, более дорогие проверки, когда режим отладки включен с -Dorg.lwjgl.util.Debug=true или Configuration.DEBUG.set(true). Это должен быть первый вариант, который можно использовать при столкновении с LWJGL. Режим отладки также создает дополнительный выход (по умолчанию stderr, может быть переопределен с Configuration.DEBUG_STREAM) и требуется некоторыми другими инструментами устранения неполадок.

Когда вы сообщаете о проблеме LWJGL, настоятельно рекомендуется включить режим отладки и включить её вывод с описанием проблемы.

Режим отладки загрузчика общей библиотеки

Одной из наиболее распространенных проблем, с которой сталкиваются новые пользователи LWJGL, является их проект, так что общие библиотеки загружаются правильно. Включение этого режима с помощью -Dorg.lwjgl.util.DebugLoader = true или Configuration.DEBUG_LOADER.set (true) создает дополнительный вывод в DEBUG_STREAM, что очень полезно при определении проблем с загрузкой библиотеки.

Также должен быть включен режим отладки.

Режим отладки распределителя памяти

Родные библиотеки означают неактивную память, что означает повышенный риск утечки памяти по сравнению со стандартным кодом Java. Включение этого режима с помощью -Dorg.lwjgl.util.DebugAllocator = true или Configuration.DEBUG_MEMORY_ALLOCATOR.set (true) делает LWJGL отслеживать все распределения вне кучи, выполняемые через org.lwjgl.system.MemoryUtil. Когда процесс JVM завершается, любые распределения, которые не были явно освобождены, будут сообщены в DEBUG_STREAM.

Этот режим может оказать очень негативное влияние на производительность.

Режим отладки в стеке памяти
Использование org.lwjgl.system.MemoryStack прост, но может быть источником сложной идентификации проблем. Включение этого режима с помощью -Dorg.lwjgl.util.DebugStack = true или Configuration.DEBUG_MEMORY_DEBUG_STACK.set (true) делает LWJGL сообщать о любом стеке pop без соответствующего нажатия в том же методе.

Этот режим может оказать очень негативное влияние на производительность.

Привязки

Некоторые привязки предлагают специальные инструменты, которые могут быть очень полезны для устранения неполадок.

EGL

EGL-реализации могут поддерживать расширение KHR_debug. См. OpenGL ниже для получения дополнительной информации.

GLFW

GLFW поддерживает регистрацию функции обратного вызова для уведомлений об ошибках, даже до инициализации самого GLFW, с помощью функции glfwSetErrorCallback. Пример кода:

// custom
glfwSetErrorCallback((error, description) -> {
	System.err.println("GLFW error [" + Integer.toHexString(error) + "]: " + GLFWErrorCallback.getDescription(description));
});
// or shortcut that prints to DEBUG_STREAM
GLFWErrorCallback.createPrint().set();
// or shortcut that throws an exception on error
GLFWErrorCallback.createThrow().set();

// easy clean-up
glfwSetErrorCallback(null).free();

OpenAL

OpenAL-Soft, реализация OpenAL в комплекте с LWJGL, может быть настроена с переменными окружения.

Переменная ALSOFT_LOGLEVEL определяет количество протоколирования. OpenAL Soft выписывает:

Эффективно отключает все протоколирование
Распечатывает только ошибки
Распечатывает предупреждения и ошибки
Распечатывает дополнительную информацию, а также предупреждения и ошибки
То же, что и 3, но и счетчик ссылок на устройство и контекст. Это позволит распечатать много информации и, как правило, не полезно, если вы не пытаетесь отслеживать утечку ссылок в библиотеке.

OpenCL

Когда создается контекст OpenCL, может быть зарегистрирована функция обратного вызова, которая будет использоваться реализацией OpenCL для отправки информации об ошибках при создании контекста, а также ошибок, возникающих во время выполнения в этом контексте. Пример кода:

CLContextCallback contextCB = CLContextCallback.create((errinfo, private_info, cb, user_data) -> {
    System.err.println("cl_context_callback info: " + memUTF8(errinfo));
});

long context = clCreateContext(ctxProps, device, contextCB), NULL, errcode_ret);

Функция обратного вызова может быть вызвана асинхронно с помощью реализации OpenCL. Обязанностью приложения является обеспечение того, чтобы функция обратного вызова была потокобезопасной.

OpenGL

Современные версии OpenGL поддерживают очень мощный режим отладки для сообщения об ошибках и предупреждениях. Этот режим стал стандартным в OpenGL 4.3, но есть расширения, которые обеспечивают такую же функциональность в более ранних версиях (KHR_debug, ARB_debug_output, AMD_debug_output). Пример кода:

// before context creation
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);

// after context creation
glfwMakeContextCurrent(window);
GL.createCapabilities();
Callback debugProc = GLUtil.setupDebugMessageCallback(); // may return null if the debug mode is not available

// cleanup
if ( debugProc != null )
    debugProc.free();

Некоторые драйверы OpenGL предоставляют подробные информационные сообщения и предупреждения по умолчанию. Количество и тип выпускаемой продукции можно контролировать с помощью таких функций, как glDebugMessageControl.

OpenGL ES

Подобно OpenGL, OpenGL ES поддерживает режим отладки, начиная с OpenGL ES 3.2. Расширение KHR_debug также может быть доступно в более ранних версиях.

Vulkan

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

Как и в случае с Vulkan, код для такой проверки не является тривиальным. Не забудьте проверить LunarG Vulkan SDK и образец HelloVulkan LWJGL для примера.


Переведено для jmonkeyengine.ru, оригинал
Автор перевода: Andry

Каковы различные компоненты нативных привязок?

Чтобы вызвать нативную функцию, LWJGL требует как Java, так и нативный код. Давайте используем функцию glShaderSource, которая была введена в версии 2.0 спецификации OpenGL:

void glShaderSource(GLuint shader, GLsizei count, const GLchar **strings, const GLint *length)

Это void функция, которая принимает целое число без знака, целое число со знаком, указатель на массив строк и указатель на массив целых чисел со знаком. В LWJGL это преобразование происходит следующим образом:

public static native void nglShaderSource(int shader, int count, long strings, long length)

Обратите внимание на префикс ‘n’ и сопоставления типов: Как со знаком, так и беззнаковые целочисленные параметры были сопоставлены с Java int и оба указателя, сопоставлены с Java long. Мы обсудим эти сопоставления позже.

Префикс ‘n’ не является абсолютно необходимым, но добавляется для того что бы автозавершение кода в среде IDE позволяло разделить нативные и не нативные методы.

В дополнение к нативному методу, так же включены несколько обычных Java-методов:

public static void glShaderSource(int shader, PointerBuffer strings, IntBuffer length)
public static void glShaderSource(int shader, CharSequence... strings)
public static void glShaderSource(int shader, CharSequence string)

Это дружественные Java методы, которые разработчики обычно используют. Первый специализирует типы указателей и сбрасывает аргумент count, второй заменяет все с помощью параметра CharSequence vararg, а третий с одним CharSequence.

С нативной стороны все очень просто. Это реализация C указателя функции:

typedef void (APIENTRY *glShaderSourcePROC) (jint, jint, const intptr_t, const intptr_t);

и реализация функции JNI:

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_GL20_nglShaderSource(
    JNIEnv *__env, jclass clazz, // unsed
    jint shader, jint count, jlong stringsAddress, jlong lengthAddress
) {
	glShaderSourcePROC glShaderSource = /* получить указатель функции */;
	const intptr_t strings = (const intptr_t)stringsAddress;
	const intptr_t length = (const intptr_t)lengthAddress;
	UNUSED_PARAM(clazz)
	glShaderSource(shader, count, strings, length);
}
Используемые C типы не соответствуют исходной функции, но они бинарно совместимы.

LWJGL осуществляет здесь очень важный выбор о реализации; нативный код ничего не делает, кроме параметров заливки для соответствующих типов и вызывает нативную функцию. Все сложности и сопоставление с удобными для пользователя параметрами для корректных нативных данных, обрабатываются в Java-коде. Все указатели передаются как примитивные long-и, примитивные значения передаются без изменений и это все. Параметры JNIEnv и jclass никогда не затрагиваются, и никакого взаимодействия с JVM нет; никакие jobject параметры не передаются, и нет вызовов C-к-Java. Нативные методы также public, что позволяет пользователям создавать свои удобные для них методы поверх исходных, и они могут быть спокойны, что никаких проблем не будет происходит при вызове функции JNI.

Двигаясь дальше, нынешний дизайн будет делать принятие Project Panama легче для LWJGL, который будет доступен на Java 10.

Вышеупомянутое относится ко всем привязкам LWJGL. Большинство пользователей никогда не будут иметь дело с нативными методами напрямую, и они не должны, если нет веской причины; их использование по сути является небезопасным. Более опытные разработчики могут делать интересные вещи с помощью исходных указателей арифметики и утилиты org.lwjgl.system.MemoryUtil.

Что такое параметр __functionAddress, который требуется многим нативным методам?

Существует две проблемы с динамически подключаемыми библиотеками: a) указатели на функции должны быть динамически обнаружены во время выполнения и b) некоторые библиотеки могут использовать разные указатели на функции в разных контекстах. С другой стороны, код JNI является статическим, что означает, что единственный способ вызвать такие функции, это передать адреса функций до нативного кода.

Префикс ‘__’ позволяет сделать более очевидным, что это синтетический параметр.

Если нативные методы не используются напрямую, пользователям редко приходится явно передавать адрес функции. LWJGL использует специальные механизмы что бы делать это автоматически. Например, методы OpenGL используют локальную память потока для текущего контекста, а методы Vulkan используют параметр dispatchable handle для получения корректного указателя функции.

Как сопоставляются примитивные значения?

Сопоставления обычно является простым, за исключением без знаковых типов и непрозрачных указателей:

  • Без знаковые целые типы сопоставляются в Java соответствующему типу со знаком. Может показаться что это звучит как с ошибкой, но в большинстве случаев это не так. Они могут быть легко преобразованы в, и из целых чисел с высокой точностью, используя при этом простые бинарные операции. Основные арифметические операции, кроме деления, работают одинаково как для целых чисел со знаком, так и без знака. Java 8 также предоставляет полезные утилиты для их обработки.
  • Непрозрачные указатели сопоставляются с Java через long.
Непрозрачные указатели являются указателями на данные, которые имеют неопределенную структуру и обычно соответствуют внутреннему состоянию, управляемому сторонним API. Значения таких указателей просто передаются в, и выходят из API и никогда не используются для прямого доступа к памяти.

Давайте посмотрим на некоторые примеры. Следующие объявления функций:

void glDepthMask(GLboolean mask)
void glAlphaFunc(GLenum func, GLfloat ref)
void glClear(GLbitfield mask)
void glCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type)
void glVertexAttrib4s(GLuint index, GLshort v0, GLshort v1, GLshort v2, GLshort v3)
void glVertexAttrib4Nub(GLuint dinex, GLubyte x, GLubyte y, GLubyte z, GLubyte w)
void glfwSwapBuffers(GLFWwindow* window)

сопоставляется со следующими Java-методами:

public static void glDepthMask(boolean flag)
public static void glAlphaFunc(int func, float ref)
public static void glClear(int mask)
public static void glCopyPixels(int x, int y, int width, int height, int type)
public static void glVertexAttrib4s(int index, short v0, short v1, short v2, short v3)
public static void glVertexAttrib4Nub(int index, byte x, byte y, byte z, byte w)
public static void glfwSwapBuffers(long window)

Как сопоставляются указатели на примитивные значения?

Данная нативная функции объявляется так:

void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);

мы имеем следующие методы Java:

public static native void nglUniform4fv(int location, int count, long value) // A
public static void glUniform4fv(int location, FloatBuffer value) // B

В этом случае мы имеем float указатель и явно заданный параметр count, который определяет, сколько векторов vec4 следует читать из этого указателя.

В GLSL vec4 представляет собой вектор из 4 float значений. Поэтому указатель должен указывать на массив count * 4 * 4 байт.

В методе A, который является исходным методом JNI, параметр count и адрес указателя являются явными.

В методе B произошли две трансформации; a) параметр value теперь является FloatBuffer, соответствующим типу нативного указателя, и b) параметр count теперь неявный, замененный value.remaining() / 4. На 4 потому, что каждый count представляет собой вектор из 4-х значений.

Текущий Buffer.position() влияет на адрес указателя, который будет передан в нативную функцию. Если FloatBuffer обертывает указатель с адресом x, а текущая позиция равна 2, будет использоваться адрес x + 8.
Текущий Buffer.limit() определяет, сколько значений будет считано или записано в буфер. В сочетании с примечанием о Buffer.position() выше это соответствует тому, как буферы java.nio обычно используются в JDK. Единственное различие заключается в том, что LWJGL никогда не изменяет текущую позицию или ограничение(limit) буфера. Это уменьшает необходимость flip() или rewind() буферов.

Как обрабатываются параметры вывода(output)?

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

Классическим примером является glGetIntegerv, очень полезная функция OpenGL:

void glGetIntegerv(GLenum pname, GLint *data)

LWJGL включает следующие методы для этой функции:

public static native void nglGetIntegerv(int pname, long params) // A
public static void glGetIntegerv(int pname, IntBuffer params) // B
public static int glGetInteger(int pname) // C

Методы A и B аналогичны тем что были описаны выше, за исключением того, что нет явного параметра count. Если бы был параметр count, то такое же преобразование было бы применено в случае B (params.remaining() будет использоваться неявно). Для этой функции пользователь должен убедиться, что для возвращаемых данных запрошенного параметра, достаточно места.

Это, очевидно плохой дизайн, но эта функция очень старая. Современные функции OpenGL делают работу с проверкой намного лучше, и большинство из них требуют точный размеров.

Метод C является интересным. Его можно использовать, когда запрос возвращает одно значение. Без него пользователь должен будет выделить одно значение буферу, вызвать метод, а затем прочитать значение из буфера, сложная процедура. Вместо этого LWJGL сбрасывает параметр params и изменяет тип возвращаемого метода из void в int. Это делает его более естественным и удобным в использовании.

LWJGL использует org.lwjgl.system.MemoryStack для реализации таких методов, как C. Такие методы можно безопасно использовать из нескольких контекстов одновременно.

Это круто. Можно ли это сделать для единичных-значений параметров ввода(input)?

Конечно, если это полезно для какой то конкретной функции. Например:

void glDeleteTextures(GLsizei n, const GLuint *textures)

может использоваться с любым из них:

public static native void nglDeleteTextures(int n, long textures)
public static void glDeleteTextures(IntBuffer textures)
public static void glDeleteTextures(int texture) // одно значение!

Есть ли удобный способ использования String или CharSequence?

Да, LWJGL упрощает обработку текстовых данных. Соглашением для текста является то, что параметры ввода сопоставляются с CharSequence, а вывода (возвращенные) значения сопоставляются с String. LWJGL корректно обрабатывает кодировки символов, поддерживает кодирование/декодирование ASCII, UTF-8 и UTF-16 и будет использовать правильный вариант в зависимости от используемой функции. Наконец, он может так же легко обрабатывать нуль-терминированные строки и строки с точной длиной. Некоторые примеры:

GLint glGetAttribLocation(GLuint program, const GLchar *name) // A) нуль-терминированный ввод
GLubyte *glGetString(GLenum name) // B) нуль-терминированный вывод
void glGetProgramInfoLog(GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog) // C) вывод w/ точной длинны

и Java методы:

// A)
public static native int nglGetAttribLocation(int program, long name)
public static int glGetAttribLocation(int program, ByteBuffer name)
public static int glGetAttribLocation(int program, CharSequence name)
// B)
public static native long nglGetString(int name)
public static String glGetString(int name)
// C)
public static native void nglGetProgramInfoLog(int program, int maxLength, long length, long infoLog)
public static void glGetProgramInfoLog(int program, IntBuffer length, ByteBuffer infoLog)
public static String glGetProgramInfoLog(int program, int maxLength)
public static String glGetProgramInfoLog(int program)

Бонусная функция: последний glGetProgramInfoLog будет использовать glGetProgrami(program, GL_INFO_LOG_LENGTH) внутри для автоматического выделения достаточного количества память для возврата лога текста!

Как можно настроить обратные вызовы из нативного кода?

Каждый тип нативного обратного вызова сопоставляется с парой interface/abstract класса. Подобно CharSequence и String, когда тип обратного вызова является параметром ввода, он сопоставляется с interface и когда вывода (возвращаемое значение) относится к классу abstract. Это важно по двум причинам:

  • Лямбда SAM преобразования применимы только к интерфейсам. Все интерфейсы обратного вызова являются функциональными интерфейсами по определению.
  • Нативные функции обратного вызова генерируются во время выполнения. Это означает, что есть нативные ресурсы, которые должны быть сохранены как состояние и должны быть явно освобождены, если их больше не используют. Только абстрактный класс может иметь состояние и метод free().

Примером функции обратного вызова, которая может быть зарегистрирована в контексте OpenGL, является:

// Сигнатура функции:
typedef void (APIENTRY *GLDEBUGPROC)(GLenum source, GLenum type, GLuint id,
 GLenum severity, GLsizei length, const GLchar* message, GLvoid* userParam)
// Регистрация обратного вызова:
void glDebugMessageCallback(GLDEBUGPROC callback, const void *userParam)

Они сопоставляются с:

public static native void nglDebugMessageCallback(long callback, long userParam)
public static void glDebugMessageCallback(GLDebugMessageCallbackI callback, long userParam)

и интерфейс org.lwjgl.opengl.GLDebugMessageCallbackI, который реализует один метод:

void invoke(int source, int type, int id, int severity, int length, long message, long userParam);

Обратите внимание, что glDebugMessageCallback принимает GLDebugMessageCallbackI. Нативная функция обратного вызова генерируется при вызове glDebugMessageCallback и указатель на эту функцию передается в LWJGL. Это означает, что простое перенос лямбды в glDebugMessageCallback вызовет утечку памяти, потому что ничего не возвращается, что разработчик может использовать для освобождения генерируемой нативной функции обратного вызова. Существует два подхода к решению этой проблемы:

// Способ 1. Сохраните эту ссылку в переменной Java.
GLDebugMessageCallback cb = GLDebugMessageCallback.create((source, type, id, severity, length, message, userParam) -> {
    // печатать сообщение
});
glDebugMessageCallback(cb, NULL);
// потом...
cb.free();

// Способ 2: используйте glGetPointer для получения обратного вызова
glDebugMessageCallback((source, type, id, severity, length, message, userParam) -> {
    // печатать сообщение
}, NULL);
// потом...
GLDebugMessageCallback.create(glGetPointer(GL_DEBUG_CALLBACK_FUNCTION)).free();

Второй способ более удобен, но не все API-интерфейсы предоставляют способ получения указателя функции обратного вызова, который был ранее задан. В таких случаях пользователь LWJGL ответственен за сохранение ссылки обратного вызова в Java-коде.

Существует ли специальное сопоставление для нативных структур?

Да, каждый тип структуры сопоставляется с классом. Класс содержит методы создания/распределения и геттеры/сеттеры для элементов структуры. Компоновка структуры (смещения поля, sizeof, alignof) доступеа как static final int поле.

LWJGL поддерживает структуры, объединение и любую комбинацию вложений друг в друга. Смещение элементов и характеристики выравнивания/размера вычисляются во время выполнения, для обработки разности указателя размера и динамического заполнения.

Каждый класс структуры также имеет внутренний класс Buffer, который представляет массив этих структур. Класс Buffer имеет API, который совместим с буферами java.nio, а также геттеры/сеттеры, которые могут использоваться для доступа к массиву структур с приспособленец (flyweight pattern).

Пример:

typedef struct GLFWimage
{
    int width;
    int height;
    unsigned char* pixels;
} GLFWimage;
GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot);
GLFWAPI void glfwSetWindowIcon(GLFWwindow* window, int count, const GLFWimage* images);

Вышеуказанные значения сопоставляются с:

public class GLFWImage extends Struct implements NativeResource

public static long nglfwCreateCursor(long image, int xhot, int yhot)
public static long glfwCreateCursor(GLFWImage image, int xhot, int yhot) // A

public static void nglfwSetWindowIcon(long window, int count, long images)
public static void glfwSetWindowIcon(long window, GLFWImage.Buffer images) // B

GLFWImage имеет следующие статические поля:

GLFWImage.SIZEOF // sizeof(GLFWimage)
GLFWImage.ALIGNOF // alignof(GLFWimage)
GLFWImage.WIDTH // offsetof(GLFWimage, width)
GLFWImage.HEIGHT // offsetof(GLFWimage, height)
GLFWImage.PIXELS // offsetof(GLFWimage, pixels)

и следующие геттеры и сеттеры:

img.width()
img.height()
img.pixels()
Существуют также «небезопасные» статические геттеры и сеттеры, которые могут использоваться для доступа к ячейкам памяти, которые не обёрнуты в класс структуры или структурный буфер.

В приведенных выше методах A просто. Результат image.address() передается в нативный код. В B images представляет собой массив структуры GLFWimage. Обратите внимание, что он был сопоставлен с GLFWImage.Buffer, и параметр count был удален, как и в других методах, которые принимают автоматический размер java.nio буферов.

Поддержка памяти структур и структурные буферы могут быть любой памятью без кучи. MemoryStack, MemoryUtil и BufferUtil могут использоваться для распределения экземпляров объектов структуры, отвечающие характеристикам удобства использования/производительности.

Переведено для jmonkeyengine.ru, оригинал
Автор перевода: Andry

Эта страница обязательна для всех пользователей LWJGL.

Почему LWJGL использует буферы так много?

LWJGL требует использования памяти вне-кучи при передаче данных в нативные библиотеки. Аналогично, любые буферы, возвращаемые из нативных библиотек, всегда возвращаются в память вне-кучи. Это не ограничение LWJGL. Есть две проблемы с Java объектами и массивами, которые живут в куче JVM:

  • Невозможно управлять компоновкой Java объектов. Различные JVM и различные настройки JVM создают очень разные компоновки полей. С другой стороны, нативные библиотеки ожидают данные с очень точно определенными компоновками.
  • Любой Java объект или массив может быть перемещен GC в любое время, одновременно с выполнением вызова нативного метода. Все методы JNI выполняются из безопасной точки(safepoint), поэтому по определению не должны обращаться к данным кучи.

Стандартный подход:

  1. Использование JNI функций для доступа к Java объектам, что очень медленно.
  2. Использование JNI функций для «привязки» Java массивов (Get/ReleasePrimitiveArrayCritical или Hotspot Critical Natives), что также неэффективен по нескольким причинам.

С другой стороны, LWJGL предназначен для использования на прямую (вне-кучи) с java.nio буфер классами для передачи данных в и из нативного кода. ByteBuffer и другие классы не являются наилучшей из возможных абстракцией для данных вне-кучи, и их API не идеален, но это единственный официально поддерживаемый способ доступа к данным вне-кучи на Java.

Самый простой способ того как стоит рассматривать ByteBuffer, это рассматривать его как обертку поверх нативного C указателя, плюс длина массива (buffer.capacity()). LWJGL сопоставляет примитивные типы C с соответствующим классом в java.nio. Массивы указателей сопоставляются в классе org.lwjgl.PointerBuffer. Указатели на структуры сопоставляются с соответствующим структуре классом. Указатели на структуры массивов сопоставляются с соответствующим <StructClass>.Buffer классом. PointerBuffer и структура Buffer классов имеют API, очень похожий на java.nio буферы.

Какой java.nio.ByteOrder следует использовать?

Порядок байтов буфера должен быть задан в ByteOrder.nativeOrder(). Это в основном требуется для правильного кросс-платформенного поведения. Это также приводит к лучшей производительности.

Все экземпляры буфера, созданные LWJGL, всегда задаются в нативном порядке байтов.

Как распределить и освободить буферы?

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

1. Распределение стека

Java не поддерживает явное распределение стека из Java объектов, и очевидно не поддерживает распределение стека вне-кучи. В C это очень просто: вы объявляете переменную внутри функции и распределяете это стек. Когда функция возвращает, память переменной автоматически утилизируется (и без накладных расходов). На Java этому нет эквивалента.

Точно так же в Java нельзя использовать нативные функции, которые ожидают или возвращает структуру по значению. Такие функции в привязках LWJGL обернуты и отображаются с помощью параметра указатель-к-структуре или возвращаемого значения.

Это проблема, потому что очень часто требуется небольшое, короткоживущее распределение при вызове нативных функций. Например, создание объекта вершинного буфера в OpenGL в C:

GLuint vbo;
glGenBuffers(1, &vbo); // очень просто

и с LWJGL:

IntBuffer ip = ...; // здесь нужно 4-byte буфера
glGenBuffers(ip);
int vbo = ip.get(0);

Реальное распределение IntBuffer в приведенном выше примере, независимо от реализации, будет намного более неэффективным, чем указатель стека в эквивалентном C коде.

Обычный ответ на эту проблему в LWJGL 2 и других Java-библиотеках заключается в том, чтобы один раз распределить буфер, кэшировать его и повторно использовать во многих вызовах методов. Это невероятно неудовлетворительное решение:

  • Это приводит к уродливому коду и пустой трате памяти.
  • Чтобы избежать пустой траты памяти, обычно используются статические буферы.
  • Использование статических буферов приводит либо к ошибкам параллелизма, либо к снижению производительности (из-за синхронизации).

Ответ LWJGL 3 это API org.lwjgl.system.MemoryStack. Он был разработан для использования со статическим импортом и блоками try-with-resources. Вышеприведенный пример:

int vbo;
try (MemoryStack stack = stackPush()) {
    IntBuffer ip = stack.callocInt(1);
    glGenBuffers(ip);
    vbo = ip.get(0);
} // стек автоматически выталкивается, ip-память автоматически утилизируется

Это, очевидно, более многословное, но имеет следующие преимущества:

  • Обычно требуется более одного распределения, но шаблон try-with-resources остается неизменным.
  • Семантика вышеуказанного кода полностью соответствует требованиям. Память стека является поточно-локальным, подобно реальному потоку потока С.
  • Производительность идеальна. Push и pop стека это простые bumps указателя, а распределение экземпляра IntBuffer либо устраняется с помощью анализом выхода кода, либо обрабатывается следующим minor/eden GC циклом (супер эффективно).
Примечание 1: Размер стека по умолчанию — 64kb. Его можно изменить с помощью -Dorg.lwjgl.system.stackSize или Configuration.STACK_SIZE.
Примечание 2: Структуры и структура буферов также могут быть распределены на MemoryStack.
Примечание 3. Статический, поточно-локальный API MemoryStack, это просто удобно. Существует дополнительный API, который позволяет создавать и/или использовать экземпляры MemoryStack, как вы сочтете нужным.

2. MemoryUtil (malloc/free)

Иногда распределение стека не может быть использовано. Память, которая должна быть распределена, слишком велика или распределяется долгое время. В таких случаях следующим наилучшим вариантом является явно заданное управление памятью. Либо через API org.lwjgl.system.MemoryUtil, либо конкретным распределителем памяти (в настоящее время в LWJGL доступны: stdlib, jemalloc). Пример:

ByteBuffer buffer = memAlloc(2 * 1024 * 1024); // 2MB
// использовать буфер...
memFree(buffer); // освободить, когда больше не требуется
Примечание 1: Как и в C, пользователь отвечает за освобождение памяти, распределённой с помощью malloc, используя free.
Примечание 2: API для стандартных функций calloc, realloc и aligned_alloc также доступен.
Примечание 3: Объекты Java, распределённые с явно заданными функциями управления памятью, также подлежат избеганию анализу.

3. BufferUtils (ByteBuffer.allocateDirect)

Иногда API явно заданного управления памятью также не может быть использован. Возможно, данное конкретное распределение трудно отслеживать не усложняя код, или возможно не удастся точно узнать, когда оно больше не требуется. Такие случаи являются законными кандидатами на использование org.lwjgl.BufferUtils. Этот класс существовал в более старых версиях LWJGL с тем же API. Он использует ByteBuffer.allocateDirect что бы делать распределения, которые имеют одно важное преимущество: пользователю не нужно явно освобождать память кучи, это делает GC автоматически.

С другой стороны, он имеет следующие недостатки:

  • Он медленный, намного медленнее, чем вызов raw malloc. Много накладных расходов над функцией, которая уже и без того медленная.
  • Он плохо масштабируется при одновременном распределении.
  • Он произвольно ограничивает объем распределямой памяти (-XX:MaxDirectMemorySize).
  • Подобно массивам Java, выделенная память всегда обнуляется. Это не обязательно плохо, но возможность выбора будет лучше.
  • Невозможно освободить выделенную память по требованию (без JQK-специальных рефлексивных хаков). Вместо этого используется reference queue, которая обычно требует двух циклов GC для освобождения нативной памяти. Это может привести к ошибкам OOM при нагрузках.
Примером LWJGL, в котором используется BufferUtils, является распределение памяти, которое поддерживает поточно-локальные экземпляры MemoryStack. Это долгое живущее распределение, которое должно быть освобождено, когда поток умирает, поэтому мы позволяем GC позаботиться о нём.
  1. Используйте org.lwjgl.system.MemoryStack, и если это невозможно…
  2. Используйте org.lwjgl.system.MemoryUtil, и если это невозможно…
  3. Использовать org.lwjgl.BufferUtil

Я хотел бы узнать больше, есть ещё что-то для меня?

Да, прочитайте Управление Памятью в блоге LWJGL 3.


Переведено для jmonkeyengine.ru, оригинал
Автор перевода: Andry

LWJGL организован в виде модулей. Каждый модуль упакован как набор артефактов, где каждый артефакт имеет одно и то же название, но другой классификатор.

  • Название артефакта для модуля ядра LWJGL, это только lwjgl.
  • Название артефакта для модуля привязки lwjgl-<привязка>, например для модуля OpenGL будет lwjgl-opengl.

Артефакты, основанные на классификаторе:

  • Нет классификатора: скомпилированные классы Java
  • Исходники: Java исходники
  • javadoc: документация по API
  • natives-<платформа>: нативные общедоступные библиотеки для соответствующей платформы
Не все артефакты имеют нативные, и не все артефакты с нативным доступны на всех платформах.

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

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

Загрузка LWJGL — Использование конфигуратора сборки

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

Существует три отдельных варианта выпуска: Release(Релиз), Stable(Стабильный) и Nightly(Ночной). Если вы не знаете, что вам с этим делать, то тогда вы должны использовать Release, так как он будет более стабильным.

Селектор Mode позволяет выбрать, нужный вам вариант скачивания библиотеки. Если вы планируете использовать IDE или вам нужны сами .jar файлы, выберите ZIP Bundle. Если вы собираетесь использовать maven или gradle, выберите соответствующий параметр для создания сценария сборки.

Во время разработки рекомендуется использовать Maven/Gradle. ZIP Bundle рекомендуется для вашего приложения при создании сборки или установке.

Нативные(Natives) не нужны для компиляции вашего проекта, но необходимы для его запуска. Таким образом, вам нужно получить нативное для всех платформ, на которых вы хотите запустить ваше приложение. Помните, что вам не нужно загружать нативное для всех платформ сразу. Вы можете скачать дополнительных нативные, когда будете готовы распространять(продавать) свой проект.

Если вы впервые используете LWJGL, предварительные настройки(presets) Getting Started или Minimal OpenGL должны быть достаточными для начала работы с большинством обучающих программ. Кроме того, вы можете создать настраиваемую конфигурацию и выбрать именно те модули, которые вам нужны. Имейте в виду, что вы можете просмотреть описание различных модулей, отметив Show descriptions(Показывать описания) в разделе Options.

Извлечение нативных (необязательно)

Общедоступные нативные библиотеки LWJGL НЕ нужно извлекать из их .jar файлов. SharedLibraryLoader, который включен в LWJGL, делает это автоматически при выполнении.

SharedLibraryLoader извлекает нативные один раз и повторно использует их от запуска к запуску. Нативное замещается только при обнаружении другой версии LWJGL.
Папку для извлечения можно настроить с помощью Configuration.SHARED_LIBRARY_EXTRACT_DIRECTORY и Configuration.SHARED_LIBRARY_EXTRACT_PATH.

Использование SharedLibraryLoader является необязательным. Например, пользовательский установщик для приложения может извлекать туземцев в определенное место. В таких случаях привычные -Djava.library.path и -Dorg.lwjgl.librarypath (или Configuration.LIBRARY_PATH) по-прежнему полностью поддерживаются.

Настройка инструмента сборки или IDE

Gradle или maven

При использовании gradle или maven вы можете просто скопировать скрипт сборки, сгенерированный генератором, и использовать его для сборки вашего проекта. Обратите внимание, что при использовании gradle вы, вероятно, захотите добавить apply plugin: ‘java’ и apply plugin: ‘application’ в build.gradle.

IntelliJ IDEA

IDEA поддерживает Gradle/Maven проекты и делает их использование тривиальным. Для пользовательской конфигурации выполните следующие действия:

  1. Перейдите на страницу загрузки, выберите канал, выберите почтовый пакет, просмотрите параметры и выберите нужные вам привязки, затем нажмите «Создать пакет».
  2. Когда загрузка будет завершена, извлеките ее содержимое в свой проект.
  3. В IntelliJ перейдите в раздел Структура проекта> Библиотеки> Новая библиотека проектов. Добавьте все библиотеки LWJGL .jar (классы + туземцы) в библиотеку и добавьте библиотеку в качестве зависимости от вашего модуля (ов) проекта. Вам не нужно извлекать собственные библиотеки.
  4. Если вы также загрузили источники, добавьте соответствующие файлы .jar в качестве источников в библиотеку LWJGL. Вам не нужно загружать и прикреплять файлы javadoc .jar, IntelliJ генерирует javadoc непосредственно из источников.

Если вы хотите точное соответствие поведения Maven/Gradle, вы можете создать две отдельные библиотеки:

  1. Один с .jar файлами классами + исходники, сделать его компиляцией зависимостей вашего модуля(ей).
  2. Один с нативными .jar файлами, сделать его с зависимостями времени выполнения вашего модуля(ей).

Вы можете сделать это в Project Structure ▸ Modules ▸ выберите модуль вкладка Dependencies , изменив Scope каждой зависимости.

NetBeans

  1. Загрузите LWJGL в качестве ZIP-пакета с помощью конфигуратора сборки и извлеките архив туда, где вы сможете его найти позже.
  2. Внутри Netbeans в разделе «Инструменты»> «Библиотеки» нажмите «Новая библиотека» … в нижнем левом углу.
  3. Назовите библиотеку что-то разумное, например «LWJGL 3.1.0», и нажмите ОК.
  4. В центре окна библиотеки теперь должны быть параметры для настройки библиотеки.
  5. На вкладке Classpath добавьте все файлы .jar из ранее загруженного ZIP-файла, за исключением тех, которые содержат источники или javadoc в их имени.
  6. (Необязательно) На вкладке «Источники» добавьте все файлы .jar с источниками в их имени. Это даст вам доступ к документации и исходному коду непосредственно из NetBeans. Источник можно просмотреть в редакторе нажатием Ctrl+Left при наведении курсора на функцию или класс, принадлежащий LWJGL.
  7. Создайте новый проект, выбрав Файл> Новый проект …. Выберите Java | Приложение Java в качестве типа проекта.
  8. Найдите проект в окне проекта (можно открыть с помощью Ctrl-1). Щелкните правой кнопкой мыши на «Библиотеки», выберите «Импорт», найдите созданную ранее библиотеку и выберите «Импортировать библиотеку», а затем «Добавить библиотеку».
  9. Убедитесь, что все работает по назначению, добавив следующую строку в ваш основной класс и запустив проект: System.out.println (org.lwjgl.Version.getVersion ()) ;. Это должно напечатать версию LWJGL, которую вы используете.

Eclipse

(вам понадобится хотя бы Eclipse Luna(4.4) или Eclipse Kepler(4.3) с патчем «Eclipse Java 8 Support (для Kepler SR2)», установленным для работы с LWJGL 3 в Eclipse)

Eclipse поддерживает проекты Gradle/Maven, и настоятельно рекомендуется использовать их вместо ручной настройки проекта Eclipse. Однако, если вы предпочитаете настраивать собственный проект Eclipse, следуйте следующим инструкциям (работает с Eclipse Neon):

  1. Загрузите пакет ZIP с https://www.lwjgl.org/customize
  2. Когда загрузка будет завершена, извлеките ее содержимое в какой-либо каталог файловой системы, отныне называемый .
  3. В Eclipse перейдите в меню «Окно»> «Настройки» и в древовидном представлении слева найдите «Java»> «Путь сборки»> «Библиотеки пользователей»,
  4. Нажмите «Создать …» в диалоговом окне «Библиотеки пользователей». В открывшемся модальном диалоговом окне «Новая пользовательская библиотека» напишите «LWJGL3» в текстовом поле «Имя пользователя:» и нажмите «ОК». Недавно созданная библиотека «LWJGL3» должна отображаться в списке «Определенные пользовательские библиотеки:».
  5. Теперь выберите этот элемент в списке и нажмите «Добавить внешние JAR …». Это открывает стандартный диалог выбора файла ОС, позволяющий выбрать файлы * .jar, которые будут добавлены в путь к классам / путь сборки всех проектов, используя пользовательскую библиотеку LWJGL3. Перейдите в и выберите все * .jar-файлы, в которых нет -javadoc или -sources. Убедитесь, что вы не забыли файл lwjgl-natives- .jar и нажмите «Открыть».
  6. Это заполнит пользовательскую библиотеку LWJGL3 в списке соответствующими записями для всех выбранных файлов jar. Теперь вы можете оставить его, чтобы использовать LWJGL 3.
  7. Однако, если вы хотите иметь источники и JavaDocs, вам нужно будет выбрать каждую из записей, щелкнуть «Вложение источника: (Нет)» и «Изменить …». Откроется диалоговое окно «Конфигурация источника подключения». Здесь вы можете выбрать «Внешнее местоположение» и «Внешний файл …», чтобы выбрать соответствующий файл * -sources.jar.
  8. Чтобы фактически использовать пользовательскую библиотеку LWJGL3 в одном из ваших проектов, перейдите на вкладку «Путь сборки» вашего проекта и выберите вкладку «Библиотеки». Здесь нажмите «Добавить библиотеку …», выберите «Пользовательская библиотека» и отметьте «Пользовательскую библиотеку LWJGL3».
  9. Теперь вы настроены на использование LWJGL 3 в своем проекте.

Другие инструменты

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


Переведено для jmonkeyengine.ru, оригинал
Автор перевода: Andry

CSGShape можно построить из любой сетки. Никаких дополнительных примитивов, кроме тех, которые предоставляются базовыми службами jMonkey, не требуется. Однако при работе со смешанными фигурами разных текстур мне требовался более тонкий контроль над тем, как работают примитивные фигуры. И я хотел упростить построение XML. Итак, были созданы следующие.

CSGMesh

CSGMesh определяет общий подход дизайна к CSG фигурам примитивов, а также предоставляет общие сервисы для каждой конкретной фигуры. Примитив по существу строится из начала координат (0,0,0) с некоторым отклонением в зависимости от размера в сторону x, y и z. Фигура будет иметь грани, масштабирование текстуры на которых можно индивидуально контролировать, и применять пользовательские Материалы.

В частности, CSGMesh позволяет:

  • Примените различное масштабирование текстур к разным граням
  • Применяйте различные Материалы к разным граням
  • Генерировать различные уровней детализации на основе Коэффициента LOD
  • Производить TangentBinormal с информацией о освещении для Сетки после ее создания
  • Общая точка входа updateGeometry(), которая перестраивает фигуру, используя все текущие настройки

Точки java входа для сервисов выше:

CSGMesh  Описание
setFaceProperties( List pPropertyList )  Сохраните список свойств граней pPropertyList и примените их к соответствующим граням при запуске updateGeometry(). Каждый экземпляр CSGFaceProperties ссылается на выбранную грань (или грани) через битовую маску и имеет необязательное значение масштабирования текстуры (Vector2f) и/или настроенный Материал для применения к этой грани.
setLODFactors( float[ ] pLODFactors )  Сохраните набор процентных коэффициентов нагрузки, которые создают несколько VertexBuffer-ов при срабатывании updateGeometry(). Каждая конкретная фигура интерпретирует процент по-своему, решая, как наилучшим образом уменьшить количество индексов на нужную сумму. Но конечным результатом является вызов базового Mesh.setLodLevels(VertexBuffer[] pLevelsOfDetail).
setGenerateTangentBinormal( boolean pFlag )  Сохраните флаг, в true, что бы вызывалось TangentBinormalGenerator.generate(thisMesh) при вызове updateGeometry().
updateGeometry()  Создайте базовую Сетку (вершины, нормали, текстуры, индексы) из активных настроек конфигурации, а затем примените масштаб текстурирования и генерацию касательной бинормали по мере необходимости. Никакая реальная Сетка не доступна для этой фигур до тех пор, пока не будет вызвана функция updateGeometry(). Последним шагом обработки Savable.read(…) (…) для каждой CSG фигуры является вызов самой функции updateGeometry().

Грани выбираются целочисленной битовой маской, где:

0x01 - FRONT (Спереди) 
0x02 - BACK (Сзади) 
0x04 - LEFT (Слева) 
0x08 - RIGHT (Справа)
0x10 - TOP (Сверху)
0x20 - BOTTOM (Снизу)
0x40 - SIDES (Сторона)
0x80 - SURFACE (Поверхность)
и так же, можно логически комбинируя, применять этими же значениями те же свойства, к нескольким граням.

Реализация XML импорта выглядит примерно так:

    <mesh class='net.wcomohundro.jme3.csg.shape.CSGSomeShape' 
            generateTangentBinormal='true' >
        <faceProperties>
            <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='FRONT_BACK' scaleX='1' scaleY='1.0'
                materialName='Textures/Rock/Rock1Rpt.xml' />
            <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='LEFT_RIGHT' scaleX='10' scaleY='1.0'/>
            <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='TOP_BOTTOM' scaleX='1' scaleY='10.0'/>
        </faceProperties>
        <lodFactors data='0.25 0.50'/>
    </mesh>

CSGBox

CSGBox создает базовую кирпичную фигуру с заданным x, y и z размером. Ключевое различие между CSGBox и стандартным jme3 Box(Куб) — это реализация граней (FRONT/BACK/TOP/BOTTOM/LEFT/RIGHT) и возможность применять различные свойства к разным сторонам.

Точки java входа для настройки конфигурации включают все, из CSGMesh и

CSGBox  Описание
setXExtent( float pExtent )
setYExtent( float pExtent )
setZExtent( float pExtent ) 
которые устанавливают размер куба в заданном направлении.

Обратите внимание, что размер применяется в плюс/минус от начала координат. То есть, конечная ширина/высота/глубина в два раза превышает значение размера.

Реализация XML импорта выглядит примерно так:

    <mesh class='net.wcomohundro.jme3.csg.shape.CSGBox' 
            xExtent='5.0' yExtent='1.0' zExtent='1.0' >
        <faceProperties>
            <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='LEFT_RIGHT' scaleX='5.0' scaleY='1.0'/>
            <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='TOP_BOTTOM' scaleX='1' scaleY='5.0'/>
        </faceProperties>
    </mesh>

CSGBoxes

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

CSGAxialBox

CSGAxialBox — это второстепенный вариант CSGBox, где текстура применяется к граням LEFT/RIGHT/TOP/BOTTOM так же, как текстура применяется к круглым сторонам цилиндра. Это помогает при выравнивании текстур из смешанных примитивов.

Реализация XML импорта осуществляется в CSGAxialBox также как CSGBox. Просто замените одно на другое.

CSGAxial / CSGRadial

CSGAxial является расширением CSGMesh, которое определяет общий подход к проектированию примитивов CSG фигур, которые построены из серии срезов вдоль оси z. CSGRadial расширяет эту идею с каждого среза, определяемого вершинами, радиально распределенными вокруг его центра.
CSGRadialCapped — радиальная форма с плоскими торцевыми заглушками, такими как цилиндр или труба. Стандартные лицевые стороны FRONT / BACK / SIDES применяются ко всем закрытым радиальным элементам. Режим текстуры управляет тем, как текстура применяется к грани, где:

CAN  Представьте, что форма наклона, чтобы сидеть на заднем лице (например, суп). Затем X перемещается по окружности, а Y увеличивается вверх на банку.
ROLLER  Представьте, что форма повернута так, что спина находится влево, а передняя — вправо. Тогда X линейно возрастает слева направо, а Y увеличивается вдоль окружности по мере продвижения вверх.

Пункт ввода java, настраивающий радиальные:

CSGAxial/CSGRadial  Описание
setZExtent( float pZExtent )  Задайте размер формы вдоль оси z
setAxisSamples( int pSampleCount )  Задайте количество срезов для генерации вдоль оси z
setRadialSamples( int pSampleCount )  Задайте количество вершин для генерации вдоль внешней части каждого среза. Число три создает треугольную форму, число в четыре дает квадратную форму, более высокие подсчеты производят круглые формы.
setFirstRadial( float pFirstRadial )  Установите угол (в радианах) первого радиуса по оси x. Для круговых срезов это имеет минимальный эффект. Но если количество радиальных выборок невелико (3,4, …), то это определяет, где расположена первая вершина, в результате чего получается квадрат по сравнению с алмазом.
setRadius( float pRadius )  Радиус применяется к срезу, который определяет расстояние от вершины до центра.
setSliceScale( Vector2f pScaling )  x / y для применения к каждому отдельному фрагменту.
(общее масштабирование геометрии, которая содержит эту форму, создает эллиптическую, а не круговую радиальность, но затем текстура, применяемая к каждому срезу, также масштабируется. Применение шкалы к отдельному срезу сохраняет исходное отображение текстуры.)
setSliceRotation( float pTotalRotation )  Общая величина углового поворота (в радианах) от задней поверхности до передней поверхности с соответствующим дробным количеством, применяемым к каждому срезу.
setClosed( boolean pIsClosed )  Если true, то концы формы закрыты. Если ложь, построена полая форма без кончиков.
setInverted( boolean pIsInverted )  Если это правда, то форма строится с ее поверхностями, обращенными внутрь. Если ложь, то поверхности обращены наружу.

Точки входа java, конфигурирующие ограниченные радиальные:

 
setRadiusBack( float pRadius )  Радиус, применяемый к срезу задней поверхности, который определяет расстояние от вершины до центра. Когда радиус и задний радиус различаются, он будет отрегулирован на каждом срезе, чтобы обеспечить плавное прогрессирование.
setTextureMode( CSGRadialCapped.TextureMode pTextureMode )  Определите, как текстура применяется к фактам.

XML-определения импорта выглядят примерно так:

    <mesh class='net.wcomohundro.jme3.csg.shape.CSGSomeRadial' 
            zExtent='3.0' axisSamples='32' radialSamples='32' firstRadial='PI/4' 
            radius='1.1' scaleSliceX='2.0' scaleSliceY='2.0' twist='2PI' 
            radius2='1.7' textureMode='CAN' >
        ... other definitions from CSGMesh ...
    </mesh>

CSGCylinder

CSGCylinder создает основную форму цилиндра, основываясь на настройке CSGRadialCapped. Цилиндр может быть открыт или закрыт, а две торцевые крышки могут иметь разный радиус.

XML-определения импорта выглядят примерно так:

    <mesh class='net.wcomohundro.jme3.csg.shape.CSGCylinder' 
            zExtent='3.0' radius='1.1' />

CSGCylinders

Цилиндр слева применяет текстуру вокруг окружности, как CAN. В цилиндре справа применяется текстура, например, ROLLER. Оба имеют текстуру «сторон», масштабированную, чтобы приблизить шаблон на торцевых крышках.

CSGSphere

CSGSphere создает основную сферу, основанную на настройке CSGRadial. Сфера имеет только одну поверхность ПОВЕРХНОСТИ (с интегральными концами) и применяется только один основной радиус. Сфера, отмеченная не закрытой, исключает срезы северного и южного полюсов, которые сходятся к одной точке, а крошечные отверстия появляются на концах.
Поскольку синус / косинус изменяются быстрее вблизи крайних углов, шар может быть сгенерирован либо четными срезами (одинаковое расстояние на каждом шаге оси z), либо для генерации большего количества срезов с меньшим шагом оси z вблизи правого угла точки.
Режим текстуры управляет тем, как текстура применяется к полярным областям (последний раздел, сгенерированный из общей центральной точки на срез), где:

ZAXIS  Оберните текстуру радиально и вдоль оси z
PROJECTED  Оберните текстуру радиально, но сферически проецируйте вдоль оси z
POLAR  Примените текстуру к каждому полюсу. Устраняет полярное искажение, но отражает текстуру на экваторе

Точки входа в java, настраивающие сферу:

setEvenSlices( boolean pFlag )  Если true, сгенерируйте все равные шаги оси z. Если false, создайте больше фрагментов рядом с крайними крайними углами.
setTextureMode( CSGSphere.TextureMode pTextureMode )  Установите режим текстуры применительно к полярным колпачкам.

XML-определения импорта выглядят примерно так:

    <mesh class='net.wcomohundro.jme3.csg.shape.CSGSphere' 
            zExtent='3.0' radius='1.1' useEvenSlices='false' textureMode='ZAXIS' />

CSGSpheres

Сфера слева использует режим текстуры ZAXIS. Сфера в центре использует режим PROJECTED texture. Сфера справа использует режим POLAR texture.

CSGPipe

CSGPipe создает цилиндрическую форму, ось z которой соответствует заданному сплайну, а не прямой. Все настройки CSGRadialCapped применяются. Труба может быть открыта или закрыта, а две торцевые заглушки могут иметь разный радиус. Ключевым параметром является сплайн, используемый для генерации центральных точек оси z для каждого среза. Ожидается, что срез будет перпендикулярен его центральной точке на каждом интервале. Поскольку одна точка не имеет перпендикуляра, мы строим перпендикуляр к прямой между текущей центральной точкой и следующей. Это означает, что срезы концевой крышки могут быть очень чувствительны к структуре сплайна. Для обработки некоторых странностей различные настраиваемые параметры PipeEnd поддерживаются там, где:

STANDARD  Конечный срез генерируется «нормально», перпендикулярно последней точке кривой
PERPENDICULAR  Конечный срез генерируется перпендикулярно осям x / y / z
PERPENDICULAR45  Конечный срез генерируется перпендикулярно / 45 градусов по оси x / y / z
CROPPED  Конечные точки сплайна НЕ производят срез, они влияют только на последний срез

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

Точки входа в java, настраивающие канал:

setSlicePath( Spline pCurve )  Предоставьте сплайн jme3, который определяет положение каждой центральной точки среза.
setSmoothSurface( boolean pFlag )  Если true, сканируйте каждый фрагмент, ища столкновение со своим соседом. Если происходит перекрытие, примите меры, чтобы отрегулировать срез, чтобы устранить столкновение.
setPipeEnds( CSGPipe.PipeEnds pEnds )  Управляйте концами трубы.

XML-определения импорта выглядят примерно так:

    <mesh class='net.wcomohundro.jme3.csg.shape.CSGPipe' 
            pipeEnds='STANDARD' smoothSurface='false >
    	<slicePath class='net.wcomohundro.jme3.csg.shape.CSGSplineGenerator'  arc='PI'/>
    </mesh;>

CSGPipes

Слева направо: сплайн, тор, спираль.

CSGSplineGenerator (вспомогательный класс)

CSGSplineGenerator не является формой. Скорее, это вспомогательный класс, который может помочь в создании сплайна, используемого CSGPipe. Сплайн может быть определен:

  • Внешне определенный экземпляр сплайна
  • Явный набор точек
  • Набор контрольных точек, интерпретируемых на основе выбранного SplineType
  • Набор созданных точек вокруг дуги окружности с дополнительной регулировкой высоты, которая создает спираль, а не тор.

Точки входа в java, настраивающие канал:

setSpline( Spline pSpline )  Используйте снайперский сплайн, как указано.
setPointList( List pPointList )  Используйте заданный набор точек, а не сплайн.
setArcRadius( float pRadius )  Создайте дугу заданного радиуса.
setArcRadians( float pRadians )  Создайте дугу данного угла (в радианах). Значение 2Pi будет генерировать полный тор. Значение, большее 2Pi, имеет смысл только для спирали.
setArcFirstRadial( float pRadial )  Создайте дугу, начинающуюся с точки с заданным углом (в радианах).
setHelixHeight( float pHeight )  Создайте спираль, которая охватывает заданную высоту.

XML-определения импорта выглядят примерно так:

    <slicePath class='net.wcomohundro.jme3.csg.shape.CSGSplineGenerator' 
    		radius='1.5' arc='PI' firstRadial='PI/4' helix='1.75' />
    		
    <slicePath class='net.wcomohundro.jme3.csg.shape.CSGSplineGenerator'  
    		type='Bezier' curveTension='0.5f' cycle='false'>
        <controlPoints>
            <com.jme3.math.Vector3f x='0.0' y='0.0' z='1.5'/>
            <com.jme3.math.Vector3f x='0.45' y='0.0' z='0.75'/>
            <com.jme3.math.Vector3f x='0.45' y='0.0' z='-0.75'/>
            <com.jme3.math.Vector3f x='00' y='0.0' z='-1.5'/>
        </controlPoints>
    </slicePath>

CSGTwisted

Образец тора, где каждый срез масштабируется в x / y для создания эллипса, срезы скручиваются спереди назад, а радиус начала отличается от радиуса конца.

CSGSurface
CSGSurface — это не сплошная, а двухмерная поверхность, используемая в качестве пола. Он похож на механизм jme3 Terrain, но не имеет поддержки LOD Terrain. Он работает, создавая сетку из стандартных данных jme3 HeightMap, с экстентами в X / Z, с высотой в Y.

Точки входа в java, настраивающие поверхность:

setExtent( int pSizeOfSquareArea )  Ширина и глубина области (требуется 2 ** N + 1)
setHeightMap( float[] pHeightMap )  Высота каждой точки данных.
setScale( Vector3f pScale )  Шкала, применяемая ко всем точкам данных.

XML-определения импорта выглядят примерно так:

    <mesh class='net.wcomohundro.jme3.csg.shape.CSGSurface' extent='129'>
        <faceProperties>
            <net.wcomohundro.jme3.csg.shape.CSGFaceProperties 
					face='SURFACE' scaleX='1032' scaleY='1032'/>
        </faceProperties>
        <heightMap class='net.wcomohundro.jme3.csg.shape.CSGHeightMapGenerator'
            			type='HILL' size='129' scale='0.025' seed='12345' />
    </mesh>

CSGHeightMapGenerator (вспомогательный класс)

CSGHeightMapGenerator — это не форма. Скорее, это вспомогательный класс, который может помочь в построении данных HeightMap, используемых CSGSurface. Он включает поддержку основных классов jme3 в com.jme3.terrain.heightmap. В частности, типы:

DISPLACEMENT  uses MidpointDisplacementHeightMap
FAULT  uses FaultHeightMap
FLUID  uses FluidSimHeightMap
HILL  uses HillHeightMap
PARTICLE  uses ParticleDepositionHeightMap

XML-определения импорта выглядят примерно так:

    <heightMap class='net.wcomohundro.jme3.csg.shape.CSGSplineGenerator'  
    		type='HILL' size='257' iterations='100' seed='0'
    	... type specific parameters here, @see the code itself ...
    />

CSGFaceProperties (вспомогательный класс)

CSGFaceProperties — это не форма. Скорее, это вспомогательный класс, который может помочь в применении пользовательских материалов и масштабирования текстур для разных лиц в CSGMesh. В большинстве случаев CSGMesh играет роль стандартного jme3 Mesh. И в то время как Mesh понимает свое собственное сопоставление координат текстуры, он не знает о Материале, который применяется. Материал определяется и применяется геометрией, содержащей Mesh.

Но в рамках CSG становится очень удобно, если базовый CSGMesh может связать другой материал с разными лицами. Например, скажем, вы собираетесь определить комнату, вычитая меньшую внутреннюю коробку из немного большей внешней коробки. Если вы хотите, чтобы различные материалы применялись к полу по сравнению с потолком по сравнению с стенами, вы застряли, используя какое-либо соглашение о сопоставлении материалов или используя несколько примитивов для представления различных компонентов.

Поскольку CSGMesh понимает пользовательские материалы, сопоставленные с его различными лицами, и поскольку CSGGeonode понимает работу с несколькими Материалами, становится довольно легко определить конкретные для лица материалы непосредственно на CSGBox, и пусть стандартная обработка CSG применит соответствующий материал к соответствующим поверхностям.

Обратите внимание, что для основной обработки jme3 CSGBox — это просто Mesh. Нет основного процесса, чтобы заметить, что пользовательские материалы были определены на уровне Mesh. Но обработка CSGShape понимает несколько Материалов, применяемых к примитивам, и особенно ищет CSGMesh. Поэтому, если вы включаете CSGBox с пользовательскими Материалами с узлом Geometry jme3, эти Материалы игнорируются. Но если вы включите тот же CSGBox через CSGShape, добавленный в CSGGeonode, тогда используются пользовательские материалы.

CSGFaceProperties также используется для управления масштабированием текстуры и позиционированием, которое применяется к лицу. Это касается вопроса о ящике, продолговатом в z, но не в x и y. В этом случае передняя и задняя поверхности должны сохранять масштабирование текстуры единицы. Но левая / правая / верхняя / нижняя грани должны быть масштабированы соответствующим образом, чтобы предотвратить растяжение текстуры.

Помимо масштабирования текстуры, вы также можете управлять ее началом и интервалом. Обычно текстура проходит от 0.0 до 1.0 на лице. Масштабирование может учитывать лицо другого размера. Но при смешивании примитивов вы можете захотеть, чтобы текстура подкомпонента совпадала с текстурой более крупного компонента. Подумайте о создании кубиков с пипсами. Вы можете создать такой элемент, вычитая меньший цилиндр несколько раз из большего куба. Но если вы хотите, чтобы структура текстуры была непрерывной по пунктам, вам необходимо установить соответствующий источник текстуры для каждого цилиндра.

Помимо текстуры, вы можете управлять физическими свойствами на лице. На примере строительства комнаты вам могут понадобиться упругие стены, а также инертный пол. Это можно сделать, определив разные физические свойства для разных лиц.

В любом случае CSGFaceProperties применяется к лицу или граням, определяемому битовой маской всех вовлеченных лиц.

XML-определения импорта выглядят примерно так:

    <faceProperties>
	    <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='FRONT_BACK' scaleX='1' scaleY='1.5'
		        materialName='Textures/Rock/Rock1Rpt.xml'/>
        <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='LEFT_RIGHT' scaleX='2' scaleY='1.5'
		        materialName='Textures/BrickWall/BrickWallRpt.xml'/>
        <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='TOP_BOTTOM' scaleX='1' scaleY='2.0'/>
    </faceProperties>

Смотрите так же:


Переведено для jmonkeyengine.ru, оригинал
Автор перевода: Andry

Цель дизайна CSG заключается в том, чтобы сделать обработку логически максимально простой. В основе CSGSpatial лежит стандартный jme3 Spatial и он добавляется в вашу сцену, так же как и любой другой Spatial. Как к Spatial, к нему может также применяться стандартная Физика. Вы начинаете с создания одного из вариантов CSGSpatial (CSGGeometry, CSGGeonode).

Затем вы добавляете/вычитаете/пересекаете некоторый набор твердых тел к этому CSGSpatial. Каждое твердое тело представлено CSGShape, которое в главным образом представляет из себя CSG-обертку вокруг некоторой произвольной Сетки(Mesh). Эта Сетка может предоставлять собой jme3 примитив (Куб, Сферу, …), CSG примитив (CSGBox, CSGSphere, …) или что-то еще, что реализует Сетку.

После того, как твердые тела были смешаны в CSGSpatial, CSGSpatial должен быть «регенерирован». Это может быть сделано программным вызовом метода .regenerate( ), или это по сути, делается в конце обработки ввода Savable.

Последний шаг — добавить Spatial в вашу сцене, а также любую обработку control (например, физику), которая может потребоваться. На данный момент CSGSpatial не должен отличаться от любого другого jme3 Spatial, которые вы используете.

CSGSpatial

CSGSpatial — абстрактный интерфейс, определяющий стандартные CSG операции. Он реализует:

  • CSGGeometry — простая обёртка, поддерживающая единый общий Материал, применяемый ко всем частям
  • CSGGeonode — обертка, которая содержит набор Материалов, каждый из которых взят из различных фигур, смешанных с этим spatial
  • CSGLinkNode — вариант, полезный для обработки импорта Savable, который может обеспечить общую среду и/или Материал другим CSGSpatial-ам, созданным во время процесса импорта, и которые могут запустить загрузку таких игровых ресурсов. Однако он ничего не делает с CSGShape-ами.

Общими точками java входа для служб CSGSpatial являются:

CSGSpatial  Описание
addShape (CSGShape pShape)  Добавьте данную фигуру в процесс смешивания фигур с операцией Булево объединение(UNION).
subtractShape (CSGShape pShape)  Добавьте данную фигуру в процесс смешивания фигур с операцией Булева разность(DIFFERENCE).
intersectShape (CSGShape pShape)  Добавьте данную фигуру в процесс смешивания фигур с операцией Булево пересечение(INTERSECTION).
addShape (CSGShape pShape, CSGOperator pOperator)  Добавьте данную фигуру в процесс смешивания через явно заданную операцию.
removeAllShapes( )
removeShape( CSGShape pShape ) 
Удалить ранее добавленную фигуру из процесса смешивания.

Обратите внимание, что это НЕ вычитание. После удаления фигура уже не является какой либо частью смешанной фигуры.

regenerate( )
regenerate( CSGEnvironment pEnvironment ) 
Применит всю обработку активной фигуры и создаcn сетку. Если не будет явно указано CSGEnvironment, то будет использоваться стандартная среда системы.
Полученная в результате CSGShape возвращается .regenerate() и полученное смешение можно пере-смешивать что бы получать другой CSGSpatial.
isValid( )  После регенерации определяет, была ли действительно произведена Сетка или нет. Если значение недействительно, информация об ошибке доступна через .getError();
getShapeRegenerationNS( )  После регенерации возвращайте количество наносекунд в виде long, которое потребовалось для создания конечной Сетки.

Некоторые точки входа, реализованные в Геометрии, были добавлены в интерфейс CSGSpatial, так что Материалы и Уровень Детализации(LevelOfDetail) могут поддерживаться единообразно:

CSGSpatial  Описание
getMaterial()
setMaterial( Material pMaterial ) 
Аксессоры для контроля Материала, который применяется к этому Spatial.
getLodLevel()
setLodLevel( int pLODLevel ) 
Аксессоры для контроля Детализации(LevelOfDetail), который применяется к этому Spatial.

Реализация XML импорта выглядят примерно так:

<net.wcomohundro.jme3.csg.CSGLinkNode fname='CSGSamples'>
    <lights class='com.jme3.light.LightList'>
        <lights size='1'>
        	<com.jme3.light.AmbientLight name='ALight' enabled='true'>
        		<color class='com.jme3.math.ColorRGBA' r='1' g='1' b='1' a='1'/>
        	</com.jme3.light.AmbientLight>
        </lights>
    </lights>
    <children>
        <net.wcomohundro.jme3.csg.CSGGeonode name='BumpyCube'
        			materialName='Textures/Debug/Normals.xml'>
            <shapes>
                <net.wcomohundro.jme3.csg.CSGShape name='Box'>
                    <mesh class='net.wcomohundro.jme3.csg.shape.CSGBox' 
                    		xExtent='1.0' yExtent='1.0' zExtent='1.0'/>
                </net.wcomohundro.jme3.csg.CSGShape>
                
                <net.wcomohundro.jme3.csg.CSGShape name='Sphere' operator='UNION'>
                    <mesh class='net.wcomohundro.jme3.csg.shape.CSGSphere'
                    	 	axisSamples='64' radialSamples='64' radius='1.2'/>
                    <transform class='com.jme3.math.Transform'>
                        <translation class='com.jme3.math.Vector3f' x='0' y='0' z='0'/>
                    </transform>
                </net.wcomohundro.jme3.csg.CSGShape>
            </shapes>
        </net.wcomohundro.jme3.csg.CSGGeonode>
    </children>
</net.wcomohundro.jme3.csg.CSGLinkNode>

КБГФигура(CSGShape)

CSGShape предоставляет обертку, поддерживающую CSG, поверх jme3 Сетки. Программно тяжелая работа происходит в конструкторе, где фигура CSGShape создается с названием и Сеткой. Затем фигура добавляется в CSGSpatial вместе с оператором смешивания.

С точки зрения импорта XML оператор задаётся в самой CSGShape фигуре. См. пример выше …

Вы часто используете Transform при реализации CSGShape фигур для размещения, поворота и/или масштабирования элемента до его добавления в смешивание. Перемещение и масштабирование векторов довольно легко понять и настроить. Но XML вращение Кватерниона основано на внутренних значениях x/y/z/w, которые для человеческих глаз совершенно бессмысленны. Чтобы сделать XML более выразительным, вы можете использовать CSGTransform и CSGQuaternion. CSGTransform примет поворот реализованный как «com.jme3.math.Quaternion» или реализованный как «net.wcomohundro.jme3.math.CSGQuaternion». С помощью CSGQuaternion вы указываете тангаж(pitch)/рысканье(yaw)/крен(roll) в градусах радиана. Вы также можете использовать конструкцию PI и написать pitch=″PI/2″, чтобы выполнить тангаж фигуры на 90° относительно оси X.

Реализация XML импорта transform для CSGShape может выглядеть так:

        <csgtransform class='net.wcomohundro.jme3.math.CSGTransform'>
            <translation class='com.jme3.math.Vector3f' x='0' y='0' z='0'/>
            <scale class='com.jme3.math.Vector3f' x='0' y='0' z='0'/>
            <rot class='net.wcomohundro.jme3.math.CSGQuaternion' yawl='PI/2' pitch='PI/32' roll='PI'/>
        </csgtransform>

В реализации XML импорта вы можете найти более логичным, работать с группами под-элементов. Например, вы планируете создать коридор, который представляет собой растянутый куб, из которой вы вычитаете арку. Это можно сделать, начав с Куба, из которого вы вычитаете Куб с половинной высотой, чтобы получить нижнюю часть выреза арки, а затем вычитаете Цилиндр, чтобы вырезать верхнюю дугообразную часть полости. Но вы должны быть очень аккуратны, чтобы было соответствие размеров/масштаба/положения меньшего куба и цилиндра, чтобы все было выровнено как надо.

Наилучший подход — создать единую сущность, который представляет верхнюю дугу, смешанную с квадратным дном. Вы можете работать в простой блочной среде, чтобы все соответствующие текстуры были выровнены. Затем отмасштабируйте эту сущность как это нужно и вычтите его из большего куба. Вы можете это сделать с помощью определения <shapes> в CSGShape. Вместо того, чтобы предоставлять простую Сетку(Mesh), реализуйте набор CSGShape-ов (вместе с соответствующими им логическим операторами) в родительском CSGShape. Обработка внутренних под-элементов происходит до того, как будет всё смешано в родительской фигуре. Это позволяет вам применить преобразования на родительском уровне, чтобы получить желаемый масштаб и положение.

Реализация XML импорта для под-элементов CSGShape может выглядеть так:

    <net.wcomohundro.jme3.csg.CSGGeonode name='CSGGeometry' materialName='Textures/BrickWall/BrickWallRpt.xml' >
        <shapes>
            <net.wcomohundro.jme3.csg.CSGShape name='OuterBox'>
                <mesh class='net.wcomohundro.jme3.csg.shape.CSGBox' 
                    	... реализация внешнего куба здесь ...
            </net.wcomohundro.jme3.csg.CSGShape>
                
            <net.wcomohundro.jme3.csg.CSGShape name='InteriorArch' operator='DIFFERENCE' >
                <shapes>
                    <net.wcomohundro.jme3.csg.CSGShape name='SquareBottom' operator='UNION'>
                        <mesh class='net.wcomohundro.jme3.csg.shape.CSGAxialBox' 
                                    xExtent='0.5' yExtent='0.25' zExtent='0.5'>
                            <faceProperties>
                                <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='FRONT_BACK' scaleX='1' scaleY='0.5'/>
                                <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='LEFT_RIGHT' scaleX='1' scaleY='0.5'/>
                                <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='BOTTOM'
                                    materialName='Textures/Rock/Rock1NormalRpt.xml'/>
                            </faceProperties>
                        </mesh>
                        <transform class='com.jme3.math.Transform'>
                            <translation class='com.jme3.math.Vector3f' x='0' y='-0.25' z='0'/>
                        </transform>
                    </net.wcomohundro.jme3.csg.CSGShape>
                    <net.wcomohundro.jme3.csg.CSGShape name='ArchedRoof' operator='UNION'>
                        <mesh class='net.wcomohundro.jme3.csg.shape.CSGCylinder' 
                                axisSamples='34' closed='true' zExtent='0.5' 
                                radialSamples='32' radius='0.5' textureMode='ROLLER'>
                            <faceProperties>
                                <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='SIDES' scaleX='1' scaleY='PI'/>
                            </faceProperties>
                        </mesh>
                    </net.wcomohundro.jme3.csg.CSGShape>
                </shapes>
                ... теперь вы регулируете масштабирование текстуры в соответствии с желаемым размером выреза(cutout) ...
                <faceProperties>
                    <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='FRONT_BACK' scaleX='1.9' scaleY='1.9'/>
                    <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='LEFT_RIGHT' scaleX='19.9' scaleY='1.9'/>
                    <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='TOP_BOTTOM' scaleX='1.9' scaleY='19.9'/>
                    <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='SIDES' scaleX='19.9' scaleY='1.0'/>
                </faceProperties>
                <transform class='com.jme3.math.Transform'>
                    <scale class='com.jme3.math.Vector3f' x='1.90' y='1.90' z='19.9'/>
                </transform>
            </net.wcomohundro.jme3.csg.CSGShape> 
            ... другие операции смешивания здесь ...
        </shapes>
    </net.wcomohundro.jme3.csg.CSGGeonode>

CSGEnvironment

CSGEnviroment реализует общие параметры конфигурации, которые контролируют обработку CSG. Он позволяет устанавливать различные допустимые отклонения и параметры, которые влияют на внутреннюю работу генератора фигуры. Если вы не планируете очень глубоко разбираться в самом CSG-коде, настройка по умолчанию должна вам подойти.

По умолчанию используется обработку IOB (работающий с двойной точностью). Вы можете перевернуть всю систему, чтобы использовать обработку IOB, включив в свой код инициализации следующее (перед использованием любой службы CSG):

CSGEnvironment.resetEnvironment( new CSGEnvironmentBSP() );

CSGExternal

CSGExternal — это специальное расширение CSGShape, которое поддерживает Savable импорт, параметр который загружает свою Сетку с помощью функции AssetManager.loadModel(). Программного использования не существует, оно применяется только во время импорта.

Реализация XML импорта выглядят примерно так:

    <net.wcomohundro.jme3.csg.CSGExternal name='Teapot' operator='UNION' 
        model='Models/Teapot/Teapot.obj' />

Смотрите так же:


Переведено для jmonkeyengine.ru, оригинал
Автор перевода: Andry

Сложные 3D-фигуры, созданные путем объединения простых примитивов

Скачать, Полный исходник

Конструктивная сплошная геометрия (КБГ) (англ. Constructive Solid Geometry, CSG) — это метод, используемый в 3D моделировании твёрдых тел. Конструктивная сплошная геометрия позволяет моделеру создавать сложную поверхность или объект с помощью булевых операторов для комбинирования объектов. Часто CSG представляет собой модель или поверхность, которая кажется визуально сложной, но на самом деле представляет собой немногим более, чем умные комбинации или декомбинации простых объектов.

Говорят, что объект построен из примитивов посредством допустимых операций, которые обычно являются булевыми операциями над множествами: объединение, пересечение и разность.

  • Булево объединение(Union) — слияние (добавление) двух объектов в один
  • Булево пересечение(Intersection) — общая часть для обоих объектов
  • Булева разность(Difference) — что останется, когда вы удалите один объект из другого

Несколько личных заметок об истории этого кода (1 квартал 2015 года)

После моего ухода из профессиональной жизни инженера-программиста которая длилась более четырех десятилетий, у меня появилась возможность побаловать себя капризами и теперь можно исследовать такие вещи, как 3D-графика. Экспериментировав с Java с момента его первоначального выпуска в 1995 году и разрабатывая коммерческие Java-приложения уровня предприятия с 2000 года, jMonkey показался мне идеальным местом что бы начать игру.

Как инженер, а не графический дизайнер, идея ручных инструментов и текстур с помощью различных инструментов для рисования, ориентированных на UI, не имела для меня привлекательности. Я являюсь разработчиком программных продуктов. Поэтому, когда я пошел искать концепцию смешивания фигур вместе, я столкнулся с CSG и примерами реализации в Java для jMonkey.

Насколько я могу судить, Эван Уоллас собрал библиотеку Javascript для поддержки CSG в браузерах, поддерживающих WebGL. Основным алгоритмом обработки является BSP — Двоичное разбиение пространства(Binary Space Partitioning) смотрите csg.js (это действительно впечатляет).
Он был преобразован в jMonkey совместимый Java что сделал fabsterpal и отправлен в репозиторий Github что сделал andychase. И вроде бы, все было правильно размещено и аннотировано для полностью открытых исходников.

Во время работы с Java-кодом я споткнулся о некоторые ошибки и обнаружил много мест, где мне бы хотелось получить более полные комментарии. Мой личный стиль обучения — это работа с рабочим примером. Туда я могу вносить небольшие, изменения, которые помогают мне понять более общую картину. С этой целью я переопределил удобным мне способом Java код, и структуру.
Но логика и алгоритмы основаны непосредственно на том, что я нашел в исходном коде Javascript/Java.

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

Другими словами, я резвюсь в своей песочнице… Но, будучи твердым сторонником открытых исходников, я делаю свои эксперименты доступными для всех. Если для вас есть что-то ценное, не стесняйтесь использовать его. Я буду пытаться сохранить действующий код полностью работоспособным, но на данный момент я не делаю никаких обещаний относительно обратной совместимости.
Если вы обнаружили проблему, составьте соответствующий Ticket в SourceForge.

Структура кода

Я пытаюсь сохранить CSG-код в виде достаточно независимого подключаемого модуля, не требующего изменений, в случае обновления ядра jme3 в новых версиях движка. Вы найдете интересный материал в пакете «net.wcomohundro.jme3.csg». От некоторых привычек отказаться очень тяжело (ну, они действительно не уходят вообще), поэтому я работаю в среде Eclipse IDE, используя исходник jMonkey, предоставленные последним стабильным релизом jme3 SDK. По состоянию на 30 Мая 2018, это версия 3.2.1

К тому что было сказано выше, у меня есть набор вещей зависимых от изменений ядра jme3, об этом сказано в репозитории CSG SVN. (о, ты же знал, что это произойдет). Я нашел Savable с поддержкой через XML, что является отличным способом управления моими тестовыми случаями. Мои вещей зависимых от изменение кода ядра в основном связаны с предоставлением разумных (т.е. не NullPointerException) значений по умолчанию для элементов, отсутствующих в XML. И я создал простой плагин XMLLoader для AssetManager, который позволяет мне загружать Игровые Ресурсы(Assets) из файлов XML. Это позволяет мне очень легко создавать тестовые случаи CSG, вручную редактируя XML файлы. Предоставляя разумные значения по умолчанию, я могу сократить мои XML-файлы до минимума.

Ядро, классы CSG реализованные в WCOmoCSG321… jar. Этот jar может быть включен в любой jMonkey IDE проект, который затем может использовать все функции ядра. Однако, если вы хотите использовать функции XML загрузчика, вам нужно будет включить WCOmoCSGjme321… jar и поместить его перед любыми стандартными jme3 jar-ми. После этого станут доступны функции импорта XML. Тестовые файлы и связанные с ними игровые ресурсы доступны в WCOmoCSGtest321… jar. Любой или все исходники могут быть загружены из SourceForge.

Особенности

  • Примитивные фигуры, основанные на любой произвольной Сетке
  • Составная геометрия на основе булевого смешивания фигур
  • Материалы, применённые к фигуре, применяются к поверхности, сгенерированной этой фигурой в конечной геометрии. Другими словами, результат может иметь несколько Материалов.
  • CSG определённые примитивные фигуры поддерживают грани с различным масштабированием текстур и материалами, применяемыми к различным поверхностям (например спереди, сзади, сверху, снизу, …)
  • Локальное Освещение, применённое к фигуре, применяются к поверхности, сгенерированной этой фигурой в конечной геометрии. Другими словами, результат может иметь Узлы-видимости освещений, которые применяются только к выбранным под-поверхностям.

Базовые элементы

Чтобы использовать CSG, вы будете работать с фигурами и геометрией.

Базовый Элемент  Описание
CSGShape  Базовый примитив CSG, основанный на любом произвольном Сетке(Mesh).
CSGGeometry  Булева точка смешивания для различных CSGShapes, которая даёт окончательную фигуру, к которой применяется один материал.
CSGGeonode  Булевая точка смешивания для различных CSGShapes, каждая из которых имеет собственный дополнительный материал, которая даёт окончательную фигуру. Материалы и локальное освещение, присвоенные примитивам, переносятся на поверхность которую они производят в конечном результате. Примитивы без явно заданного материала используют материал, присвоенный CSGGeonode в целом.

Целью моего первоначального дизайна, было реализовать CSGGeometry для точки смешивания, с возможностью поддержки нескольких материалов. Когда я узнал больше о jMonkey, я обнаружил, что Геометрия легко, может поддерживать только одну Сетку(Mesh). Чтобы предоставить несколько материалов, которые присвоены примитивам, вам действительно будет нужно использовать Узел с потомками Геометриями. Поэтому был создан CSGGeonode. Производится несколько сеток, соответствующих различным материалам. CSGGeometry больше не требуется, поскольку CSGGeonode предоставляет все те же возможности. Но CSGGeometry обеспечивает оптимизированную обработку только для тех объектов, которые имеют один материал.

Примитивные фигуры CSG

CSGShape можно построить из любой сетки. Никаких дополнительных примитивов, кроме тех, которые предоставляются ядром jMonkey, не требуется, и цель моего первоначального дизайна проекта была в том, чтобы не создавать какие-либо специальные примитивы для CSG. Но во время моих тестирований я споткнулся о проблемы связанные текстурами для Кубов, Цилиндров и Сфер. Поэтому я создал некоторые примитивы CSG.

CSG примитивы пытаются обеспечить единый подход их реализации. Это включает в себя идею фигур, созданных несколькими срезами, взятыми вдоль оси z. И включает концепцию грани (передняя, ​​задняя, ​​верхняя, нижняя, …), где текстуры на каждой поверхности могут иметь разный масштаб и могут применятся различные материал.

  • Для CSGBox это означает, что одна повторяющаяся текстура может быть соответствующим образом масштабироваться, оставаясь при этом неискаженной для любого продолговатого края.
  • Для CSGCylinder или CSGPipe грани концов не искажены радиально, а просто представляют собой круглый вырез текстуры, который может независимо масштабироваться.
  • Для CSGSphere в настоящее время нет специальной реализации поверхности.

Другие улучшения включают:

  • Радиальное искажение текстуры на полюсах Сферы устранено.
  • Вы можете использовать криволинейную текстуру поверхности Цилиндра по высоте, а не по окружности.
  • Шкала x/y может применяться к каждому отдельному срезу вдоль оси z.
  • Вращение может быть применено к каждому отдельному срезу вокруг оси z. Это приводит к скрученности конечной фигуры.
  • Простой Сплайн генератор, который может легко создавать торы или спирали.
  • Обработка LOD поддерживается за счет уменьшения количества срезов на определенный процент.
  • В будущем также улучшится сокращение числа радиальных точек.
  • Генерация TangentBinormal может быть вызвана настройками в файле XML Savable.

Лицензированние — BSD как и jMonkey

Новая BSD лицензия (3-пункта). Другими словами, вы сможете делать всё то, что сделает вас счастливыми!

Загрузить/Получить доступ к коду

Управление jMonkeyCSG осуществляется через проект SourceForge, ссылка на который здесь. Файлы .jar и .zip можно скачать здесь. Все исходники доступны через репозиторий SourceForge SVN.

JMonkey IDE

Как я уже говорил ранее, я работаю в Eclipse, ссылаясь на исходники jme3, из стабильной версией jme3 SDK. Я не очень хорошо знаком с IDE jMonkey, но у меня CSG в ней заработал, после того как я просто включил файл ядра CSG в разделе Библиотеки проекта jMonkey IDE.

С чего начать

Самый простой пример — добавить некоторые фигуры в геометрию, регенерируйте эту геометрию и примените Материал и добавить геометрию в свою сцену.

    	// Смешивание фигур в геометрии
    	CSGGeometry aGeometry = new CSGGeometry();
    	aGeometry.setMaterial( new Material( assetManager, "Common/MatDefs/Misc/ShowNormals.j3md" ) );

    	// Начните со сферы
    	CSGShape aSphere = new CSGShape( "Sphere1", new Sphere( 32, 32, 1.3f ) );
    	aGeometry.addShape( aSphere );

    	// Отнимем куб
    	CSGShape aCube = new CSGShape( "Box", new Box(1,1,1) );
    	aGeometry.subtractShape( aCube );
    	
    	// Произведём окончательную фигуру
    	aGeometry.regenerate();
    	
    	// Теперь добавьте aGeometry в вашу сцену

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

    	// Смешаем фигуры в geonode с общим материалом
    	CSGGeonode aGeometry = new CSGGeonode();
    	aGeometry.setMaterial( new Material( assetManager, "Common/MatDefs/Misc/ShowNormals.j3md" ) );

    	// Начнём с куба
    	CSGShape aCube = new CSGShape( "Box", new Box(1,1,1) );
    	aGeometry.addShape( aCube );

    	// Отнимем окрашенный цилиндр
    	CSGShape aCylinder = new CSGShape( "Cylinder", new Cylinder( 32, 32, 1.1f, pLength, true ) );
    	Material mat1 = new Material( assetManager, "Common/MatDefs/Misc/Unshaded.j3md" );
    	mat1.setColor( "Color", ColorRGBA.Yellow );
        aCylinder.setMaterial( mat1 );

    	aGeometry.subtractShape( aCylinder );
    	
    	// Произведём окончательную фигуру
    	aGeometry.regenerate();
    	
    	// Теперь добавьте aGeometry в вашу сцену

Тестовые случаи

Различные тестовые случаи включены в репозиторий SVN, все они основаны на формате Savable XML. Например, коридор может быть создан путем вычитания цилиндра из вытянутого куба, и затем вырезания дверного проема. Как вы можете видеть, довольно легко и быстро создавать комбинированные примеры, редактируя XML файл.

    <net.wcomohundro.jme3.csg.CSGLinkNode fname='CSGSample'>
        <lights class='com.jme3.light.LightList'>
            <lights>
                <com.jme3.light.AmbientLight name='WhiteLight' enabled='true'>
                    <color class='com.jme3.math.ColorRGBA' r='1' g='1' b='1' a='1'/>
                </com.jme3.light.AmbientLight>
            </lights>
        </lights>
        <children>
            <net.wcomohundro.jme3.csg.CSGGeonode name='ACorridor'
                    materialName='Textures/Rock/Rock1Rpt.xml'>
                <shapes>
                    <net.wcomohundro.jme3.csg.CSGShape name='Exterior'>
                        <mesh class='net.wcomohundro.jme3.csg.shape.CSGBox' 
                                xExtent='1.0' yExtent='1.0' zExtent='1.0'>
                            <faceProperties>
                                <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='FRONT_BACK' scaleX='1' scaleY='1.0'/>
                                <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='LEFT_RIGHT' scaleX='10' scaleY='1.0'/>
                                <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='TOP_BOTTOM' scaleX='1' scaleY='10.0'/>
                            </faceProperties>
                        </mesh>
                        <transform class='com.jme3.math.Transform'>
                            <scale class='com.jme3.math.Vector3f' x='1.0' y='1.0' z='10.0'/>
                        </transform>
                    </net.wcomohundro.jme3.csg.CSGShape>
                    
                    <net.wcomohundro.jme3.csg.CSGShape name='Interior' operator='DIFFERENCE'
                            materialName='Textures/BrickWall/BrickWallRpt.xml'>
                        <mesh class='net.wcomohundro.jme3.csg.shape.CSGCylinder' 
                                closed='true' height='1.0' radius='0.5' textureMode='FLAT_LINEAR'>
                            <faceProperties>
                                <net.wcomohundro.jme3.csg.shape.CSGFaceProperties face='SIDES' scaleX='9.95' scaleY='3.0'/>
                            </faceProperties>
                        </mesh>
                        <transform class='com.jme3.math.Transform'>
                            <scale class='com.jme3.math.Vector3f' x='1.95' y='1.95' z='19.9'/>
                        </transform>
                    </net.wcomohundro.jme3.csg.CSGShape>
		
                    <net.wcomohundro.jme3.csg.CSGShape name='Doorway' operator='DIFFERENCE'>
                        <mesh class='net.wcomohundro.jme3.csg.shape.CSGBox' 
                             xExtent='0.9' yExtent='0.5' zExtent='0.9'/>
                        <transform class='com.jme3.math.Transform'>
                            <translation class='com.jme3.math.Vector3f' x='0' y='0.5' z='10'/>
                        </transform>
                    </net.wcomohundro.jme3.csg.CSGShape>
                </shapes>
            </net.wcomohundro.jme3.csg.CSGGeonode>
        </children>
    </net.wcomohundro.jme3.csg.CSGLinkNode>

Смотрите так же:


Переведено для jmonkeyengine.ru, оригинал
Автор перевода: Andry

Вы можете использовать JUnion:

  • как исходный транслятор: junionc
  • как плагин компилятора для javac 1.8
  • как плагин Eclipse, в Netbeans
  • как часть Ant, Maven, Gradle сборки

Исходный Транслятор

Вы можете скомпилировать java исходники с помощью junion используя следующие команды:

java -jar junionc1.0.jar -classpath lib/junion1.0.jar -version 1.8 -sourcepath src -outputpath out

where:

  • -classpath — javac classpath например: lib1.jar:lib2.jar
  • -version — строка версии исходников, например 1.8, 9, 10
  • -sourcepath — папка(и) исходников, например: src, или src1:src2
  • -outputpath — папка для выпуска скомпилированного java исходника

Плагин Javac

Вы можете скомпилировать проект JUnion непосредственно с javac 1.8, добавив аргумент командной строки: -Xplugin:junion. Убедитесь, что все указанные библиотеки находятся в classpath.

Плагин Eclipse

Чтобы использовать JUnion в среде Eclipse, выполните следующие действия:

  1. Установите JUnion плагин из: https://tehleo.github.io/junion/update
  2. Создайте новый Java Проект или Откройте существующий
  3. Добавить библиотеку времени выполнения проекта: junion<версия>.jar
  4. Создайте новый файл в проекте под названием “.junion
  5. В файле свойств .junion, задайте свойство compileLibs= путь к junionc<версия>.jar
  6. Вместо шага 5. вы можете добавить библиотеку junionc<версия>.jar напрямую в свой classpath. Однако это не зависимость времени выполнения.
  7. Сохраните файл, это должно автоматически создать папку “.generated_src_junion” (Если вы не видите файлы, начинающиеся с точки, в вашем проводнике пакетов включен фильтр)
  8. В свойствах проекта ▸ Java Build Path ▸ Sources ▸ Добавьте Папку и добавьте .generated_src_junion
  9. Window ▸ Preferences ▸ Java ▸ Compiler ▸ Error/Warnings ▸ Deprecated или Restricted API Установите Forbidden Reference на Warning.

Кроме того, можете посмотреть видео-урок по настройке Eclipse:

Поддержка Netbeans

Чтобы использовать JUnion в среде Netbean IDE, выполните следующие действия:

  1. Добавьте все библиотеки в проект.
  2. Свойства проекта ▸ Собрать ▸ Компиляция ▸ Уберите галочку Компиляцию при сохранении и введите «-Xplugin:junion» в Дополнительные параметры компилятора

Gradle

Файл сборки Gradle:

apply plugin: 'java'

repositories {
    mavenCentral()
}
configurations {
    junion
}
dependencies {
    compile 'com.github.tehleo:junion:1.1.1'
    junion 'com.github.tehleo:junionc:1.1.1'
}
task junionTask(type: JavaExec) {
    classpath configurations.junion
    classpath += sourceSets.main.runtimeClasspath
    main = 'theleo.jstruct.plugin.SourceCompiler'
    args = ['-classpath', sourceSets.main.runtimeClasspath.getAsPath(),
            '-version', '1.8',
            '-sourcepath', files(sourceSets.main.java.srcDirs).getAsPath(),
            '-outputpath', file('build/generated').getPath()
    ]
   	sourceSets.main.java.srcDirs = ['build/generated']
}

build.dependsOn junionTask

Maven

Файл сборки Maven:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

	<groupId>tehleo.maventest</groupId>
	<artifactId>test</artifactId>
	<version>1.0-SNAPSHOT</version>
	<packaging>jar</packaging>

	<properties>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	 </properties>

 	 <name>Hello Structs</name>
 
	<dependencies>
		<dependency>
			<groupId>com.github.tehleo</groupId>
			<artifactId>junion</artifactId>					
			<version>1.1.1</version>
		</dependency>
	</dependencies>

	<build>
		<sourceDirectory>${basedir}/target/generated-sources</sourceDirectory>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-dependency-plugin</artifactId>
				<version>3.1.0</version>
				<executions>
					<execution>
						<id>build-classpath</id>
						<phase>generate-sources</phase>
						<goals>
							<goal>build-classpath</goal>
						</goals>
						<configuration>
							<outputProperty>classpath-string</outputProperty>
						</configuration>
					</execution>
				</executions>
			</plugin>

			<plugin>
			<groupId>org.codehaus.mojo</groupId>
			<artifactId>exec-maven-plugin</artifactId>
			<version>1.6.0</version>
			<executions>
				<execution>
				<phase>generate-sources</phase>
				<goals>
					<goal>java</goal>
				</goals>
				</execution>
			</executions>
			<configuration>
				<includePluginDependencies>true</includePluginDependencies>
				<mainClass>theleo.jstruct.plugin.SourceCompiler</mainClass>
				<sourceRoot>${basedir}/target/generated-sources</sourceRoot>
				<arguments>
					<argument>-noSystemExitOnSuccess</argument>
					<argument>-classpath</argument>
					<argument>${classpath-string}</argument>
					<argument>-version</argument>
					<argument>1.8</argument>
					<argument>-sourcepath</argument>
					<argument>${basedir}/src/main</argument>
					<argument>-outputpath</argument>
					<argument>${basedir}/target/generated-sources</argument>
				</arguments>
			</configuration>
			<dependencies>
				<dependency>
				<groupId>com.github.tehleo</groupId>
				<artifactId>junionc</artifactId>
				<version>1.1.1</version>
				</dependency>
			</dependencies>
			</plugin>
		</plugins>
	</build>
</project>

Смотрите так же:


Переведено для jmonkeyengine.ru, оригинал
Автор перевода: Andry

jMonkeyEngine.ru © 2017. Все права сохранены.