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

Andry

Опубликованно: AndryAndry31.07.2018, 14:02
Последняя редакция, Andry: 10.09.2018 20:20

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

Чтобы вызвать родную(native) функцию, LWJGL требует как Java, так и родной(native) код. Давайте используем функцию 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 позволяло разделить родные(native) и не-родные(native) методы.

В дополнение к родному(native) методу, так же включены несколько обычных 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.

С родной(native) стороны все очень просто. Это определение 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 принимает здесь очень важное решение; родной(native) код ничего не делает, кроме параметров заливки для соответствующих типов и вызывает функцию native. Вся сложность и сопоставление с удобными для пользователя параметрами для правильных исходных данных обрабатываются в Java-коде. Все указатели передаются как примитивные longs, примитивные значения передаются без изменений и все. Параметры JNIEnv и jclass никогда не затрагиваются, и никакого взаимодействия с JVM нет; никакие параметры задания не передаются, и нет вызовов C-to-Java. Собственные методы также общедоступны, что позволяет пользователям создавать свои собственные методы удобства поверх исходных, и они могут быть уверены, что ничего необычного не происходит при вызове функции JNI.

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

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

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

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

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

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

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

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

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

Непрозрачные указатели являются указателями на данные, которые имеют неопределенную структуру и обычно соответствуют внутреннему состоянию, управляемому сторонним 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)

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

Учитывая объявление родной функции, например:

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

Непрозрачные указатели являются указателями на данные, которые имеют неопределенную структуру и обычно соответствуют внутреннему состоянию, управляемому сторонним 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 теперь неявный, замененный значением .remaining () / 4. 4 есть потому, что каждый счетчик представляет собой вектор из 4 значений.

Текущий Buffer.position () влияет на адрес указателя, который будет передан в нативную функцию. Если FloatBuffer обертывает указатель с адресом x, а текущая позиция равна 2, будет использоваться адрес x + 8.

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

Как обрабатываются выходные параметры?

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

Классическим примером является 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. Такие методы можно безопасно использовать из нескольких контекстов одновременно.

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

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

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) // single value!

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

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

GLint glGetAttribLocation(GLuint program, const GLchar *name) // A) null-terminated input
GLubyte *glGetString(GLenum name) // B) null-terminated output
void glGetProgramInfoLog(GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog) // C) output w/ explicit length

и методы 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) для автоматического выделения достаточного количества хранилища для возвращаемого текста журнала!

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

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

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

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

// Function signature:
typedef void (APIENTRY *GLDEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, GLvoid* userParam)
// Callback registration:
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 вызовет утечку памяти, потому что ничего не возвращается, что разработчик может использовать для освобождения генерируемой функции обратного вызова. Существует два подхода к решению этой проблемы:

// Method 1: store this reference in a Java variable.
GLDebugMessageCallback cb = GLDebugMessageCallback.create((source, type, id, severity, length, message, userParam) -> {
    // print message
});
glDebugMessageCallback(cb, NULL);
// later...
cb.free();

// Method 2: use glGetPointer to retrieve the callback
glDebugMessageCallback((source, type, id, severity, length, message, userParam) -> {
    // print message
}, NULL);
// later...
GLDebugMessageCallback.create(glGetPointer(GL_DEBUG_CALLBACK_FUNCTION)).free();

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

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

Да, каждый тип структуры сопоставляется с классом. Класс содержит методы создания / распределения и getters / setters для членов структуры. Макет структуры (смещения поля, sizeof, alignof) доступен как статические конечные поля int.

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

Каждый класс структуры также имеет внутренний буферный класс, который представляет массив этих структур. Класс Buffer имеет API, который совместим с буферами java.nio, а также getters / seters, которые могут использоваться для доступа к массиву структур с помощью мухи.

Пример:

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()

Существуют также «небезопасные» статические геттеры и сеттеры, которые могут использоваться для доступа к ячейкам памяти, которые не завернуты в класс struct или struct buffer.

В приведенных выше методах A просто. Результат image.address () передается в собственный код. В B-изображениях представлен массив структур 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-<платформа>: родные(natives) общедоступные библиотеки для соответствующей платформы
Не все артефакты имеют родные(natives), и не все артефакты с natives доступны на всех платформах.

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

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

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

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

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

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

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

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

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

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

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

SharedLibraryLoader извлекает natives(родные) один раз и повторно использует их от запуска к запуску. Natives замещаются только при обнаружении другой версии 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. Один с родными(natives) .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

Типы структур

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

Вы можете реализовать тип структуры, задав ему аннотацию @Struct.

@Struct
public class Vec3 {
    public float x,y,z;
}

Структура Vec3 имеет размер 12 байт. Вы можете проверить это двумя способами, выполнив:

System.out.println("Size: " + Mem.sizeOf(Vec3.class));
System.out.println(Mem.layoutString(Vec3.class));

Выведется

Size: 12
~Struct test.HelloStruct$Vec3(12/12/12) Align: 4 ~
|0:x FLOAT(4), -1|
|4:y FLOAT(4), -1|
|8:z FLOAT(4), -1|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Автоматическое выравнивание данных

Для получения информации о выравнивании данных смотрите здесь.

@Struct
public class MixedData {
    byte Data1;
    short Data2;
    int Data3;
    byte Data4;
}

Структура MixedData не имеет выровненных данных. JUnion обнаруживает это и автоматически перестраивает данные. Мы можем еще раз использовать код ниже, чтобы проверить размер и компоновку структуры.

System.out.println ("Размер:" + Mem.sizeOf (MixedData.class));
System.out.println (Mem.layoutString (MixedData.class));

Выведется

Size: 8
~Struct test.HelloStruct$MixedData(8/8/8) Align: 4 ~
|0:Data3 INT(4), -1|
|4:Data2 SHORT(2), -1|
|6:Data1 BYTE(1), -1|
|7:Data4 BYTE(1), -1|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Первое Data3 с индексом 0, кратно четырем, выравнивается. Аналогично, Data2 начинается с индексом 4, оно кратно 2, также выравнивается.

Ручное выравнивание данных

Если ваш код зависит от компоновки данных внутри структуры, используйте autopad в свойствах аннотации.

@Struct(autopad=false)
public class Manual {
    public byte b;
    private byte padding;
    public char ch;
}

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

Создание Массивов Типов Структур

Массивы структур выделяются в куче и автоматически высвобождаются.

Vec3[] arr = new Vec3[12];
arr[5].y = 10;
		
System.out.println("arr[5].y = " + arr[5].y );

64-битные long адреса массива

Массивы структур поддерживают адресацию с long-ами.

Vec3[] arr = new Vec3[Mem.li(1000000000L)];
arr[Mem.li(900000000L)].y = 10;
		
System.out.println("y = " + arr[Mem.li(900000000L)].y );

Изменение Native DirectByteBuffer-ов

Мало того, что доступ к/изменение bytebuffer-ов более читабельно с помощью синтаксиса структуры, оно также повышает производительность.

ByteBuffer a = ByteBuffer.allocateDirect(num*vec3Size).order(ByteOrder.nativeOrder());

Vec3[] av = Mem.wrap(a, Mem.sizeOf(Vec3.class));

Проверка Индексов

Vec3[] arr = new Vec3[12];

arr[-1].x = 5; //throws IndexOutOfBoundsException
arr[12].x = 5; //throws IndexOutOfBoundsException

Вложенные структуры

@Struct
public class Line2 {
    public Vec3 a, b;
}

Компоновка

Size: 24
~Struct test.HelloStruct$Line2(24/24/24) Align: 4 ~
|0:a STRUCT(4), -1|
|12:b STRUCT(4), -1|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Ссылки Структур

При сборке древа структур вы можете написать что-то вроде этого.

@Struct
public class Node {
    public Node left, right;
}

Однако структура не может содержать себя. JUnion обнаруживает эту ошибку как Круговую Зависимость(Circular Dependence).

Чтобы собрать древо структур, мы можем написать:

@Struct
public class Node {
    @Reference public Node left, right;
}

Проверка нулевой ссылки

Node[] n = new Node[10];
Node a = n[0].left; //throws NullPointerDereference

Чтобы проверить, является ли ссылка нулевой(null) , используйте Mem.isNull

Node[] n = new Node[10];
if(!Mem.isNull(n[0].left)) {
    Node a = n[0].left;
}

Срез Массива

Вы можете создавать срезы массива. Срезы обмениваются данными, поэтому изменения в срезе отражаются и в исходном массиве.

Vec3[] arr = new Vec3[10];
for(int i = 0; i < arr.length; i++) arr[i].x = i;
		
Vec3[] a = Mem.slice(arr, 5, arr.length);
Vec3[] b = Mem.slice(arr, 5, arr.length, 2);
Vec3[] reversed = Mem.slice(arr, 0, arr.length, -1);
		
System.out.print("a:\t");
for(int i = 0; i < a.length; i++) System.out.print(a[i].x + ",");
System.out.print("\nb:\t");
for(int i = 0; i < b.length; i++) System.out.print(b[i].x+",");
System.out.print("\nrev:\t");
for(int i = 0; i < reversed.length; i++) System.out.print(reversed[i].x+",");

Выведется

a:	5.0,6.0,7.0,8.0,9.0,
b:      5.0,7.0,
rev:	9.0,8.0,7.0,6.0,5.0,4.0,3.0,2.0,1.0,0.0,

Обобщения

Vec3[] arr = new Vec3[10];
for(int i = 0; i < arr.length; i++) arr[i].x = i;

ArrayList<Vec3> list = new ArrayList<>();
list.add(arr[5]);
list.add(arr[1]);
list.add(arr[7]);

for(Vec3 v : list) {
    System.out.println(v.x);
}

Выведется

5.0
1.0
7.0

Размещение в стеке

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

Vec3 v = Mem.stack(Vec3.class); {
    v.x = 1; v.y = 2; v.z = 3;
}

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

Vec3 v = Mem0.stackRaw(Vec3.class);

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


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

(Дополнительная информация доступна на веб-сайте этого проекта)

drawing

Предоставляет типы структур для языка программирования Java.

При создании int массивов мы имеем два основных варианта:

int[] intArray = new int[1000];  
Integer[] intBoxedArray = new Integer[1000];

Сколько байтов используют intArray, intBoxedArray для хранения 1000 int-ов?

intArray 4016 bytes 4*1000 + ~16(около 16 байт для заголовка массива) 
intBoxedArray 20016 bytes (4 + ~12 + ~4)*1000 + ~16 (точное число зависит от VM)

Это почти в 5 раз больше!
Ну, это учит нас отдавать предпочтение примитивным массивам, а не их версиям оболочкам.
Так о чем этот проект?

Рассмотрим

class Point { float x,y;}
Point[] arr = new Point[500];

arr возьмёт 14016 байт
Данные состоящие из 500 point, по 2 float, поэтому 4000 байт должно быть достаточно.
Если Point это структура, то arr возьмёт ~4000 байт.

Разве было бы неплохо иметь возможность создавать типы структур в Java, такие как класс и работающие как структуры?

С JUnion вы можете сделать это, отметив класс с помощью аннотации @Struct!

Создадим структуру Vec3:

@Struct
public class Vec3 {
    public float x,y,z;
}

Далее вы можете использовать его так:

//Создадим новый массив структур
Vec3[] arr = Vec3[10];
arr[5].x = 10;
Vec3 v = arr[5];
...
//
ByteBuffer a = ByteBuffer.allocateDirect(10*Mem.sizeOf(Vec3.class))
   .order(ByteOrder.nativeOrder());
//Измените Direct Native Bytebuffer как бы структуру
Vec3 arr = Mem.wrap(a);
arr[5].x = 10;
...

Список возможностей можно найти здесь.

Зачем использовать типы структур?

  • Типы структур используют меньше памяти.
  • Имейте производительность примитивных типов.
  • Позволяет задавать данные в direct native ByteBuffer-ах с синтаксисом как класс.

Тест производительности

testarrayperf75

Скачать

Ознакомьтесь с последней версией

и руководство по интеграции использованию/IDE.

Поддержка и пожертвования

Вы хотели бы поддержать JUnion? Вы можете сообщить об ошибках или просить здесь новые возможности или пообщаться здесь

Или вы хотите сделать пожертвование? Вы можете сделать это через PayPap

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


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

LWJGL3 Руководство по миграции. Данное руководство предназначено для того что бы показать процесс переноса приложения с LWJGL2 на LWJGL3. Для этого я написал простую игру Pong в LWJGL2 (2.9.3), и это руководство будет описывать, как я перенес его на LWJGL3 (3.0.0b Build # 64 — последняя стабильная на момент написания статьи). Руководство будет разделено на commit-ы, по одному commit-у для каждой функции, которую я переношу на LWJGL3, и вы можете точно увидеть, что я изменил, просмотрев разные commit-ы в репозиторий Github: Миграция Pong в Github Репозитории. Репозиторий содержит оригинал игры в папке «PongLWJGL2», а в папке «PongLWJGL3» содержится обновленная версия.

Прежде чем вы прочитаете это руководство, если вы хотите использовать улучшения стабильности (не функции) LWJGL3, но не хотите переносить свою игру, то у вас есть альтернатива. Каппа написал слой совместимости называемый LWJGLX, смотрите это сообщение на форуме. ##Commit 1 — Переключение Этот commit просто переключает natives и библиотеки на использование LWJGL3, ничего более. Как и следовало ожидать, это не сработает. ##Commit 2 — Синтаксис команд OpenGL. Одно из наиболее незначительных изменений, внесенных в LWJGL3, является изменение синтаксиса OpenGL на актуальный синтаксис, которые вы можете найти в C API. Как это бывает, единственное изменение, которое требуется сделать в примере программы, заключается в изменении всех вызовов:

glUniformMatrix4(int location, boolean transpose, FloatBuffer values);

на:

glUniformMatrix4fv(int location, boolean transpose, FloatBuffer values);

Логика синтаксиса LWJGL2: подразумевается, что это float матрица, поскольку вы проходите FloatBuffer. LWJGL3 возвращается к исходному API, чтобы быть более совместимым с другими источниками информации, особенно официальной ссылкой.

Как правило, вы будете добавлять «f» и «i’s» и «fv» и.т.п. для вызова команд. Не должно вызывать слишком много проблем и, вероятно, будет достаточно очевидно, что заменить.

Следующий commit будет охватывать самое большое изменение из всех — переход от собственной оконной системы LWJGL2 к GLFW, используемой LWJGL3. ##Commit 3 — Создание Контекста Window/OpenGL. Я думаю, что есть три важных момента, которые нужно знать, когда дело доходит до создания окна и контекста OpenGL в LWJGL3 (то есть GLFW, использует библиотеку окон LWJGL).

  1. GLFW может управлять несколькими окнами и, подобно тому, как OpenGL управляет своими объектами, на них ссылается обработчик, который передается функциям, которые управляют этим окном. LWJGL3 привязки используют «long» переменные для хранения этого обработчика.
  2. Перед созданием окна вы указываете все атрибуты, которые вы хотели бы иметь с помощью подсказок окна. Все передаётся с помощью одной функции: glfwWindowHint()
  3. Поскольку вы можете иметь дело с несколькими окнами, каждое из которых потенциально имеет свой собственный контекст OpenGL, то после его создания вы должны указать LWJGL3, какой контекст вы используете.

Прежде чем мы что-либо сделаем в GLFW, мы должны инициализировать GLFW с помощью glfwInit(), у этого есть сопутствующая функция для завершения GLFW, реализующая его ресурсы glfwTerminate(). В Pong они добавляются к началу и концу функций init() и deinit() соответственно.

Затем неплохо было бы заставить GLFW печатать свои ошибки на консоли, что он не делает по умолчанию, но это просто, и LWJGL предоставляет функцию утилиту для создания соответствующего обратного вызова (подробнее об обратных вызовах позже) в org.lwjgl.glfw.GLFWErrorCallback классе.

glfwSetErrorCallback(errorCallback = GLFWErrorCallback.createPrint(System.err));

Это будет работать отлично, но errorCallback ДОЛЖЕН будет быть полем, а НЕ локальной переменной (запомните дополнительно о обратных вызовах позже).

Теперь, чтобы задать свойства окна. Шаг за шагом в Pong: заголовок передается как параметр функции glfwCreateWindow(), свойство resizeable устанавливается с флагом GLFW_RESIZABLE, запрошенная версия OpenGL устанавливается с помощью флагов GLFW_CONTEXT_VERSION_MAJOR и GLFW_CONTEXT_VERSION_MINOR, запрошенный профиль устанавливается с помощью Флаг GLFW_OPENGL_PROFILE и с полноэкранным режимом мы пока не будем иметь дело.

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

В старом LWJGL2

Display.setTitle("Pong - LWJGL2");
Display.setResizable(true);
Display.create(
    new PixelFormat(), 
    new ContextAttribs(3, 3).withProfileCore(true)
);

В новом LWJGL3

glfwWindowHint(GLFW_RESIZABLE, GL_TRUE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 
window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Pong - LWJGL3", 0, 0);

«0» в вызове glfwCreateWindow() относятся к полноэкранному и совместно используемому контекстам OpenGL, если вам все равно, их можно игнорировать, но они будут объяснены позже. Дополнительную информацию об этих функциях смотрите в официальной документации: glfwWindowHint(), glfwCreateWindow(). Вы также заметите, что LWJGLException теперь никогда не используется в коде (и фактически удален в LWJGL3), поэтому все случаи применения в Pong можно удалить.

Однако это ещё не все, так как мы все еще проверяем, было создано окно успешно, сообщите LWJGL, чтобы использовать контекст OpenGL этого окна и сделать окно видимым (поскольку окно GLFW скрыто по умолчанию).

Теперь glfwCreateWindow() вернет ‘0’, предоставляя значение NULL, если оно по какой-либо причине не сработает. Таким образом, в Pong добавлена простая проверка, и ваш обратный вызов может поймать причину ошибки, если вы этого хотите:

if(window == 0) {
    throw new RuntimeException("Failed to create window");
}

Теперь мы должны установить контекст этого окна как текущий контекст в этом потоке, вызвав glfwMakeContextCurrent() с обработчиком нашего окна в качестве параметра и, наконец, вызвать GL.createCapabilities(), чтобы LWJGL использовал контекст текущего (нашего окна). Подробнее о контекстах здесь (OpenGL Контексты), и вот документация по glfwMakeContextCurrent().

glfwMakeContextCurrent(window);
GL.createCapabilities();

И, наконец, сделайте окно видимым с помощью glfwShowWindow(). Документация здесь: glfwShowWindow().

glfwShowWindow(window);

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

Прежде всего уничтожить окно, как только вы с ним закончите. Эквивалентом Display.destroy() является glfwDestroyWindow(), документация: glfwDestroyWindow().

glfwDestroyWindow(window);

Теперь Display.isCloseRequested() становится glfwWindowShouldClose(), но это API-интерфейс C, поэтому он не будет возвращать логическое значение, а будет возвращать целое число, представляющее логическое значение, посмотрите здесь: glfwWindowShouldClose.

while(glfwWindowShouldClose(window) == GL_FALSE && 
		remainOpen) {
	...
}

И, наконец, Display.update() не имеет прямого аналога в основном потому, что он выполняет две функции: свопирует буферы и опросы устройств ввода. В GLFW вы должны использовать две функции: glfwPollEvents() и glfwSwapBuffers(), дополнительную информацию смотрите здесь glfwPollEvents() и здесь glfwSwapBuffers().

glfwPollEvents();
glfwSwapBuffers(window);

На этом этапе, если вы закомментируете все содержимое update(), setDisplayMode() и получите что currentTimeMillis() вернёт 0, Pong запустится и отобразит растягиваемое закрываемое окно нужного размера. К несчастью, оно будет просто пустым, но не потому, что рендеринг не работает, а потому что, так как мы не имеем дело с событиями изменения размера, проекционная матрица неправильна.

Это то, что мы будем делать дальше. ##Commit 4 — Framebuffer Resize Handling(Обработка Изменения Размера Кадрового буфера) Прежде чем продолжить, я хочу объяснить важное различие между фреймбуфером(кадровым буфером) и окном. Фреймбуфер — это «холст» OpenGL — массив пикселей, который может быть отображен. Окно — это то, что содержит фреймбуфер, и его можно перемещать и изменять размер и.т.п. Как правило, изменение размера окна приведет к изменению размера фреймбуфера, но это два разных события, и GLFW позволит вам обрабатывать их независимо. Здесь мы сосредоточимся только на событиях изменения размера фреймбуфера. Подробнее о фреймбуферах читайте здесь.

Чтобы обрабатывать события изменения размера фреймбуфер, а также большинства других событий, GLFW использует обратные вызовы. Фактически мы уже использовали обратный вызов для обработки ошибок GLFW. Теперь обратные вызовы — это, по сути, другое название для слушателей, которые мы используем в Swing, JavaFX и во многом другом. LWJGL3 предоставляет абстрактные классы для переопределения, для всех вариантов обратных вызовов GLFW и даже интерфейсы для использования в качестве функциональных интерфейсов, если вы используете лямбды Java8, в этом случае org.lwjgl.glfw.GLFWFramebufferSizeCallback и функциональный интерфейс: org.lwjgl.glfw.GLFWFramebufferSizeCallback.SAM.

Есть одна критически важная вещь связанная с обратными вызовами GLFW. ВСЕГДА ПОДДЕРЖИВАЙТЕ В ПОРЯДКЕ ССЫЛКИ ОБРАТНОГО ВЫЗОВА. Иначе обратные вызовы будут накапливаться в виде мусора, что приведет к довольно сложной ошибке отладки.

Теперь задайте функции изменения размера фреймбуфера для обратного вызова — это glfwSetFramebufferSizeCallback() подробнее здесь: glfwSetFramebufferSizeCallback().

В LWJGL2 этот код был у нас в методе update(). Обратите внимание, на то что нам нужно было иметь дополнительный флаг, чтобы прослушивать полноэкранные изменения, так как был только флаг изменения размера окна по умолчанию:

if(Display.wasResized() || goneFullscreen) {
    //Сбросить флаг goneFullscreen.
    goneFullscreen = false;
    onResize(Display.getWidth(), Display.getHeight());
}

В LWJGL3 мы можем избавиться от флага goneFullscreen, и все получается намного проще. Мы имеем это в коде инициализации:

glfwSetFramebufferSizeCallback(window, (framebufferSizeCallback = new GLFWFramebufferSizeCallback() {
    @Override
    public void invoke(long window, int width, int height) {
        onResize(width, height);
    }
}));
onResize(WINDOW_WIDTH, WINDOW_HEIGHT);

Мы вызываем onResize() после настройки обратного вызова фреймбуфера для инициализации проекционной матрицы до её начального значения.

Теперь Pong запустится, он создаст рабочее окно и отобразит в нем все элементы Pong. Но пока что он ещё не будет реагировать на ввод, или обновление любого из его элементов. Это будет в следующем commit, но прежде чем мы сможем обрабатывать ввод, нам нужно обновить наш код настройки времени. ##Commit 5 — Получение Времени. LWJGL2 давал нам две функции для получения точного времени Sys.getTime() и Sys.getTimerResolution(). Последняя требовалась, потому что первая, в зависимости от платформы могла работать в других единицах, чтобы вы получали лучшее разрешение и точность. Дело в том, что эта функциональность была написана в то время, когда эквивалентов JRE просто не было, что теперь уже не так, поэтому их можно заменить на функции, найденные в java.lang.System.

Однако GLFW также предоставляет альтернативу, которую мы будем использовать здесь: glfwGetTime() возвращает время в секундах, подробности здесь. Для использования этих значений в Pong мы должны преобразовать их в double значения для хранения текущего времени с точностью, которую может обеспечить этот метод, currentTimeMillis поэтому будет:

public static double currentTimeMillis() {
    return GLFW.glfwGetTime() * 1000;
}

##Commit 6 — Ввод с Клавиатуры. В Pong и, в общем случае, мы получаем ввод с клавиатуры двумя способами. Во-первых, мы запрашиваем клавиатуру из её текущего состояния, выясняя, находится ли какая-то конкретная клавиша нажатой или нет в это время, во-вторых, мы действуем на события клавиатуры, о которых сообщает наша система ввода.

Первый метод является самым простым, и именно это мы рассмотрим в первую очередь. Итак, в LWJGL2 у нас была Keyboard.isKeyDown (). В GLFW мы имеем glfwGetKey (), который вместо логического возвращает GLFW_PRESS или GLFW_RELEASE. Так в Понге:

updatePaddle(
    paddle1, delta, 
    glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS, 
    glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS
);
updatePaddle(
    paddle2, delta, 
    glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS,
    glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS
);

Теперь для обработки событий клавиатуры, и здесь GLFW использует обратные вызовы. На этот раз функция glfwSetKeyCallback (), абстрактный класс LWJGL — org.lwjgl.glfw.GLFWKeyCallback, а функциональный интерфейс — org.lwjgl.glfw.GLFWKeyCallback.SAM. Как всегда с обратными вызовами, вы должны ПОМНИТЬ, ЧТОБЫ СОХРАНЯТЬ СИЛЬНУЮ СПРАВКУ К ВЫЗОВУ. Поэтому в Понге мы должны добавить это к нашей функции init ().

glfwSetKeyCallback(window, (keyCallback = new GLFWKeyCallback() {

    @Override
    public void invoke(long window, int key, int scancode, int action, int mods) {
        if(key == GLFW_KEY_SPACE && action == GLFW_RELEASE) {
            onPlayPauseToggle();
        } else if(key == GLFW_KEY_F5 && action == GLFW_RELEASE) {
            setDisplayMode(true);
        } else if(key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
            //Request close.
            remainOpen = false;
        }
    }

}));

Полное описание параметров метода invoke () callback можно найти в связанном описании, но важно отметить mods, бит-поле, описывающее ключи модификатора, включенные во время этого события. Вы найдете это очень полезным, помните об этом. Также стоит отметить, что callback может получать события GLFW_REPEAT, а также события GLFW_PRESS и GLFW_RELEASE.

Если вы раскомментируете код обновления мяча в update (), на данный момент у вас будет полностью работающая игра в Понге, хотя и отсутствуют некоторые из особенностей оригинала. Следующее commit мы заменим код ввода мыши. ## Commit 7 — Ввод мыши Перед тем, как мы начнем, есть одна важная разница с записью с GLFW по сравнению с LWJGL2. В LWLJGL2, когда он подошел к позиции курсора, нижний левый угол был выбран как начало большинства приложений OpenGL. GLFW берет начало координат в верхнем правом углу, как и в большинстве графических интерфейсов. В Понге это потребует небольшой модификации.

Как и в случае с клавиатурой, мы должны обрабатывать наши запросы мыши и события мыши, а API следует точно так же, как и ввод с клавиатуры. Однако самый простой способ получить позицию курсора в GLFW — использовать обратный вызов позиции курсора, glfwSetCursorPosCallback (), org.lwjgl.glfw.GLFWCursorPosCallback. Как всегда помните, что ХРАНИТЕ СИЛЬНУЮ СПРАВОЧНУЮ ЗАДЕРЖКУ.

В этом фрагменте cursorPos — это просто поле, в котором хранятся текущие позиции x и y текущего курсора. Мы вычитаем заданную позицию y из высоты фреймбуфера, чтобы учитывать GLFW, беря верхний левый угол в качестве источника, когда мы ожидаем, что он будет внизу слева.

glfwSetCursorPosCallback(window, (cursorPosCallback = new GLFWCursorPosCallback() {

    @Override
    public void invoke(long window, double xpos, double ypos) {
        cursorPos.x = xpos;
        cursorPos.y = framebuffer.height - ypos;
    }

}));

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

updateNewBall(Mouse.getX(), Mouse.getY());

будет выглядеть так:

updateNewBall(cursorPos.x, cursorPos.y);

И цикл событий мыши в update () становится:

glfwSetMouseButtonCallback(window, (mouseButtonCallback = new GLFWMouseButtonCallback() {

    @Override
    public void invoke(long window, int button, int action, int mods) {
        if(button == 0) {
            if(action == GLFW_PRESS && addBall == null) {
                onNewBall(cursorPos.x, cursorPos.y);
            } else if(action == GLFW_RELEASE && addBall != null) {
                onNewBallRelease(cursorPos.x, cursorPos.y);
            }
        }
    }

}));

И с этим изменением у нас есть полностью функционирующая игра Понга. Единственное, чего у нас не было, это полноэкранная возможность, которая будет нашей следующей фиксацией. ## Commit 8 — Fullscreen Первое, что нужно сказать, это то, что при переходе на GLFW мы не сможем реализовать полноэкранное переключение с тех пор, поскольку с момента написания этого GLFW не поддерживает полноэкранное переключение. Это можно сделать, но это связано с созданием нового окна и простым переключением между ними.

Способ, которым мы указываем полноэкранный режим, — передать не-нулевой (не нулевой) монитор в glfwCreateWindow (). Этот аргумент говорит GLFW, который контролирует размещение полноэкранного приложения. Когда вы передаете ненулевой монитор, ширина и высота становятся запрошенным разрешением, которое GLFW будет делать, это лучше всего выполнить. Если мы хотим разрешение рабочего стола (что обычно является хорошей идеей для плавного запуска и производительности).

Мы можем получить основной монитор с помощью glfwGetPrimaryMonitor () (документация), который возвращает дескриптор монитора. Выбор монитора, который не является основным монитором, довольно сложный, и если вам действительно нужно, что лучший вариант — позволить пользователю выбрать из списка.

Когда у нас есть монитор, нам нужно разрешение рабочего стола, которое мы можем получить, запросив текущее разрешение монитора. GLFW хранит эту информацию в структуре видеорежима, GLFWVidMode может быть восстановлен с помощью glfwGetVideoMode.

monitor = glfwGetPrimaryMonitor();
GLFWVidMode vidMode = glfwGetVideoMode(monitor);
width = vidMode.width();
height = vidMode.height();

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

window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Pong - LWJGL3", 0, 0);

становится

int windowWidth = WINDOW_WIDTH;
int windowHeight = WINDOW_HEIGHT;
long monitor = 0;
if(START_FULLSCREEN) {
    monitor = glfwGetPrimaryMonitor();
    Retrieve the desktop resolution
    GLFWVidMode vidMode = glfwGetVideoMode(monitor);
    windowWidth = vidMode.width();
    windowHeight = vidMode.height();
}
window = glfwCreateWindow(windowWidth, windowHeight, "Pong - LWJGL3", monitor, 0);

Это создаст полноэкранное окно с разрешением рабочего стола или обычное окно размера WINDOW_WIDTH x WINDOW_HEIGHT на основе значения START_FULSCREEN.

Но в Понге есть еще одна вещь — когда мы инициализируем нашу матрицу проекций с первым вызовом

onResize(WINDOW_WIDTH, WINDOW_HEIGHT

мы не принимаем во внимание, что фреймбуфер не будет такого размера, если мы находимся в полноэкранном режиме. Мы должны получить размер фреймбуфера, используя glfwGetFramebufferSize () (документация). Связывание LWJGL3 этого немного отличается, поскольку мы не имеем указателей. Мы используем java.nio.Buffers, в данном случае IntBuffers, чтобы дать функции где-то положить возвращаемые значения, а затем извлечь значения из буферов. Следовательно:

IntBuffer framebufferWidth = BufferUtils.createIntBuffer(1), 
                framebufferHeight = BufferUtils.createIntBuffer(1);
glfwGetFramebufferSize(window, framebufferWidth, framebufferHeight);
onResize(framebufferWidth.get(), framebufferHeight.get());

Понг теперь будет корректно отображаться и будет полностью функционировать.


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

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