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

Andry

Опубликованно: AndryAndry10.06.2018, 13:53
Последняя редакция, Andry: 13.06.2018 20:27

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

Добро пожаловать в MonkeyBrains вики!

  • С чего начать
  • Простое поведение
  • Реализация Пользовательского оружия
  • Реализация системы Очков Здоровья
  • Реализация системы Инвенторя
  • Система Событий в MonkeyBrains
  • Реализация Пользовательский control-ов для игры

С чего начать

  • Скачайте MonkeyBrains отсюда github.
  • Откройте его как проект в jMonkeyEngine SDK
  • Если не будут найдены библиотеки то добавьте библиотеку jme3-core.
  • В свойствах проекта в пункте Исходные файлы напротив Формат исходного/бинарного файла выберите JDK 8 или выше.
  • Далее очистите и соберите проект
  • Теперь в папке проекта dist появится файл MonkeyBrains.jar
  • Добавить его в свой проект в качестве библиотеки, и можете пользоваться.
  • Есть вероятность того что вам потребуется что то обновить так как он создавался при более ранней версии движка, нежели нынешняя.

Также следует знать что для не релизной версии, а версии в разработке данная статья устарела так как автор внёс некоторые существенные изменения в свой проект не исправив документацию и не обновив примеры. По сути она не закончена и я бы не рекомендовал её использовать новичкам. Вот его разъяснение:

Я сделал некоторые коммиты MonkeyBrains: переместил некоторые вещи в другой пакет (monkeystuff) и лишил агентов некоторых функций.

Старая функциональность сохраняется в новых BluePillAgents. Я назвал их таким образом, потому что я думаю, что это передает мое намерение: «Голубая Таблетка»(Blue Pill) — обычный способ, он хорошо документированы в вики, и он должны работать с текущими примерами.

Поскольку есть «Голубая Таблетка», будет также «Красная Таблетка»(Red Pill), которая будет немного отличаться. Я не являюсь экспертом в области ИИ, я просто хочу приключений в этом аспекте:

«Красная Таблетка» будет построена вокруг потребностей моей игры, так что да, я на самом деле буду есть свою собачью еду (обезьянью еду?).

Основные понятия:

Свой класс Агента вы наследуете от класса Agent. Он содержит атрибуты и операции над этими атрибутами. Их не рекомендуется изменять(расширять) при наследовании. Вместо расширения вы можете использовать метод setModel(T model) и задать свои настраиваемые атрибуты для агента.

Behavior Базовый класс для поведения агента. Каждое создаваемое поведение должно расширить этот класс. Каждое поведение затем должно реализовывать controlUpdate(float tpf). Те будет запрашивается com.jme3.scene.control.AbstractControl, который был расширен поведением(behavior). Также класс, который расширяет Behavior, должен иметь конструктор, который будет содержать агента в качестве параметра. При этом behavior(поведение) может изменять атрибуты агента, которые являются behavior(поведением). В com.jme3.ai.agents.behaviors.npc вы можете увидеть примеры некоторых простых видов поведения, которые легко использовать для ваших агентов NPC (Не персонажа Игрока). В com.jme3.ai.agents.behaviors.player есть некоторые примеры простого поведения, достаточные что бы поиграться с агентами.

Класс GameEntity нужен для всех сущностей в игре, которые имеют некоторое отношение к агентам. Примером может быть: Предметы, пули, препятствия, другие агенты (класс Agent расширяет GameEntities) …

MonkeyBrainsAppState Единственный класс, который содержит все игровые объекты и обновляет их. Он создан для упрощения манипуляций с агентами.

Пример:

public class Example extends SimpleApplication {

    //определение игры
    private MonkeyBrainsAppState brainsAppState = MonkeyBrainsAppState.getInstance(); 
    
    public static void main(String[] args) {
        Example app = new Example();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        //определение app
        brainsAppState.setApp(this);

        //инициализация агентов с их именами и spatial-ами
        Agent agent = new Agent("First agent", createAgentSpatial()); 
        //в фреймворке нет такого метода как createAgentSpatial()
        //предполагается что пользователь должен сам создавать свой spatial для игры

        //добавление агента в MonkeyBrainsAppState
        brainsAppState.addAgent(agent);

        //настройте moveSpeed(скоротьДвижения), rotationSpeed(скорость поворота), mass(вес)..
        agent.setMoveSpeed(20); 
        agent.setRotationSpeed(30);
        //используйте для управления поведения из com.jme3.ai.agents.behaviors.npc.steering
        agent.setMass(40);
        agent.setMaxForce(3);

        //создание основного поведения
        //агент может иметь только одно поведение, но это поведение может содержать другое поведение
        agent.setMainBehavior(new MyCustomBehavior(agent));

        //старт агентов
        brainsAppState.start();

    }

    @Override
    public void simpleUpdate(float tpf) {
        brainsAppState.update(tpf);
    }
}

Простое поведение

Простым поведением являются некоторые общие поведения, поддерживаемые MonkeyBrains.

SimpleMainBehavior — основное общее поведение для агентов. Основное поведение содержит другие поведения, и если оно активно, он обновит все разрешенные поведения. Вы можете добавить только одно управление поведением к этому контейнеру. Но вы можете использовать CompoundSteeringBehavior, чтобы объединить больше управлений поведениями в одно.

Пример:

Agent agent = new Agent("SimpleMain", createAgentSpatial());
SimpleMainBehavior mainBehavior = new SimpleMainBehavior(agent);
mainBehavior.addBehavior(new MyCustomBehavior1(agent));
mainBehavior.addBehavior(new MyCustomBehavior2(agent));
agent.setMainBehavior(mainBehavior);

SimpleMoveBehavior — это общее поведение перемещений для агентов. Агент должен перейти в намеченное положение или по moveDirection(направлению Движения). Если оба будут добавлены, то тогда агент переместится в намеченное положение. Предупреждение: иногда Агент никогда не будет двигаться точно в намеченное положение, если moveSpeed(скорость Движения) слишком высока, поэтому добавьте соответствующую ошибку расстояния. Вместо этого рекомендуется использовать управление поведениями(steering behaviors).

SimpleRotateBehavior — это общее поведение вращения. Если spatial не добавлен, то будут вращать все spatial-ы агентов. Если spatial был добавлен, то будет вращаться только этот spatial.

SimpleLookBehavior — это общее поведения зрения(взгляда) для агентов. Он требуется, для всех поведений в которые добавят слушатели. Это поведение может видеть только GameEntity(Игровые Сущности), если оно добавлено в MonkeyBrainsAppState. Чтобы правильно настроить это поведение, вам необходимо задать следующие параметры:

/**
 * Диапазон видимости. Как далеко может видеть агент.
 */
float visibilityRange;
/**
 * Угол, в котором будут видны GameEntities.
 */
float viewAngle;

Если эти параметры не заданы, тогда поведение зрения никогда не сообщит, что оно что-то увидело. Также необходимо добавить поведение, которое реализовано интерфейсом GameEntitySeenListener. (подробнее см. здесь).

Существует необязательный параметр:

   /**
     * Какие сущности будут сообщать поведению увиденное.
     */
    protected TypeOfWatching typeOfWatching;

    public static enum TypeOfWatching {

        /**
         * Зрение только для агентов.
         */
        AGENT_WATCHING,
        /**
         * Зрение только для игровых сущностей.
         */
        GAME_ENTITY_WATCHING,
        /**
         * Зрение для агентов и игровых сущностей.
         */
        WATCH_EVERYTHING;
    }

Вы можете изменить WATCH_EVERYTHING идущий по умолчанию, чтобы сообщалось только то, что вам нужно.

SimpleAttackBehavior — это общее поведение атаки для агентов. Это поведение агента для атаки targetEntity(намеченнойСущности) или фиксированной точки в пространстве. Он будет атаковать с оружием, и он не проверяет, есть ли у него боеприпасы. Оно реализует GameEntitySeenListener. Если не задана targetedEntity ни фиксированная точка в пространстве, поведение не будет активировано.

Простые поведения игрока реализованы здесь 2 поведениями для играбельности агента в MonkeyBrains.

SimplePlayerMoveBehavior — это общее поведение перемещения для агента, контролируемого игроком. Оно поддерживает AnalogListener. Вы должны добавить для него поведение и соответствующее имя при его использовании (оно должно быть таким же, как и в InputManager).

SimplePlayerAttackBehavior — это общая атака для агента, контролируемого игроком. Он поддерживает ActionListener. Он атакует огнём пуль в направлении щелчка мыши.

Реализация Пользовательского оружия

Оружие — самое важное в играх шутерах, поэтому MonkeyBrains позволяет легко реализовать пользовательское оружие, и оно хорошо интегрируется в фреймвор.

Основные понятия:

AbstractWeapon — абстрактный класс являющийся базовым классом для определения всего оружия, используемого агентами. Он расширяет GameEntity, поэтому вы можете использовать его как элемент, который вы можете найти на карте. Некоторые важные параметры для оружия:

/**
 * Название оружия.
 */
protected String name;
/**
 * Имя агента, к которому добавлено оружие.
 */
protected Agent agent;
/**
 * Максимальный диапазон оружия.
 */
protected float maxAttackRange;
/**
 * Минимальный диапазон оружия.
 */
protected float minAttackRange = 0;
/**
 * Урон от атаки оружием.
 */
protected float attackDamage;
/**
 * Сколько времени потребуется ждать для следующей атаки.
 */
protected float cooldown;

Методы, которые необходимо реализовать:

public abstract void attack(Vector3f targetPosition, float tpf);

/**
 * Метод для вычислений, если для оружия было выполнено требования
 * быть использованным. (Есть ли боеприпасы и.т.п.)
 *
 *Примечание: Не делайте проверку перезарядки, она уже проверяется этим фреймворком.
 *
 * @return true если используется, false в противном случае
 */
public abstract boolean isUsable();

/**
 * Метод для проверки есть ли здесь неограниченного использования оружия.
 *
 * @return true - неограниченное использование, false в противном случае
 */
protected abstract boolean isUnlimitedUse();

/**
 * Метод для уменьшения характеристик от оружия после использования. (патроны, мана, хп и.т.п.)
 */
protected abstract void useWeapon();

Поскольку в классе Agent нет слотов для оружия. Вам нужно будет реализовать Inventory(инвентарь) и поместить оружие внутри него. Не забудьте поместить все методы обновления оружия в метод обновления инвентаря. Если вы этого не сделаете, перезарядка оружия никогда не закончится.

AbstractBulletBasedWeapon — это абстрактный класс для оружия, который стреляет какой-то разновидностью пуль. Он имеет еще один параметр, и это AbstractBullet. Рекомендуется клонировать этои пули для целей оптимизации. Вместо поведения атаки вам необходимо реализовать:

/**
 * Настраиваем пулю, которая должна быть выпущена и придаем ей начальную скорость.
 *
 * @param направление
 * @param tpf
 * @return
 */
protected abstract AbstractBullet controlAttack(Vector3f direction, float tpf);

Поведение атаки уменьшит количество боеприпасов, сбросит перезарядку и добавит пулю в MonkeyAppState для обновления.

AbstractBullet — это базовый абстрактный класс для пуль в игре. Он также расширяет GameEntity. У него есть только один параметр, и это оружие, из которого им стреляли.

AbstractFirearmWeapon — это абстрактное оружие, которое имеет параметр numberOfBullets и имеет реализацию для всех методов, за исключением метода controlAttack(…).

AbstractEnergyWeapon аналогичен AbstractFirearmWeapon, за исключением того, что он имеет параметры:

/**
 * Количество энергии, необходимое для зарядки оружия. Установите значение -1, если бесконечно.
 */
protected int energyRequired;
/**
 * Источник энергии, который питает оружие.
 */
protected AbstractPowerSource powerSource;

AbstractPowerSource — это интерфейс для источника питания, используемого для AbstractEnergyWeapon. Он имеет методы:

/**
 * @param energyRequest(ТребуемаяЭнергия)
 * @return true если источник питания содержит в себе достаточно энергии.
 */
public boolean contains(int energyRequest);

/**
 * Уменьшая количество мощности в источнике питания.
 *
 * @param energyRequest(ТребуемаяЭнергия)
 */
public void consume(int energyRequest);

Реализация системы Очков Здоровья или Hit Points(Implementing Hit Points system)

Фреймворк MonkeyBrains обеспечивает гибкую реализацию системы очков здоровья. Это означает, что программист может реализовать, столько игровых сущностей сколько будут иметь очки здоровья (очки жизни, очки энергии…) и как управлять им.

Основные понятия

HitPoints — это интерфейс для реализации вашего собственного объекта HitPoints для вашей игровой сущности. Методы, которые должны быть реализованы:

/**
 * @return текущие очки здоровья игровой сущности.
 */
public float getCurrentHitPoints();

/**
 * @return максимальное значение очков здоровья для игровой сущности.
 */
public float getMaxHitPoints();

/**
 * Метод уменьшения очков здоровья.
 * @param damage(ущерб)
 */
public void decreaseHitPoints(double damage);

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

SimpleAgentHitPoints — это класс, который реализует HitPoints. Он реализован для агентов.

HitPointsControl — это интерфейс для игровой сущности цели которая потеряет свои очки здоровья. Он реализует только один метод:

/**
     * Уменьшение hitPoints сущности цели на величину ущерба причинённого 
     * атакующей сущностью.
     *
     * @param target(цель)
     * @param damage(ущерб)
     */
    public void decreaseHitPoints(GameEntity target, float damage);

Пример:

MonkeyBrainsAppState.getInstance().setHitPointsControl(new MyCustomHitPointsControl());
Agent target = new Agent("target", createAgentSpatial());
target.setHitPoints(new SimpleAgentHitPoints(agent));
//уменьшение очков здоровья цели на 30f
MonkeyBrainsAppState.getInstance().decreaseHitPoints(target, 30f);

Реализация системы Инвентаря(Implementing Inventory system)

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

Inventory — это интерфейс для реализации инвентаря (дух :)). Он содержит три метода:

/**
 * Метод для обновления все предметов инвентаря, таких как оружие и.т.п.
 *
 * @param tpf время на кадр
 */
public void update(float tpf);

/**
 * Возвращает общую массу инвентаря. Используется главным образом для управления поведением.
 *
 * @return
 */
public float getInventoryMass();

/**
 * Возвращает активное оружие. Это необходимо реализовать, если вы используете
 * SimpleAttackBehavior.
 *
 * @see SimpleAttackBehavior
 * @return
 */
public AbstractWeapon getActiveWeapon();

Пример:

Agent agent = new Agent("AgentWithInventory", createAgentSpatial());
agent.setInventory(new MyCustomInventory());

Система Событий в MonkeyBrains(Event system in MonkeyBrains)

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

Основные понятия:

GameEntityEvent — это базовое событие для всех событий игровых сущностей. Рекомендуется расширять этот класс для всех ваших пользовательских событий.

GameEntitySeenEvent — это событие, связанное со всеми увиденными игровыми сущностями. Он используется в SimpleLookBehavior.

GameEntitySeenListener — это интерфейс для слушателей события GameEntitySeenEvent. Нет никакого слушателя по умолчанию, предоставленного MonkeyBrains, и настоятельно рекомендуется использовать EventListener в качестве базового интерфейса для слушателей.

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

public class MyCustomGameEntityEvent extends GameEntityEvent {
    private GameEntity customEntity;
    
    public MyCustomGameEntityEvent(Object source, GameEntity gameEntity) {
        super(source);
        this.customEntity = gameEntity;
    }

    public GameEntity getCustomEntity() {
        return customEntity;
    }
}

Сделаем пользовательский слушатель:

public interface MyCustomGameEntityListener extends EventListener {
    public void handleMyCustomGameEntityEvent(MyCustomGameEntityEvent event);
}

Сделаем триггер поведения:

public class TriggerBehavior extends Behavior{
    private List<MyCustomGameEntityListener> listeners;

    public TriggerBehavior(Agent agent){
        super(agent);
        listeners = new ArrayList<MyCustomGameEntityListener>();
    }
    
    public void addListener(MyCustomGameEntityListener listener){
        listener.add(listener);
    }

    @Override
    protected void controlUpdate(float tpf) {
        ...
        //создадим MyCustomGameEntityEvent
        MyCustomGameEntityEvent event = new MyCustomGameEntityEvent(agent, customEntity);
        //перешлите его всем слушателям
        for (MyCustomGameEntityListener listener : listeners) {
            listener.handleMyCustomGameEntityEvent(event);
        }
    }    
}

Сделаем обработку поведения:

public class HandleBehavior extends Behavior implements MyCustomGameEntityListener{

    public HandleBehavior(Agent agent) {
        super(agent);
    }

    public void handleMyCustomGameEntityEvent(MyCustomGameEntityEvent event) {
        //сделайте что-нибудь с этим событием
    }

    @Override
    protected void controlUpdate(float tpf) {
        //код который обрабатывает поведение, обычно в игровом цикле
    }
}

Реализации основной логики:

Agent agent = new Agent("EventAgent", createAgentSpatial());
SimpleMainBehavior mainBehavior = new SimpleMainBehavior(agent);
TriggerBehavior triggerBehavior = new TriggerBehavior(agent);
HandleBehavior handleBehavior = new HandleBehavior(agent);
triggerBehavior.addListner(handleBehavior);
mainBehavior.addBehavior(triggerBehavior);
mainBehavior.addBehavior(handleBehavior);
agent.setMainBehavior(mainBehavior);

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

Реализация Пользовательский control-ов для игры

Класс MonkeyBrainsAppState с информацией об агентах и последовательности их поведения в игре. Нет необходимости использовать его, но он позволяет упростить обновление статуса игры. Содержит агенты и игровые сущности и обеспечивает общее управление ИИ. Он может быть расширен для реализации ваших собственных пользовательских операций, но это не рекомендуется. Вместо этого используйте GameControl.

GameControl — это интерфейс для игровых Control-ов, используемых в игре. Методы, которые необходимо выполнить:

/**
 * Добавить все inputManagerMapping которые будет использовать игроком.
 */
public void setInputManagerMapping();

/**
 * Добавить все настройки камеры, которые будут использоваться в игре.
 * @param cam 
 */
public void setCameraSettings(Camera cam);

/**
 * Добавьте все настройки fly camera(летающей камеры), которые будут использоваться в игре.
 * @param flyCam 
 */
public void setFlyCameraSettings(FlyByCamera flyCam);

/**
 * Метод для маркироваки конца. Должен так же останавливать игру.
 * @see Game#stop() 
 * @return 
 */
public boolean finish();

/**
 * Вычисление, выиграл ли агент игру.
 * @param agent
 * @return 
 */
public boolean win(Agent agent);

/**
 * Перезапуск всех параметров игры.
 */
public void restart();   

/**
 * Метод создания сущностей в заданной области.
 * @param gameEntity сущность, которая должна быть создана
 * @param область, в которой будет создана сущность
 */
public void spawn(GameEntity gameEntity, Vector3f... area);

Пример:

MonkeyBrainsAppState.getInstance().setGameControl(new MyCustomGameControl());

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


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

Этот репозиторий, это результат этапа проектирования фрймворка Atom в течение многих лет.

Мотивация

Он родился для того, чтобы помочь JME3 сосредоточиться на разработке игры/симуляции, и в дальнейшем продвигать последние и будущие Java-технологии, чтобы превратить разработку игр в предприятие/AAA в качестве конечной цели, но пойти на компромисс/совместимости с существующей инфраструктурой JME3 и OpenGL.

Его концепция, дизайн и архитектура вдохновлены игровым движком AAA и решением Enterprise Java.

Обзор

На этой странице:

  1. Цели Фреймворка Atom — это расширять JME3 и язык Java, чтобы они больше подходили для разработчика игр.
  2. Проблемы приложений в реальном времени (особенно игры).
  3. Решения и те базовые платформы, которые мы собираемся использовать для достижения этих целей.
  4. Оставшиеся работы и видение будущего

Цели дизайна

Общие цели

  • Гибкость: Игра | симуляции главное, но не навязанное!
  • Модульная: Введение зависимостей вместе с введением компонентов
  • Параллельно: Использовать параллельные вычисления
  • Следующий сведенья: Приходит с ещё развивающимися технологиями и мощью языка Java.
  • Облачность готова: Масштабирование до сети и распределённых вычислений
  • С простотой: У инструментов GUI почти везде, нулевые потребности в конфигурации
  • Перенос на устройства: Может работать на широком спектр устройств PC-мобильные-TV.. любое, на котором есть Java!

Дополнительные подцели

  • Минимальные зависимости и их наложение
  • Малый размер и эффективность

Слайды

Здесь вы можете просматривать слайды и загружать документы о дизайне фреймворка Atom.

Приложения реального времени

Дизайн Atom СОЗДАН одновременно для приложений в реальном времени, встроенных систем и ИГР

Атом в сравнении с Птолемеем

Как упоминалось в введении в фреймворк Atom, он действительно вдохновлен проектом Птолемей

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

В сравнении

Atom предназначен для СОЗДАНИЯ одновременно, приложений в реальном времени, встроенных систем и ИГР. Поэтому он больше фокусируется на генерации кода, профилировании, мониторинге; фокусируется больше на графике, физике, опыте игрока… и.т.д. В основе этого лежит довольно много концепций, собранных в Птолемее (не коде!).

Цели

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

  • Atom не имеет ни малейшего отношения/интереса относительно математической/физической коррекции (то есть симуляции), но имеет интерес к графической стороне и общему поведению.
  • У Atom есть дух приложений с открытым исходным кодом… но не академический! Вы можете видеть, что Птолемей довольно «сложный и не очень популярный, поскольку он предоставлен в первую очередь для академической среды; в отличие от него, Atom предоставляется для каждого пользователя и для игры как для детей в песочнице.

Различия в методах:

  • Atom не просто зависит от четко определенных моделей!
  • Atom использует простые модели, а иногда проще значит лучше!
    • Модель Простые Сущности (Simplier Entity) (не абстрактный объект из Java)
    • Модель Актера(Actor) так же не абстрактная и на самом деле из потоковой среды
    • Никаких контрактов в Системе, изучение Системных связей и интерактивности не связано только с анализом данных, то есть системой мониторинга потока данных по рабочей системе.
    • Timming: из-за отсутствия интересов в математике / физике, модель времени и прецизионная модель не определены, но также и на платформе Java.
  • Те, что выше и основные методы, которые вы можете найти ниже, заставляют Atom быть менее независимым от Java, но также хорошо встраивают характеристики языков Java и среды времени выполнения. Так что можно сказать, что Atom строится прямо на вершине Java без колебаний!

Atom в сравнении с JScience

Atom также зависит от Javolution и имеет некоторые части из базы кода JScience, но в очередной раз цель, сосредоточение Atom в играх и симуляции … Также много методов Atom отличается от используемого в JScience.

Можно также видеть, что Atom полностью заполняет экспериментальный пакет игр JScience. :)

Целевые Устройства и Платформы

PC


Mobile


Android


Web


HTML5 и WebGL

Проблемы


Решения и Фреймворки и Платформы

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

Сотни проектов с открытым исходным кодом …

Например, модуль AtomCore зависит от этих высококачественных библиотек:

  • JME3
  • Общие аннотации JSR Java:
  • Apache commons
    • Lang
    • Configurations
    • BeanUtils
    • Collections
  • Google’s
    • Guava:
    • Guice: Dependency injection
    • Snappy:
    • LevelDB
    • Auto

Другие требуют кусочков писать из sk

Платформа Atom для разработки дизайна

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

Игра и приложение в реальном времени


Кросс-игровые элементы

С уровня абстракции Game — это особый вид программного обеспечения (почти всегда):

  • составленные сущностями, и их Сцена;
  • где Действия происходят в цикле, процедура События;

Немного более подробно, Геймплей — это то, как игрок играет в Игру, имеет:

  • Logic:
    • Trigger: in which Conditions, active some appropriate Action, as primitive brick.
    • Rule: the laws, restrictions form the game rule which player, entities obey.
  • Routines: Situations/ Events/ Actions that happen in the game Cycle.
    • Story/Cinematic mode: When player just watch the game like a movie.
    • Interactive mode: When player interact with the game world
  • Control: The way player handle their entities
  • League:
    • Single: Infos, score, rewards stick to an individual
    • Multi: The way players join, left, make friend and interactive and play together…
  • Status: Way to pause/continue , save/load current game

«Программное обеспечение для игры» должно быть опубликовано в конкретной среде, а затем:

  • Configurations : appropriate settings for specific enviroment, device.
  • Data : appropriate size and format

Взаимодействие CPU-GPU


Java-родные взаимодействия


Тайминг


Цикл

Архитектура AtomCore

Ядро — это часть, которая фокусируется на игре и в режиме реального времени. Он объявляет

Здесь вы можете узнать больше об архитектуре Core. atomcore

Шаблоны проектирования и парадигмы программирования

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

узоры

Вопросы программирования


Java, но не только Java

Большинство «Java-вещей в AtomCore — это Bean and Properties. Два чистых типа данных Java, которые очень полезны в игровом мире. Бин предназначен для моделирования игровых объектов и свойств для конфигурации.

Конечно, другие технологии Java также используются, но не упоминаются, потому что это не обязательно. Но Bean and Properties — это те технологии, которые сильно используются!

«Хорошие расширения Java, используемые в AtomCore 0.2+: Guava:

  • Принесите Eventbus, сеть в порядке
  • Коллекция, Свободный, функциональный синтаксис и вкус к Java.
  • Гуава также поддерживает операции кэширования, отражения и более низкого уровня

Guice: принести инъекцию зависимости, лучший модульный тест, рефакторинг в легкой манере.

Groovy — это JVM-язык и глубоко интегрирован с AtomCore, наиболее удобен как язык сценариев, но помнит, что он также может заменить Java или рассматривать как Java. Groovy также предлагает намного больше превосходных вещей.

Код и данные

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

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

Вокруг Bean

Это так важно, чтобы упомянуть, что каждая инфраструктура технологий Atom основана на технологиях Bean / POJO.

Например:

  • AtomEX : the bridge to AKKA Actor model also use POJO as its candidate
  • EJB leverage…
  • Fx: use POJO as its effect elements

Вот краткое объяснение, почему Bean / POJO выбрано основой ядра Atom?

Как построено в Java-технологиях, Bean / POJO является единственным «хорошим решением»:
** «мост» ** ** от Java OOP до COP **, ** от Java OOP до AOP **
также можно рассматривать как
** от объектно-ориентированного программирования до ориентированного на данные программирования **
** от объектно-ориентированного программирования до аспектно-ориентированного программирования **
или ** Код, но также Данные ** …

Программирование Polyglot

Я хочу Лучшее из обоих миров! (.. если это возможно?)

Atom в своей основе пытается быть полиморфизмом (программирование полигонов), с набором с ООП, КС, АОП или функциональным … Да, это Java, но хороший вид Java.

Поскольку AtomScripting и другие используют Groovy, поэтому он наследует (много) емкость полигонов от Groovy.

Читайте: http://groovy-lang.org/Polyglot+Programming+with+Groovy

Функциональное реактивное программирование


Программирование на основе потока


Компонентное программирование


Комбинированное программирование


Управляемый данными и управляемый моделью и управляемый доменом


Модульная архитектура

Я хочу повторно использовать (или СУХОЙ) !!

Посмотрите, например, на игру JME3, например, менеджер, если вы хотите, чтобы работа двух менеджеров была совместной, но не зависела друг от друга, или что, если вы хотите, чтобы государство направляло Менеджер делать что-то, но имело минимальную информацию о них …

Более абстрактно, всякий раз, когда у вас есть какая-то Служба, которая свободно зависит друг от друга, вы должны попробовать Injection Dependency http://martinfowler.com/articles/injection.html.

Вот где Guice помогает в большой картине.

Внедрение зависимости


https://code.google.com/p/google-guice/

Инъекция компонентов


http://wiki.apidesign.org/wiki/Component_Injection

Инъекция зависимостей VS Компонентная инъекция


http://code.imagej.net/gbh/lookup/DependencyInjectionandLookup.html

Охлаждение управления зависимостью

Итак, что круто в зависимости от приложения в реальном времени и игры, в которую включен Atom … Многое, но позвольте мне отметить несколько:

Взаимозависимость в реальном времени — это новая функция для разработки игр … Представьте себе, что даже игра просто может загружать часть активов, а другая задерживается или отсутствует, график зависимости может помочь игровому циклу продолжать работать, часть его в среднем время.

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

Возможности предприятия. Вы можете себе представить, как инфраструктура Atom имеет тенденцию к объединению JME-игры и универсальной сети. На самом деле это не так сложно. Потому что Java-технологии уже существуют. Многие из них построены на вершине инъекции зависимостей и инверсии контроля (или еще) … Мне действительно нравится инъекция зависимостей, но я не могу согласиться с тем, что я всегда должен сочетаться с IoC как таковой. Это будет обсуждаться позже в этой документации

Предприятия


Услуги, зависимость и развязка

Мир enteprise развивает модульную парадигму, чтобы связать сервисы (база данных, конфигурации, сетевые протоколы, сеть …) и помочь им работать вместе в одном приложении.

Доступные услуги

Попробуйте AtomEx

К базе данных


В другой репозиторий


К конфигурациям


В Интернете

Мониторинг и рабочий процесс разработки


Будущее видение


Ссылки и вдохновение

Конструкция фреймворка Atom ограничена:

  • Игровая книга
  • Серия игр для игр
  • ИИ Game Engine книга
  • Книга Мудрости ИИ

другой GameEngine, который я использовал:

  • UDK
  • Единство
  • CryEngineSDK
  • Двигатели JavaScript-игр: CraftyJs, GameQuery ..
  • Flash-игры: Starling,
  • … десяток двигателей с близким исходным кодом.

другие технологии Java:

  • EJB
  • весна
  • Groovy
  • Netbean
  • … hunread проектов с открытым исходным кодом

Приступают к работе.


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

Открытая версия игры Dungeon Keeper 2

Цель: Полностью реализовать игру (версии 1.7 с 3 бонусными пакетами) в качестве открытой кросс-платформенной версии
— Минимум изменений;
— Использование оригинальных ресурсов игры.

Будущее: совершенствование графики и прочие ништяки.

Реализация: осуществляется в JAVA помощью JMonkeyEngine. В настоящее время мы используем JME 3.1 + JAVA 8

Проект размещен здесь: https://github.com/tonihele/OpenKeeper/

Группа проекта в ВК: https://vk.com/openkeeper
Группа проекта в Facebook: https://ru-ru.facebook.com/openkeeper/

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