forum.boolean.name

forum.boolean.name (http://forum.boolean.name/index.php)
-   FAQ (http://forum.boolean.name/forumdisplay.php?f=15)
-   -   Учебник по PhysX Wrapper для Blitz3D (http://forum.boolean.name/showthread.php?t=7568)

ABTOMAT 05.01.2009 06:20

Учебник по PhysX Wrapper для Blitz3D
 
Итак, по просьбам трудящихся я таки решился написать малюсенький туторчик по врапперу товарища Рендера
(
www.xors3d.com - офф. сайт, враппер можно скачать оттуда
http://forum.boolean.name/showthread.php?t=2734 - тема на Булке (похоже, сдохла, но ссылочка пускай будет)
http://blitz.pp.ru/forum/showthread.php?s=&threadid=10 - тема на Блицпп
)

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

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

Для тех, кто в танке. Что такое реал-таймовая физика в играх? Что такое PhysX Wrapper?

Думаю, вам уже известно, что такое реал-таймовая физика. Помните, как реалистично в Half-Life 2 отскакивали ящики от метких выстрелов, или как здорово труп валился вниз по лестнице, подчиняясь всем законам Ньютона? Помните, как в Crysis'е можно было схватить ящик и засадить им в супостатов, при этом сам ящик разбивался, а враги без сознания отлетали к дальней стенке? Всё это стало возможным относительно недавно, когда компьютеры стали достаточно мощными, чтобы обрабатывать подобные вычисления без огромных тормозов. Сначала такие игры можно было пересчитать по пальцам и они достаточно ограниченно использовали физику. Можно было потолкать ящики при помощи выстрелов, скинуть их в пропасть, но не более того. Позже трупы врагов стали тоже физическими и теперь перестали проваливаться сквозь стены (помните в Counter-Strike 1.6 если чел сидел в углу и его пристрелить, то он откинется на спину и только ножки торчат из стены. Поначалу забавно, но ни о какой реалистичности и речи быть не могло), а стали падать в соответствии с тем окружением, где они находятся. Благодаря этому смерти каждого персонажа перестали быть похожими друг на друга. Кстати, эта технология называется "Ragdoll", как такое сделать самому я тоже обязательно раскажу. Конечно, такие штуковины сделали игры намного более реалистичными, но не повлияли на геймплей. Игрой, по-настоящему завязанной на физике, стала Half-Life 2. Всякие приколы типа "Положи 5 кирпичей на один конец качели чтобы другой конец поднялся вверх и ты смог бы по нему забраться на уступ", "Поставь ящики горкой чтобы по ним забраться выше" или "Подсунь бочки с воздухом под решётку чтобы она вспыла из-под воды" просто немыслимы без физики. А одна гравипушка чего стоит! До этого ничего подобного нигде не встречалось и она была бы невозможно без рассчёта игровой физики.

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

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

Поначалу так и было. В играх не было очень качественной физики. В основном это были ящики, имеющие форму параллелепипеда, да какие-нибудь доски. В принципе, это вполне удовлетворяло потребности игродела того времени. Но требования неуклонно росли и программистам приходилось всё сложнее и сложнее. А ведь им кроме физики надо ещё писать движок, логику, графику... Но зачем 10 людям делать одно и то же, 10 раз, когда можно собраться вместе и сделать это один раз, но в 10 раз лучше, да ещё и дать пользоваться другим, чтобы те не изобретали велосипед? Так уже давно произошло с трёхмерными движками. Сначала геймдевелоперы сами пытались написать себе 3Д-графику, но, когда вышел Quake, стало ясно, что гораздо проще взять уже готовый движок и делать на нём игру, чем только год потратить на создание такого движка. (Справедливости ради стоит отметить, что и до этого делались попытки использовать движок другой игры, например, Doom. Например, на его движке написана культовая в своё время игра Blood. Но Quake Engine благодаря своей универсальности пользовался бешеным успехом и произвёл настоящий бум. Игры на нём и на его модифицированных версиях писались вплоть до середины 2000-х) Вот и мы используем Blitz3D и не задумываемся о всяких матрицах трансформации, конвеере рендера и т.п. - всё это уже сделано до нас. Точно так же произошло и с физическими движками. Стоит только оформить все необходимые функции и структуры в удобный интерфейс - и ими сможет пользоваться всякий желающий. Одним из самых известных, мощных и функциональных движков стал Havoc. Именно благодаря ему мы кидались ящиками из гравипушки в Half-Life 2, благодаря ему так смачно разлетались тушки скелетов в Painkiller'е. Одно время с ним конкрурировал физический движок Karma (известен нам по играм Unreal 2, Unreal Tournament 2003-2004, Postal 2, Ведьмак и многие другие игры на движке Unreal Engibe 2.0 и не только) И вот, совсем недавно на сцену вышел физический движок нового поколения - PhysX. Он примечателен тем, что стал первым (и пока единственным) физическим движком, который ускоряется аппаратно. Сначала эта возможность была доступна тольтко владельцам специальных устройств, именуемых "Ageia PhysX Chip", который стоил как хорошая видяха и пихался в PCI-разъём. Были выпущены специальные демки, в которых демонстрировался огромный прирост произовдительности по сравнению с рассчётами на ЦП. Но, к сожалению, игр, которые поддерживали этот физ. ускоритель, было катастрофически мало и желающих его купить нашлось немного. Позже компанию Ageia купила nVIDIA и внедрила аппаратную поддержку физики в свои видеокарты начиная с серии GeForce 8xxxx. Таким образом отпала необходимость покупать отдельный чип и любой владелец современной видеокарты GeForce мог кусить все прелести аппаратного ускорения физики. Вследствие этого популярность движка возросла, теперь список игр, его поддерживающих, стал достаточно внушительным. И, наконец, nVIDIA сделала всем нам огромный подарок, разрешив юзать PhysX всем желающим бесплатно (Вроде как есть какие-то ограничения, вроде того что надо указывать, что твоя игра использует PhysX, но это фигня, главное, что не надо платить баснословные бабки за лицензию). Это послужило причиной роста популярности движка и позволило ему на равных конкурировать с монстрилой Havoc'ом, который в последнее время теряет свои позиции. Нынче он используется в нескольких десятках самых известных игр, среди них: Unreal Tournament 3, Sacred 2, CellFactor и многие другие (думаю, все тайтлы перечислять не стоит, если вам это интересно, то на Википедии есть полный список, кроме того можете почитать там и о самом движке) Набор функций и библиотек для использования PhysX'а в своей игре называется PhysX SDK. Однако, он очень сложен в освоении да и не существует его версии для Blitz3D (если монстрилы индустрии вообще знают, что вообще есть на свете такая штука)

Как же быть? На наше счастье, существует враппер этого самого SDK для Блитза, именуемый PhysX Wrapper за авторством Андрея Гутыри, более известного, как Render. Увы, он не бесплатен, но прогерам кушать тоже хочется. Корче того, то, что просит за него афтор - воистину смехотворная сумма по сравнению с тысячами долларов, которые просят за лицензию движки ААА-класса.

Итак, что же представляет из себя этот враппер? А представляет он из себя набор dll-ок и деклз-файл, которые позволяют использовать движок PhysX на нашем старичке Блитзе. Да-да, вы не ослышались, это действитиельно возможно! Всё вышеописанное можно сделать и на Блитзе (с прямыми руками и головой на плечах, разумеется)

Конечно, для Блитза и ранее существовали врапперы физических движков (Newton, ODE). Я не стану перечислять их достоинства и недостатки, и так я уже расписал своё лирическое отступление на полчаса чтения)) Коль речь идёт о PhysX'е, я постараюсь объяснить новичкам как им пользоваться, а заодно, возможно, и сам приобрету что-то новое. Итак, вперёд!

ABTOMAT 05.01.2009 06:28

Ответ: Учебник по PhysX Wrapper для Blitz3D
 
Вложений: 2
Начало работы. Что понадобится? Первое простое приложение на ФизикСе.

Вам понадобятся:
  • Блитз3Д (Думаю, раз уж вы читаете тутор по врапперу для этого движка, то он (движок) у вас уже есть)
  • Физикс Враппер, установленный на него (можно скачать с офф сайта)
  • Какое-никакое знание Блитза, т.к. я не стану объяснять его азы, ведь статья не об этом.
  • Голова на плечах =)) (полностью на вашей совести)

Итак, качаем установщик враппера. Запускаем его, указываем путь к Блитзу. Там несложно, разберётесь. Собсно это всё. Теперь перезапускаем Блитз и можно уже писать программу.

Вот простой пример кода на Блитз3Д, который я взял за основу:

Код:

Graphics3D 800,600,32,2
SetBuffer BackBuffer()

cam = CreateCamera()
PositionEntity cam,0,10,-20

plane = CreatePlane()
EntityColor plane,64,128,128

Repeat

RenderWorld()
Flip

Until KeyHit(1)
End

Он создаёт камеру, зеленоватую плоскость и рисует это всё. Теперь надо подключить к проекту Физикс. Делается это при помощи команды:
Цитата:

pxCreateWorld(plane%, key$)
Собственно, она и говорит Блитзу, что отныне в проекте используется Физикс. В функцию надо передать параметр plane%, отвечающий за создание физической плоскости. Может принимать значения 1 или 0. Соответственно 1 - создать плоскость, а 0 - не создавать. Кроме этого, нужно передать параметр key$ - это ключ продукта, чтобы либа не надоедала сообщениями "This is demo version!". На пока вполне сгодится и демо-версия. Если у вас нет ключа - просто передайте пустую строку.
Эту команду надо вставить после инициализации графического 3Д-режима aka Graphics3D (как видите, я создал физ. плоскость и не стал передавать ключ):
Код:

pxCreateWorld(1,"")
Далее - нам понадобится обновлять физику каждый цикл. Делать это нужно перед рендером. Нам поможет команда:
Цитата:

pxRenderPhysic(time#, sinc%)
Она собственно и обновляет физику. В блитзе есть похожая команда UpdateWorld, которая обновляет каждый цикл коллизии и анимацию, так что использование схоже. Итак, у функции 2 параметра. Первый параметр time отвечает за скорость обновления физики. Чем меньше это число, тем быстре будут двигаться физические тела и быстрее происходить вообще все процессы в физическом мире. Пока что поставим сюда 60, это, я думаю, оптимальное значение для начала. Второй параметр sinc может принимать значения 0 или 1 и указывает, надо ли включать встроенную в синхронизацию. Пока что она работает не вполне корректно, так что лучше её отключить, а если она понадобится, то её можно сделать и своими силами. Итак, эту команду надо использовать перед рендером сцены:
Код:

pxRenderPhysic(60,0)
Ну чтож, мы подключили PhysX и прописали обновление физики. Но не хватает главного - физических объектов! Тут я сделаю опять небольшое лирическое отступление. В физических движках всё происходит отдельно от трёхмерного движка. То есть если у нас есть куб-трёхмерная модель и куб-физическое тело, то, когда мы обновляем физику, тело будет падать, кататься и вести себя по физическим законам. модель же ни с места не сдвинетя от этого самого обновления физики при помощи команды pxRenderPhysic. Привязать модель к телу на манер EntityParent тоже нельзя. Поэтоум надо вручную устанавливать модель в координаты и в поворот тела. Поначалу это кажется непривычным, но со временем вы поймёте, что это на самом деле удобно.
Итак, давайте создадим кубик. Обычный, блитзевский, при помощи всем нам хорошо известной команды CreateCube():
Код:

Cube = CreateCube()
Теперь надо создать для него физическое тело при помощи команды:
Цитата:

pxBodyCreateCube%( dx#, dy#, dz#, mass#)
По сути, пользоватья ей нужно так же, как и простым блитзевским CreateCube(). Но тут всё сложнее. Так как, к сожалению, масштабировать физические объекты в реальном времени нельзя, это надо делать при их создании. Поэтому первые три аргумента этой функции dx, dy, dz указывают масштабирование создаваемого физического тела-куба по осям X,Y,Z соответственно. Думаю, нам пока что хватит и обычного кубика размерами 1х1х1, поэтому мы укажет в этих параметрах единицы. Но кроме этого каждый физический объект должен иметь массу, именно за неё отвечает четвёртый аргумент mass. Думаю, масса 1 будет вполне достаточной. Физический куб, как и блитзевский, создаётся в координатах 0,0,0 с поворотом по осям 0,0,0.

На заметку
Если выставить массу 0, то объект уже не будет динамическим, а будет статическим. Об этом я расскажу позже, но пока просто имейте ввиду. Для динамических тел нам понадобится масса, отличная от нуля.


Итак, пишем код создания тела прямо после кода создания модели куба:
Код:

Body = pxBodyCreateCube(1,1,1,1)
Так, теперь у нас есть ещё и тело для куба. Но оно лежит в точке 0,0,0. Думаю, его надо приподнять повыше. Тут нам поможет команда:
Цитата:

pxBodySetPosition (body%, pos_x#, pos_y#, pos_z#)
Функция, по сути, аналогична Блитзевской PositionEntity, только в неё надо пихать первым аргументом body то тело, которое мы желаем переместить. В следующие три аргумента pos_x, pos_y, pos_z передаём новые координат для тела. Давайте поднимем тело куба на 10 единиц над землёй:
Код:

pxBodySetPosition Body,0,10,0
Ну вот, теперь ему есть откуда падать) Но тот куб, который мы увидим на экране всё ещё неподвижен :( как я писал выше, его надо вручную устанавливать в позицию и поворот соответствующего тела. К счастью, уже есть удобная команда, выполняющая это действие:
Цитата:

pxBodySetEntity (entity%, body%)
Первый параметр - это тело, которое нужно устанавить в позицию и поворот соответствующего тела, body - соответствующее тело. Вызывать такую функцию нужно каждый раз после обновления физики, но до вывода на экран. Т.е. между pxRenderPhysic и RenderWorld. Давайте применим её к нашему кубу и его телу:
Код:

pxBodySetEntity(Cube,Body)
Всё! Теперь можно запускать программу и наблюдать адение нашего кубика!
Да, чего-то здесь не хватает. Похоже, неплохо бы добавить освещение:
Код:

light = CreateLight()
Ну вот, теперь совсем другой вид! Собственно, у меня получилось вот что:

Полный код примера вы найдёте в аттаче "PhysXExample1.zip"
Мда, с одним кубиком не очень-то впечатляет. Давайте насоздаём их много. Используем для этого типы. Буду краток, есчли вы не очень хорошо разбираетесь в типах, то советую почитать вот этот перевод от товарища Импера:
http://forum.boolean.name/showthread.php?t=10
(я сам по нему когда-то учился :super: )

Так, значится. Создадим тип для кубика:

Код:

Type pxCube
        Field mesh
        Field body
End Type

Всё, в-общем-то, дорлжно быть ясно: в типе хранится модель и тело объекта. Больше нам пока ничего не надо.

Теперь напишем функцию обновления каждого кубика:

Код:

Function UpdatepxCubes()
        For pxC.pxCube = Each pxCube
                pxBodySetEntity pxC\mesh, pxC\body
        Next
End Function

Здесь перебирается каждый объект типа pxCube и модель позиционируется в координаты тела. Как вы уже догадались, использовать её надо аналогично pxBodySetEntity (т.е. между обновлением физики и рендером):
Код:

UpdatepxCubes()
Теперь можно написать и функцию создания. Это, в принципе, то же самое, что было описано выше для одного кубика, но теперь всё это упорядочено в типы:
Код:

Function CreatepxCube(x#,y#,z#)
        pxC.pxCube = New pxCube
        pxC\mesh = CreateCube()
        pxC\body = pxBodyCreateCube(1,1,1,1)
        pxBodySetPosition pxC\body,x,y,z
End Function

Создаётся объект pxCube, для него создаются модель и тело, а затем тело выставляется в переданные в функцию координаты.
Ну, раз мы упорядочили всё в типы, то можно удалить наш старый кубик.
Стираем этот код:
Код:

Cube = CreateCube()
Body = pxBodyCreateCube(1,1,1,1)
pxBodySetPosition Body,0,10,0

и этот:
Код:

pxBodySetEntity(Cube,Body)
Давайте создавать кубики по нажатию пробела в точке 0,10,0. Теперь это совсем несложно:
Код:

If KeyHit(57) Then CreatepxCube(0,10,0)
Всё! Можно запускать. Запустили и тарабаним по пробелу. Вот что вышло у меня:

Да, этот результат намного интереснее!
Полный код примера вы найдёте в аттаче "PhysXExample2.zip"

Ну что ж, я описал базовые знания об этом враппере. Думаю, они окажутся вам полезными и вы прочитаете продолжение, которое я вскоре напишу. Однако не бойтесь экспериментировать и читать хелп самостоятельно. Ладно, уже скоро полшестого утра по Москве, пойду спать. -_-

ABTOMAT 06.01.2009 05:25

Другие примитивы.
 
Вложений: 2
Другие примитивы.
Итак, вчера я рассказал как создавать кубики. Но кроме них в ФизикСе есть ещё и другие примитивы. Думаю, будет полезно с ними кратко познакомиться.
Первым будет цилиндр:
Цитата:

pxBodyCreateCylinder%(radius#, height#, nbEdge%, mass#)
Итак, эта функция создаёт физическое тело-цидиндр. Функция очень похожа на pxBodyCreateCube, только тут вместо масштаба по осям нужно указывать радиус цилиндра radius#, высоту цилиндра height# и количество граней цилиндра nbEdge%. Следует заметить, что . Масса цилиндра mass# указывается точно так же, как и для куба.
Теперь давайте изменим пример PhysXExample2.zip чтобы он создавал не только кубики, но и цилиндры, причём случайно. Я изменил код создания куба таким образом:
Код:

Function CreatepxCube(x#,y#,z#)   
    pxC.pxCube = New pxCube
    fig = Rand(1,2)
    Select fig
        Case 1 ; куб
            pxC\mesh = CreateCube()
            pxC\body = pxBodyCreateCube(1,1,1,1)
        Case 2 ; цилиндр
            pxC\mesh = CreateCube()
            pxC\body = pxBodyCreateCylinder(1, 3, 8, 1)
    End Select
    pxBodySetPosition pxC\body,x,y,z
End Function

Как видите, сначала случайным образом выбирается переменная fig.
Если она равна единице, то создаётся куб, как бы это делали раньше, а если двойке - то физическим телом будет цилиндр. Как вы можете видеть, для него я выбрал радиус 1, высоту 3, 8 граней и массу 1.
Но модель по-прежнему куб. Непорядок. Куб с физической моделью цилиндра - это жестоко. Пускай будет блитзевский цилиндр с 8 гранями:
Код:

pxC\mesh = CreateCylinder(8)
Но так как я указал физической модели высоту 3, то и у модельки тоже надо изменить высоту до трёх (напомню, что при создании она равна 1). Сделаем это при помощи простого масштабирования:
Код:

ScaleEntity pxC\mesh,1,3,1
Ну вот! Теперь если поклацать по пробелу, то мы увидим созданные не только кубики, но и цилиндрики:

Для порядку я решил переименовать тип из pxCube в pxBody т.к. обзывать цилиндр кубом не очень корректно. Делать это необязательно, но это сделает код более понятным. Теперь функции и тип выглядят таким образом:
Код:

Type pxBody
    Field mesh
    Field body
End Type

Function UpdatepxBodies()
    For pxB.pxBody = Each pxBody
        pxBodySetEntity pxB\mesh, pxB\body
    Next
End Function

Function CreatepxBody(x#,y#,z#)   
    pxB.pxBody = New pxBody
    fig = Rand(1,2)
    Select fig
        Case 1 ; куб
            pxB\mesh = CreateCube()
            pxB\body = pxBodyCreateCube(1,1,1,1)
        Case 2 ; цилиндр
            pxB\mesh = CreateCylinder(8)
            ScaleEntity pxb\mesh,1,3,1
            pxB\body = pxBodyCreateCylinder(1, 3, 8, 1)
    End Select
    pxBodySetPosition pxB\body,x,y,z
End Function

Надеюсь, вы уже догадались, что в этом случае нужно поменять название ф-ии и при создании при обновлении в цикле:
Код:

If KeyHit(57) Then CreatepxBody(0,10,0)
Код:

UpdatepxBodies()
Запускаем, чтобы на всякий случай убедиться, что всё работает.
Полный код примера вы найдёте в аттаче "PhysXExample3.zip"

Продолжаем тему примитивов. На очереди у нас сфер. Ну, тут всё предельно просто:
Цитата:

pxBodyCreateSphere(radius#, mass#)

radius - радиус сферы, mass - масса. Тут и понимать нечего. В визуальном плане ей соответствует блитзевская сфера, создаваемая командой CreateSphere(). Давайте попробуем в наш пример встроить и сферу:
Код:

Function CreatepxBody(x#,y#,z#)   
    pxB.pxBody = New pxBody
    fig = Rand(1,3)
    Select fig
        Case 1 ; куб
            pxB\mesh = CreateCube()
            pxB\body = pxBodyCreateCube(1,1,1,1)
        Case 2 ; цилиндр
            pxB\mesh = CreateCylinder(8)
            ScaleEntity pxb\mesh,1,3,1
            pxB\body = pxBodyCreateCylinder(1, 3, 8, 1)
        Case 3 ; сфера
            pxB\mesh = CreateSphere(8)
            pxB\body = pxBodyCreateSphere(1,1)
    End Select
    pxBodySetPosition pxB\body,x,y,z
End Function

Как видите, я добавил третий случай (когда fig = 3). Если это происходит, создаётся физическая сфера радиусом 1 и массой 1 да соответствующая ей модель-сфера.
Запускаем, давим на пробел - теперь у нас ещё и шарики появляются.

Но есть у сферы одна важная особенность, делающая её отличной от других примитивов. Если помните, при создании физического цилиндра мы указывали количество его граней. То есть в предыдущем примере получился никакой не цилиндр, а 8-угольная призма. Это происходит потому, что каждый объект в ФизикСе (да и во многих других движках) имеет конечное количество точек. Чем больше точек, тем более округлую поверзность можн осмоделлировать, но тем больше будут затраты на расчёт физики. В-общем, тут полная аналогия с полигональной графикой: чем больеш треугольников - тем круглее и точнее поверхность, но тем больеш нагрузка на видеокарту. Так вот, физ.сфера является исключением из этого правила! Она действительно математически является сферой (т.е. идеально круглой). Насколько мне известно, для таких объектов в Физиксе обработка происходит по несколько другим алгоритмам. За счёт того, что сферу можно легко описать лишь её радиусом, вычисления получаются во много раз быстрее, чем если то же самое делать из точек и треугольников, как цилиндр. Поэтому запомните: если вам нужно создать физический объект, имеющий шарообразную форму или близкую к ней, используйте физический примитив-сферу: это ускорит расчёт физики, чем если сферу моделировать как другие тела.

Теперь перейдём к капсуле.
Думаю, сначала надо рассказать, что же такое капсула. Она предстваляет из себя примерно такую штуку:

По сути, это цилиндр с закруглёнными краями. Чем-то напоминает пилюлю. Создаётся вот такой командой:
Цитата:

pxBodyCreateCapsule%(height#,radius#,mass#)
Параметр height указывает высоту цилиндрической части капсулы, radius - радиус капсулы, mass - масса.
Глядите на схему, чтобы лучше понять.
Попробуем у нас создавать капсулу высотой 3, радиусом 1 и массой 1:
Код:

pxB\body = pxBodyCreateCapsule(3,1,1)
Теперь надо бы создать для капсулы визуальную модель. Так как в Блитзе нету примитива-капсулы, то нужна для удобства собственная функция создания меша капсулы. Вот моя попытка изобрести такую функцию:
Код:

Function CreateCapsule(height#,radius# )
    cyl = CreateCylinder()
    ScaleMesh cyl, radius, height-radius*1.5, radius
   
    sph = CreateSphere()
    ScaleEntity sph, radius,radius,radius
    PositionMesh sph, 0,height/2,0
    AddMesh sph, cyl
   
    PositionMesh sph, 0,-height,0
    AddMesh sph, cyl

    FreeEntity sph
    Return cyl
End Function

Конечно, можно оптимизировать её (например, попытаться удалить части сфер, которые попадают внутрь цилиндра, или убрать "шапки" цилиндров, но это долго, геморно и к PhysX'у непосредственно не относится. А для учебных целей нам вполне хватит и такой функции. Итак, теперь функция CreatepxBody выглядит так:
Код:

Function CreatepxBody(x#,y#,z#)   
    pxB.pxBody = New pxBody
    fig = Rand(1,4)
    Select fig
        Case 1 ; куб
            pxB\mesh = CreateCube()
            pxB\body = pxBodyCreateCube(1,1,1,1)
        Case 2 ; цилиндр
            pxB\mesh = CreateCylinder(8)
            ScaleEntity pxb\mesh,1,3,1
            pxB\body = pxBodyCreateCylinder(1, 3, 8, 1)
        Case 3 ; сфера
            pxB\mesh = CreateSphere(8)
            pxB\body = pxBodyCreateSphere(1,1)
        Case 4 ; капсула
            pxB\mesh = CreateCapsule(3,1)
            pxB\body = pxBodyCreateCapsule(3,1,1)
    End Select
    pxBodySetPosition pxB\body,x,y,z
End Function

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

Полный код примера вы найдёте в аттаче "PhysXExample4.zip"

Что-то уже поздновато -_-
Думаю, на сегодня всё. Завтра я расскажу о хуллах и тримешах.
Продолжайте экспериментировать.

ABTOMAT 06.01.2009 22:33

Ответ: Учебник по PhysX Wrapper для Blitz3D
 
Вложений: 2
Хулл, или как сделать физическую модель в 3D Studio max'е

В прошлые разы я рассказал об основных физических примитивах на PhysX Wrapper'е, однако только ими дело не ограничивается. Можно (куда ж без этого?) создавать физические тела произвольной формы. Для этого существуют Hull, Trimesh и Compound.

Итак, начнём с Хулла. Хулл - это выпуклый физический объект. Обратите внимание: выпуклый.

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

Если это всё-таки возможно, то такое тело выпуклым не будет:

То есть выпуклое тело - такое, в котором нет вмятин. Надеюсь, вы меня поняли.
Все примитивы, рассмотренные ранее были выпуклыми телами. По сути, оони все (кроме сферы и капсулы) являются хуллами.
Но мы можем создать хулл и произвольной формы, имея набор точек (т.к. из набора точек можно построить выпуклое тело). Его можно считать, скажем, с трёхмерной полигональной модели, а это значит, что по сути хуллы можно редактировать в 3Д Максе или любом другом трёхмерном редакторе, лишь бы он умел сохранять в блитзопонятный формат. Вот, сделал в Максе от балды такую модельку:

Чем-то напоминает пресс-папье. Как видите, эта модель выпуклая.

Теперь давайте используем её в Блитзе. Возьмём за основу Пример 3 (ищите в аттачах выше). В ём мы могли создавать различные физические объекты по нажатию пробела. Но давайте-ка их выкинем от греха подальше и заменим их на вышеуказанный объект. Я сохранил ту модель в "PressPapier.b3d" и теперь код создания физ. объекта будет выглядеть примерно так (в самом начале проги я загрузил модель в глобальную переменную PressPapierMesh, при создании только копирую её):
Код:

Global PressPapierMesh = LoadMesh("PressPapier.b3d")
HideEntity PressPapierMesh

...
Код:

Function CreatepxBody(x#,y#,z#)   
    pxB.pxBody = New pxBody
   
    pxB\mesh = CreateCube()
    pxB\body = pxBodyCreateCube(1,1,1,1)

    pxBodySetPosition pxB\body,x,y,z
End Function

Так, вроде, работает. Но физической моделью всё ещё остаётся кубик. Надо заменить его на физ. тело, соответствующее нашей модели. Нам надо познакомиться с командой создания хулла:
Цитата:

pxBodyCreateHull%( vbank%, nvert%,mass#)
vbank - ссылка на банк, в котором содержится информация о вершинах. Структуру банка я опишу ниже.
nvert - количество вершин, из которых будет состоять хулл.
mass - масса
Количество треугольников, которое получится в конечном хулле не должно превышать 512 ! Так что не надо пытаться строить хуллы из High-Poly моделей.

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

Структура банка вот такая:
Цитата:

Размер банка - количество вершин * 12

n*12+0 - координата X n-ной вершины (вещественное число - float)
n*12+4 - координата Y n-ной вершины (вещественное число - float)
n*12+8 - координата Z n-ной вершины (вещественное число - float)
Кстати, в хелпе этого нет ;) Досадное упущение.

Но не будем заморачиваться. В примрах к врапперу уже есть функция, которая сама создаёт банк из 3Д-модели и из него физическое тело. Просто я хотел чтобы вы знали структуру банка и в случае чего могли написать его генерацию самостоятельно. Как выяснится дальше, нижеследующая функция далеко не всегда идеальна:

Код:

Function BodyCreateHull%(mesh%, mass#)

    Local nsurf = CountSurfaces(mesh)
    Local nvert = 0
    For ns = 1 To nsurf
        Local surf = GetSurface(mesh,ns)
        nvert = nvert + CountVertices(surf)
    Next
        vbank = CreateBank(nvert*4*3)
    nv = 0
    For ns = 1 To nsurf
        surf = GetSurface(mesh,ns)
        nvv = CountVertices(surf)
        For nvc = 0 To nvv - 1
            PokeFloat vbank,nv*12+0,VertexX(surf,nvc)
            PokeFloat vbank,nv*12+4,VertexY(surf,nvc)
            PokeFloat vbank,nv*12+8,VertexZ(surf,nvc)
            nv = nv+1
        Next
    Next
    Local bbb%= pxBodyCreateHull(vbank, nvert, mass)
    FreeBank vbank
    Return bbb
End Function

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

Но тут есть одна неприятность. Дело в том, что если использовать (в Максе или в Блитзе) различные трансформации вроде поворотов и масштабов, то координаты вершин непосредственно не затрагиваются, а меняются некие параметры объекта, на основе которых перед рендером он будет отскейлен и повёрнут как надо. Но ведь мы считываем информацию непосредственно с вершин модели! А это означает, что вышеописанные трансформации никакого эффекта не дадут. Очень часто на форумах пишут о том, что тело из модели создаётся неправильно и т.п. - в 90% случаев причиной как раз является то, что я только что описал. Как же быть?
  • Перед экспортом из Макса следует произвести такую операцию: выделите объект, перейдите на вкладку "Utilities" справа и нажмите на кнопку "ResetXForm" а затем на "Reset Selected" (см. картинку). Также лучше отключите все материалы и группы сглаживания.

    В других редакторах не знаю как - ищите сами либо спрашивайте на соотв. форумах. Буду рад добавить если кто подскажет.
  • В блитзе с объектом, из которого будете делать Hull, НЕ использйуте команды: TurnEntity, RotateEntity, MoveEntity, PositionEntity, ScaleEntity. Вместо них надо использовать PositionMesh, RotateMesh, ScaleMesh.
Хочется добавить, что если вы попытаетесь создать хулл из невыпуклого меша, то при создании "вмятины" будут "заровнены", т.е. ошибок не вылетит и тело создастся. Но всё же лучше следите за формой мешей сами. Ведь очень неприятно получить не то, что ты делал.

В моей модели я уже всё сделал по-вышеописанному, так что проблем быть не должно. Однако помните об этом, это очень важно.
Так-с, теперь наконец можно непосредственно создать хулл. Пусть у него будет масса 1 (о да, я знаю, что у меня богатая фантазия на выбор массы:-D ), тогда код создания будет выглядеть так:
Код:

BodyCreateHull(PressPapierMesh, 1)
Вставим это вместо pxBodyCreateCube в нашей функции. Теперь запускаем и любуемся. Вроде, поведение объектов соответствует визуальной модели и это хорошо.

Но мы каждый раз при создании физ. объекта генерируем хулл из вершин. Это отнимает немного времени, но всё же оно достаточно значительное: до 1 мс. Но можно очень легко избавиться от этого: в самом начале создадим хулл, а потом будем только копировать его. Копирование займёт гораздо меньше времени, чем создание. Итак, в самом начале рядом с загрузкой модели создадим ещё одну глобальную переменную, в которую мы создадим хулл и откуда будем копировать тело:
Код:

Global PressPapierHull = BodyCreateHull(PressPapierMesh, 1)
Так, теперь настало время копировать физическое тело. Тут нам поможет команда
Цитата:

pxCopyBody%(body%)
Эта команда копирует тело. Она практически идентична блитзевскому CopyEntity, так что, я думаю, с пониманием принципа её использования у вас не возникнет проблем. Помните, что копия тела создаётся в точке 0,0,0 с поворотом 0,0,0. Почему-то многие забывают об этом, хотя это и очевидно. Помните, что копирование происходит намного быстрее, чем создание. Поэтому использовать нужно в первую очередь копирование, если это возможно.

Итаке, вместо того, чтобы заново рассчитывать тело при создании очередной "промокашки":
Код:

pxB\body = BodyCreateHull(PressPapierMesh, 1)
мы будем его просто копировать:
Код:

pxB\body = pxCopyBody(PressPapierHull)
Запускаем, смотрим. Вот и первая проблема. Похоже, что на полу чаляется какое-то невидимое тело. Это, как нетрудно догадаться, то тело, которое мы сгенерировали в самом начале чтобы делать из него копии. Вот мы его создали и оно валяется бесхозное, мозоля глаза. Надо что-то делать.

А делать мы будем вот что: отключим телу коллизию с другими телами, дабы оно не влияло на их перемещение. Делается это при помощи команды
Цитата:

xBodySetFlagCollision%(body%,stat%)
Устанавливает флаг коллизии с телом.
stat - показывает, нужно ли включить или отключить коллизию. 1 - вкл, 0 - выкл.
body - тело, к которому этот флаг применяется.

В нашем коде отключение коллизии для PressPapierHull будет выглядеть примерно так:

Код:

pxBodySetFlagCollision(Int PressPapierHull, 0)
Ну вот, теперь нам ничего не мешает там, внизу.

Полный код примера вы найдёте в аттаче "PhysXExample5.zip"

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

Кстати! (Это очень важно!) У визуальной модели и у физической модели центры должны находиться в одних и тех же координатах! Для удобства лучше делать их в одном и том же max-файле, чтобы физ. модель совпадала с визуальной насколько это возможно.
Вот, сделал аналогичную модель в Максе, она состоит из меньшего числа полигонов:

Её я сохранил в файл "PressPapierPhys.b3d"
Кстати! Т.к. нам нужны лишь координаты вершин, то такую информацию, как нормали, текстуры, материалы и т.п. можно (и нужно) не сохранять!
Вот теперь загрузим модельку из этого файла, создадим хулл уже из неё и саму модель удалим, т.к. она нам больше не понадобится (если вы внимательно читали всё что я писал ранее, то код должен быть понятен):
Код:

HullMesh = LoadMesh("PressPapierPhys.b3d")
Global PressPapierHull = BodyCreateHull(HullMesh, 1)
pxBodySetFlagCollision(Int PressPapierHull, 0)
FreeEntity HullMesh

Запускаем, смотрим. Ха! На глаз практически не отличишь, а ресурсов кушает теперь заметно меньше!



Всегда упрощайте физическую модель когда это не в ущерб конечному результату. Тут намного больше возможностей, ведь игрок не видит физическую модель, а это значит что даже если она будет совсем уж угловатая, пользователь этого не заметит. Конечно же, делать физ. модель для бочки треугольной не стоит: она просто не сможет катиться по земле. Во всём нужно знать меру.

Полный код примера вы найдёте в аттаче "PhysXExample6.zip"

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

ABTOMAT 09.01.2009 20:19

Ответ: Учебник по PhysX Wrapper для Blitz3D
 
Вложений: 1
Trimesh или физическая модель невыпуклых тел.

В прошлый раз я рассказал о хуллах - выпуклых телах (см. предыдущий пост - как отличить выпуклое тело от невыпуклого). А что делать, если нам надо создать физическую модель невыпуклого тела? Например, физ. модель для комнаты, для кастрюли, стула, где есть множество внутренних пространств?

Для этого в Физиксе существуют такие понятия, как Тримеш (Trimesh) и Компаунд (Compound)

Компаунд - это как бы связка из нескольких хуллов (выпуклых тел). Например стул можно представить в качестве нескольких прямоугольных параллепипедов (в народе - "собрать из кубиков"), я тут попытался изобразить:



То есть невыпуклый объект представляется как совокупность выпуклых. Затраты на расчёт такого объекта будут совсем небольшими (по сравнению с Тримешем). То есть если объект можно представить в виде кубиков, цилиндров и прочих выпуклых объектов то целесообразно использовать его.

Если же это невозможно (например, если надо сделать (физически честную! естественно для небольших ведёрок в экшене лучше сделать просто хулл) модель ведра, то будет очень проблематично выстраивать "частокол" из "досочек" по периметру, да и вычислительные затраты будут большими) то можно использовать Тримеш. Тримеш - это физическая модель невыпуклого тела. То есть по-честному рассчитываются всякие спадины, дырки и прочие "впуклости". Само собой, особенно, если тело динамическое, это влечёт намного большие вычислительные затраты по сравнению с хуллами и компаундами. Так что прежде чем использовать тримеш, лучше подумать нельзя ли обойтись иными способами. Я бы рекомендовал использовать его для создания физ. модели уровня (т.к. статика жрёт меньше ресурсов), а для динамических объектов - использовать хуллы и компаунды. Существует очень важный недостаток тримешей, ограничивающий их применение. Дело в том, что в физиксе нету коллизии trimesh-to-trimesh. А это значит что два тела-тримеша будут просто проваливаться друг сквозь друга. Поэтому тримеш используем только для статики.

Итак, этот пост будет посвящён тримешу.

Сделал я в Максе такую модельку:

Эдакая совдеповкская кастрюля. Это яркий пример где целесообразно использовать тримеш. Сделаем это.

Возьмём за основу пример 6.
Переделаем функцию создания объекта таким образом:
Код:

Function CreatepxBody(x#,y#,z#, fig = 0)       
        pxB.pxBody = New pxBody
       
        If fig = 0 Then fig = Rand(1,2)
        Select fig
                Case 1 ; пресс-папье
                        pxB\mesh = CopyEntity(PressPapierMesh)
                        pxB\body = pxCopyBody(PressPapierHull)
                Case 2 ; кастрюля
                       
        End Select
        pxBodySetPosition pxB\body,x,y,z
End Function

Надеюсь, тут понятно. Суть изменений: теперь в функцию передаётся параметр fig, который указывает, какой именно объект создавать (пресс-папье, кастрюля) если он не указан или равен 0 то тогда это выбирается рандомно.

Теперь надо разобраться с кастрюлей:

Код:

Global PotMesh = LoadMesh("Pot.b3d")
HideEntity PotMesh

Вот, я загрузил модель кастрюли и скрыл её чтоб не мешала. Думаю, из предыдущих примеров это ясно.
В функции создания объекта дописываем копирование сетки.
Код:

                Case 2 ; кастрюля
                        pxB\mesh = CopyEntity(PotMesh)

А как быть с телом? Надо создать тримеш. Тут нам поможет команда:
Цитата:

pxCreateTriMesh%(vbank%, fbank%, MESH_NBVERTICES%, MESH_NBFACES%, mass#)
Создаёт тримеш. Параметры:

Vbank - банк вершин,
Fbank - банк треугольников,
MESH_NBVERTICES - количество вершин,
MESH_NBFACES - количество треугольников,
mass - масса.

Структура Vbank'а идентична тому, который используется при создании хулла. Структура Fbank'а выглядитследующим образом:

Цитата:

Размер банка - количество треугольников * 12

n*12+0 - индекс нулевой вершины n-ного треугольника (целое число - int)
n*12+4 - индекс первой вершины n-ного треугольника (целое число - int)
n*12+8 - индекс второй вершины n-ного треугольника (целое число - int)

* Индексы вершин соответствуют тому порядку, в котором они заносились в Vbank. Если использовались стандартные блитзевские индексы вершин в меше, то проблем не будет.
Опять же, в примерах есть соответствующая функция создания Тримеша из блитзевского меша:

Код:

Function BodyCreateMesh(mesh%)
        nsurf = CountSurfaces(mesh)
        nvert = 0
        nface=0
        For ns = 1 To nsurf
                Local surf = GetSurface(mesh,ns)
                nface = nface+CountTriangles(surf)
                nvert = nvert +CountVertices(surf)
        Next

        fbank = CreateBank(nface*4*3)
        nf = 0
        vbank = CreateBank(nvert*4*3)
        nv = 0
        For ns = 1 To nsurf
                surf = GetSurface(mesh,ns)
                nfv = CountTriangles(surf)
                For nfc = 0 To nfv -1
                        PokeInt fbank,nf*12+0,TriangleVertex(surf,nfc,0)
                        PokeInt fbank,nf*12+4,TriangleVertex(surf,nfc,1)
                        PokeInt fbank,nf*12+8,TriangleVertex(surf,nfc,2)
                        nf=nf+1
                Next

                nvv = CountVertices(surf)
                For nvc = 0 To nvv - 1
                        PokeFloat vbank,nv*12+0,VertexX(surf,nvc)
                        PokeFloat vbank,nv*12+4,VertexY(surf,nvc)
                        PokeFloat vbank,nv*12+8,VertexZ(surf,nvc)
                        nv = nv+1
                Next
        Next
        bbb%=pxCreateTriMesh(vbank, fbank, nvert, nface,0)
        FreeBank vbank
        FreeBank fbank
        Return bbb%
End Function

Внимание! При экспорте из макса модели для последуюбщей генерации из неё тримеша нужно придерживаться тех же правил, что и для хулла! (ищите в посте выше)

Из ентити она создаёт Тримеш с массой 0 (т.е. статическое тело)
Но мы всё-таки попробуем создать динамический тримеш. Поэтому модифицируем функцию таким образом:
Код:

Function BodyCreateMesh(mesh%, mass#=0)
...
bbb%=pxCreateTriMesh(vbank, fbank, nvert, nface,mass)

Вот, теперь в функцию можно передать значение массы и тримеш создастся с указанной массой.

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

Код:

Global PotHull = BodyCreateMesh(PotMesh, 5)
pxBodySetFlagCollision(PotHull, 0)

Разница - в том что вместо BodyCreateHull используется BodyCreateMesh, ну и массу я поставил побольше.

При создании кастрюли просто копируем это тело:

Код:

pxB\body = pxCopyBody(PotHull)
Теперь изменим немного условие создания объекта. Пускай это будут разные клавиши:

Код:

If KeyHit(57) Then CreatepxBody(0,20,0,1)
If KeyHit(15) Then CreatepxBody(0,10,0,2)

Табуляция - кастрюля, пробел - промокашка. Тут, наверное, всё понятно.
Кстати, внимательные догадались, что теперь хуллы создаются чуть выше ;)

На всякий случай оnодвинем камеру подальше, а то великовата вышла кастрюля :-D

Код:

PositionEntity cam,0,50,-50
TurnEntity cam,30,0,0

Запускаем, давим сначала на табуляцию, потом на пробел:
(я ещё сделал рандомное назначение цвета для промокашек - думаю, не стоит объяснять как это делается)



Как видите, вся невыпуклость кастрюли налицо. :super:

Однако, попробуйте нажимать TAB несколько раз. Видите, одна кастрюля проваливается в другую? :( Поэтому, повторюсь, тримеши надо использовать только для статических объектов.

При генерации тримеша подставим массу 0:
Код:

Global PotHull = BodyCreateMesh(PotMesh, 0)
Как видите, теперь на кастрюлю не действует гравитация и столкновения с объектами не сообщают ей никаких импульсов, т.е. она статична. Именно так и создаются физические модели для уровней (Конечно, кастрюля - не слишком удачный пример для уровня).

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



Кстати, при экспорте из Макса физической модели не обязательно хранить инфу о текстурах, нормалях и т.п. - так экономится размер файла :cool:

Код:

PotMeshPhys = LoadMesh("PotPhys.b3d")
Global PotHull = BodyCreateMesh(PotMeshPhys, 0)
pxBodySetFlagCollision(PotHull, 0)
FreeEntity PotMeshPhys

Тут всё аналогично хуллу.

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

Полный код примера вы найдёте в аттаче "PhysXExample7.zip"


Следующий пост будет посвящён компаундам.
З.Ы. К сожалению, теперь буду писать с частотой максимум раз в неделю т.к. с 11-го уже опять школа :(

З.З.Ы. !! Не забывайте про ResetXForm! !!

Надеюсь, этот пост оказался вам полезен.

ABTOMAT 17.01.2009 19:35

Ответ: Учебник по PhysX Wrapper для Blitz3D
 
Вложений: 1
Компаунды, или дешёвые невыпуклые тела с коллизией.

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

Итак, напомню что такое компаунды: это группа выпуклых физических тел, крепко соединённых вместе. То есть, например, если нам надо сделать стул, чтобы какой-нибудь шарик мог прокатиться у него между ножек (невыпуклая геометрия) мы можем сделать для него тримеш (тормознуто, отсутствие коллизии с другими тримешами), а можем описать его как группу из нескольких прямоугольных параллелепипедов ("боксов", "кубиков") и это будет обрабатываться значительно быстрее тримеша:



Теория закончилась, переходим к практике. Возьмём за основу пример 2. Там можно было по нажатию на пробел создавать кубики. Попробуем соорудить вместо них что-то типа столов. Для начала нам потребуется моделька стола. Её можно было бы создать в максе, но, я думаю, связь между компаундом и моделью будет яснее, если мы соберём эту подельку прямо в блитзе, из кубиков:

Код:

Global TableMesh = CreateCube()
ScaleMesh TableMesh,3,0.3,1.5
PositionMesh TableMesh,0,3.3,0

leg = CreateCube()
ScaleMesh leg,0.3,1.5,0.3
PositionMesh leg,2.7,1.5,-1.2
AddMesh leg, TableMesh
PositionMesh leg,0,0,2.4
AddMesh leg, TableMesh
PositionMesh leg,-5.4,0,0
AddMesh leg, TableMesh
PositionMesh leg,0,0,-2.4
AddMesh leg, TableMesh
FreeEntity leg
HideEntity TableMesh

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

Опять немного теории.
Создать Compound несколько сложнее, чем простое тело. Сначала создаётся КомпаундДескриптор. Это своего рода контейнер информации о геометрии будущего компаунда. В него заносятся выпуклые физические тела, затем поворачиваются и располагаются как надо в соответствии с тем, что мы хотим получить. Затем из Дескриптора создаётся непосредственно Компаунд, к которому можно прикладывать силу, указывать координаты и т.п., т.е. всё, что можно творить с обычным физическим телом.

Итак,

Цитата:

pxCreateCompoundDesc()
Функция создаёт дескриптор и возвращает его хендл. Параметров нет.

Создадим дескрипторв нашей проге:

Код:

Desc = pxCreateCompoundDesc()
Теперь в него нужно добавлять физические тела (кстати, они в данном случае называются Shape'ы), т.к. сам он "пустой".
Чтобы добавить в него кубик, нужна команда:

Цитата:

pxCompoundAddCubeShape(Desc%, dx#, dy#, dz#)
Добавляет кубик к дескриптору. Возвращает хендл созданного Shape'а. Параметры:
Desc - дескриптор, куда добавлять кубик.
dx, dy, dz - масштаб по осям для кубика (аналогично pxBodyCreateCube, смотрите выше подробное описание если что-то упустили)

Обратите внимание: массу указывать не надо. Как бы это не физическое тело, а фигура, не имеющая массы (возможно, потому она и называется Shape а не Body). Она будет общая для всего компаунда и указывается при его (компаунда, не дескриптора) создании. Центр массы будет рассчитан там же.

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

Код:

Global TableMesh = CreateCube()
ScaleMesh TableMesh,3,0.3,1.5
PositionMesh TableMesh,0,3.3,0

Теперь такой же кубик делаем и для дескриптора:

Код:

Sh = pxCompoundAddCubeShape(Desc,3,0.3,1.5)
Это я создал куб в дескрипторе, отмасштабировал его, и хендл созданного шейпа у меня теперь лежит в переменной Sh. А как же его переместить? Для этого пригодится команда:

Цитата:

pxCompoundSetShapePos(shape%, x#, y#, z#)
Устанавливает позицию для шейпа. Аргументы: shape - хендл шейпа, x#, y#, z# - координаты куда надо устанавливать шейп.
Собственно, аналог PositionEntity, только для Shape'ов дескриптора. Ничего сложного тут нет.

Теперь переместим и наш шейп туда, где он должен стоять (а стоять он должен там же, где и столешница-меш, то есть в 0,3.3,0) :

Код:

pxCompoundSetShapePos(Sh,0,3.3,0)
По аналогии создаём и располагаем кубики и для ножек стола:

Код:

Sh = pxCompoundAddCubeShape(Desc,0.3,1.5,0.3)
pxCompoundSetShapePos(Sh,2.7,1.5,-1.2)
Sh = pxCompoundAddCubeShape(Desc,0.3,1.5,0.3)
pxCompoundSetShapePos(Sh,2.7,1.5,1.2)
Sh = pxCompoundAddCubeShape(Desc,0.3,1.5,0.3)
pxCompoundSetShapePos(Sh,-2.7,1.5,-1.2)
Sh = pxCompoundAddCubeShape(Desc,0.3,1.5,0.3)
pxCompoundSetShapePos(Sh,-2.7,1.5,1.2)

Так, дескриптор создан и все шейпы в ём - тоже. Теперь настало время создавать непосредственно физическое тело. Это делается командой:

Цитата:

pxCreateCompound (Desc, mass#)
Создаёт физическое тело из дескриптора. Возвращает хендл этого самого тела. Аргументы: Desc - дескриптор (естественно со всеми уже заранее созданными в нём шейпами) , mass - масса создаваемого тела.

Вот теперь давайте создадим физическое тело нашего стола:

Код:

Global TableBody = pxCreateCompound(Desc,1)
Из ранее созданного дескриптора создалось тело с массой 1.
Так как это обыкновенное тело, с ним можно вытворять всё то же что и с любым другим телом. Давайте отключим ему коллизию (как в более ранних примерах мы это делали чтобы тело-эталон не мешалось):

Код:

pxBodySetFlagCollision TableBody,0
Готово. Теперь изменим пару строчек в функции CreatepxCube чтобы вместо кубиков создавались столы:

Код:

pxC\mesh = CreateCube()
pxC\body = pxBodyCreateCube(1,1,1,1)

меняем на:

Код:

pxC\mesh = CopyEntity(TableMesh)
pxC\body = pxCopyBody(TableBody)

В персой строке вместо модели куба копируется меш стола, а во второй - вместо создания физического куба копируется тело - компаунд стола. Всё. Можно запускать и нажимать на пробле. Только вот они создются все в одной точке и потому падают ровной стопкой. Добавим некоторый элемент рандомности при создании:

Код:

pxBodySetPosition pxC\body,x+Rnd(-10,10),y,z+Rnd(-2,2)
Вот, теперь вся невыпуклость компаундов налицо. Ножки столов засовываются между ножек других столов и т.д. (прямо-таки камасутра для мебели :blink: )



Однако обратите внимание на такую вещь (вы, наверное, уже заметили): если два компаунда создать "друг в друге" (если слишком часто нажимать на пробел), то сложные компоунды иногда не смогут "выскочить" друг из друга, как это ранее легко делали хуллы и так и останутся слипшимися. Это не есть гуд, но что поделать. Так что следите за тем, где вы создаёте компаунды.

Полный код примера вы найдёте в аттаче "PhysXExample8.zip"
(там я переименовал типы и функции из Cube в Table чтобы они соответствовали своему названию ;))

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

Следующий пост ждите примерно через неделю ;)

ABTOMAT 24.01.2009 22:35

Ответ: Учебник по PhysX Wrapper для Blitz3D
 
Вложений: 3
Другие примитивы и хуллы в компаунде.

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

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

Код:

Global TableMesh = CreateCube()
ScaleMesh TableMesh,3,0.3,1.5
PositionMesh TableMesh,0,3.3,0
HideEntity TableMesh

И из создания тела:

Код:

Desc = pxCreateCompoundDesc()
Sh = pxCompoundAddCubeShape(Desc,3,0.3,1.5)
pxCompoundSetShapePos(Sh,0,3.3,0)

Global TableBody = pxCreateCompound(Desc,1)
pxBodySetFlagCollision TableBody,0

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

Код:

Function CreateCapsule(height#,radius# )
    cyl = CreateCylinder()
    ScaleMesh cyl, radius, height-radius*0.5, radius
   
    sph = CreateSphere()
    ScaleMesh sph, radius,radius,radius
    PositionMesh sph, 0,height-radius*0.5,0
    AddMesh sph, cyl
   
    PositionMesh sph, 0,-height*2+radius,0
    AddMesh sph, cyl

    FreeEntity sph
    Return cyl
End Function

Итак, к мешу столешницы приделаем ножку-капсулу:

Код:

leg = CreateCapsule(1.5,1.0)
PositionMesh leg,0,1.5,0
AddMesh leg, TableMesh
FreeEntity leg

Чтобы создать капсулу в компаунде, потребуется команда:

Цитата:

pxCompoundAddCapsuleShape(Desc%, radius#, height#)

Создаёт шейп-капсулу в дескрипторе. Аргументы:
Desc - дескриптор, куда добавлять, radius и height - радиус и высота капсулы (см. выше пост про примитивы = полная аналогия). Возвращает хендл созданного шейпа.

Теперь к дескриптору компаунда приделаем шейп-капсулу с теми же параметрами:

Код:

Sh = pxCompoundAddCapsuleShape(Desc,1.0,1.5)
pxCompoundSetShapePos(Sh,0,1.5,0)

Всё, теперь можно запускать и давить на пробел. Как видите, получился столик с ножкой-капсулой. Т.к. нижний её конец круглый, то "столик" должен падать, однако если его просто создать и никак не воздействовать, этого не происходит (наверное, вы раньше замечали это и с капсулами-телами). Это происходит потому что капсула в данном случае идеальна и столешница тоже находится чётко посередине ножки, т.е. чентр тяжести точно над капсулой. Потому-то она и не сваливается на бок. Давайцте при создании немного поворачивать тело на рандомный угол. Делается это командой (как, вы ещё о ней не знаете?):
Цитата:

pxBodySetRotation (body%, pitch#, yaw#, roll#)
Задаёт абсолютный угол поворота. По сути, идентична блицовскому RotateEntity, только для физических тел. Параметры: body - тело, которое собираемя вертеть, pitch, yaw, roll - соответственно углы.
Используем эту функцию чтобы повернуть наш "столик" на рандомный угол при создании:
Код:

pxBodySetRotation pxT\body,Rnd(-10,10),Rnd(-10,10),Rnd(-10,10)
Теперь порядок. Можно сделать угол поворота побольше:
Теперь совсем рандомщина :cool:



Давайте теперь сделаем капсулу не такую толстую:
Код:

leg = CreateCapsule(1.5,0.5)
...
Sh = pxCompoundAddCapsuleShape(Desc,0.5,1.5)

И добавим ей на конец шар. Для этого создадим его сначала в меше:

Код:

sph = CreateSphere()
ScaleMesh sph,2,2,2
PositionMesh sph,0,-0.5,0
AddMesh sph, TableMesh
FreeEntity sph

А теперь надо создать и в компаунде. Тут используем команду:

Цитата:

pxCompoundAddSphereShape(Desc%, radius#)

Создаёт шейп-сферу в дескрипторе. Аргументы: Desc - дескриптор, куда добавлять, radius# - радиус сферы. Возвращает хендл созданного шейпа.

Создадит теперь и сферу-шейп и расположим её так же, как и меш:

Код:

Sh = pxCompoundAddCapsuleShape(Desc,0.5,1.5)
pxCompoundSetShapePos(Sh,0,1.5,0)

Можно запускать. Вот что получилось у меня:


Полный код примера вы найдёте в аттаче "PhysXExample9.zip"

Так, со сферами и с капсулами разобрались. Далее по списку - цилиндер.

Цитата:

int pxCompoundAddCylinderShape( Desc%, radius#, height#, nbEdge%)
Создаёт шейп-цилиндр в дескрипторе. Аргументы:
Desc - дескриптор, куда добавлять, radius и height - радиус и высота цилиндра, nbEdge - количество граней цилиндра. Полная аналогия с pxBodyCreateCylinder (см. выше). Возвращает - кто бы мог подумать? - хендл созданного шейпа.

Заюзаем на практике. За основу я взял Пример 9. Уберите оттуда всё, что связано с капсулой и сферой, они тут не понадобятся. Теперь заюзаем цилиндр вместо ножки стола. Создадим его в меше:
Код:

leg = CreateCylinder(8)
ScaleMesh leg,1.0,1.5,1.0
PositionMesh leg,0,1.5,0
AddMesh leg, TableMesh
FreeEntity leg

Тут всё Блицевское, так что, я думаю, понятно.

Далее создаём цилиндр в дескрипторе (опять же, скайлим его точно так же и позиционируем точно так же, как и меш):

Код:

Sh = pxCompoundAddCylinderShape(Desc,1,1.5,8)
pxCompoundSetShapePos(Sh,0,1.5,0)

Запускаем - ок. Давайте-ка уберём ту рандомщину с поворотами:
Код:

pxBodySetRotation pxT\body,Rnd(-100,100),Rnd(-100,100),Rnd(-100,100)
Заметьте - теперь падая на ронвый пол, даже если тело получает слабые толчки, то всё равно становится ровно, в отличие от капсулы, потому что донышко у него плоское.

Полный код примера вы найдёте в аттаче "PhysXExample10.zip"

Ну что ж, теперь - самое интересное - Хулл!

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

Цитата:

pxCompoundAddHullShape(Desc%,vbank%,nvert%)

Создаёт в дескрипторе шейп-хулл, возвращает его хендл. Аргументы: Desc - дескриптор, куда создавать, vbank - банк вершин, nvert - количество вершин в том банке.

Структура vbank'а - такая же как и при создании хулла-тела, то есть такая:

Цитата:

Размер банка - количество вершин * 12

n*12+0 - координата X n-ной вершины (вещественное число - float)
n*12+4 - координата Y n-ной вершины (вещественное число - float)
n*12+8 - координата Z n-ной вершины (вещественное число - float)
Как и в случае с pxBodyCreateHull, этот банк можно загружать из файла, можно брать прямо из меша и т.п., воспользуемся вторым случаем. Вот функция, которая создаёт хулл в дескрипторе на основе меша и возвращает его (шейпа) хендл:
Код:

Function CompoundCreateAddHullShape%(Desc, mesh%)
    Local nsurf = CountSurfaces(mesh)
    Local nvert = 0
    For ns = 1 To nsurf
        Local surf = GetSurface(mesh,ns)
        nvert = nvert + CountVertices(surf)
    Next
        vbank = CreateBank(nvert*4*3)
    nv = 0
    For ns = 1 To nsurf
        surf = GetSurface(mesh,ns)
        nvv = CountVertices(surf)
        For nvc = 0 To nvv - 1
            PokeFloat vbank,nv*12+0,VertexX(surf,nvc)
            PokeFloat vbank,nv*12+4,VertexY(surf,nvc)
            PokeFloat vbank,nv*12+8,VertexZ(surf,nvc)
            nv = nv+1
        Next
    Next
    Local bbb%= pxCompoundAddHullShape(Desc,vbank, nvert)
    FreeBank vbank
    Return bbb
End Function

Как видите, она создаёт банк координат вершин из меша, а потом из него создаёт в указанном дескрипторе шейп.

Внимание! Перед импортом меша из Макса, проделайте действия, описанные в посте №4 про хуллы, чтобы избежать глюков (ResetXForm и т.д.)!

Теперь опробуем на деле. Возьмём Пример 10. Уберём из него создание меша и компаунда стола и перепишем его опять.

Но для начала нам понадобится моделька чтоыб из неё делать хулл. Далеко ходить не надо, используем модельку из поста №4.

Загрузим её(предварительно убрав все "упоминания" о старом объекте):
Код:

Global TableMesh = LoadMesh("PressPapier.b3d")
HideEntity TableMesh

Это, значит, визуальный меш. Так пока и оставим.
Тперь загрузим-ка упрощённый меш для создания из него хула (надеюсь, понятно почему?):
Код:

TableMeshPhys = LoadMesh("PressPapierPhys.b3d")
Далее - создадим дескриптор как обычно
Код:

Desc = pxCreateCompoundDesc()
И чуть пониже, чтоб не откладывать в долгий ящик, сразу создание компаунда и удаление меша:

Код:

FreeEntity TableMeshPhys
Global TableBody = pxCreateCompound(Desc,1)
pxBodySetFlagCollision TableBody,0

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

Код:

Sh = CompoundCreateAddHullShape(Desc,TableMeshPhys)
Запускаем, тестим. Ну, это у нас получилось как обычные хуллы (т.к. хулл-то в дескрипторе всего один :-) ) Теперь попробуем создать ещё один такой же хулл и повернуть относительно первого на 90°...
Код:

Sh = CompoundCreateAddHullShape(Desc,TableMeshPhys)
Это я создал второй такой же хулл.
Да, чуть не забыл, чтобы повернуть шейп в дескрипторе, нужно использовать эту команду:
Цитата:

pxCompoundSetShapeRot(shape%,pitch#,yaw#,roll#)
Поворачивает шейп. Аргументы: shape - хендл шейпа, pitch#,yaw#,roll# - соответственно углы в радианах.
Итак, поднимем новый шейп относительно первого, перевернём его чтобы креглешом смотрел вверх и ещё на 90°:
Код:

pxCompoundSetShapeRot(sh,0,90,180)
pxCompoundSetShapePos(sh, 0, 6, 0)

Вот я его перевернул и переместил. Теперь то же, но для меша:
Код:

TableMesh2 = CopyMesh(TableMesh)

RotateMesh TableMesh2,0,90,180
PositionMesh TableMesh2,0,6,0

AddMesh TableMesh2,TableMesh
FreeEntity TableMesh2

Скопировал, перемернул, переместил, аттачнул, удалил лишний.
В-общем-то, должно быть понятно. Запускаем, тестим, теперь у нас компоунд из нескольких хуллов:

Полный код примера вы найдёте в аттаче "PhysXExample11.zip"

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

ABTOMAT 01.02.2009 03:11

Ответ: Учебник по PhysX Wrapper для Blitz3D
 
Луч, силы, импульсы.

Теперь дела пойдут повеселее! Будем толкать наши физические тела!
Итак, для начала немного подготовимся.
Возьмём за основу пример №2, тот, где можно было создавать кубики.
Немного переделаем функцию создания куба, чтобы она ещё и возвращала ссылку на его тип:
Код:

Function CreatepxCube.pxCube(x#,y#,z#)
...
Return pxC

И создадим для опытов один кубик, в самом начале:
Код:

Cube.pxCube = CreatepxCube(0,10,0)
Пробел пока не долбим. В центре внимания - этот куб. Можно даже его раскрасить, чтобы потом мы могли в дальнейшем его отличать от остальных:
Код:

EntityColor Cube\mesh,200,0,0
Ну-с, приступим. Научим наш куб прыгать. Пускай он прыгает по пробелу, а онг у нас занят. Невелика проблема, я перенёс создание куба на клавишу Tab:
Код:

If KeyHit(15) Then CreatepxCube(0,10,0)
Переходим непосредственно к физиксу. Чтобы кубик подпрыгнул, к нему надо применить силу, а точнее, импульс. Вообще, в курсе физики это считается разными вещами, но из-за схожести эффекта и (видимо) расчётов, сила, импульс и некоторые другие явления объединены в одну функцию. Вот она:
Цитата:

pxBodyAddForce(body%, vx#, vy#, vz#, mode%)
Применяет силу к центру массы тела. Внимание! Нельзя применять силу к статическим телам (масса = 0)

body - тело, на которое применяется сила (импульс, ...), vx, vy, vz - вектор силы (чем он "длиннее" - тем большая сила приложится), mode - режим.

Если с первыми четырьмя аргументами всё более-менее ясно, то на последнем остановимся поподробней. Итак, этот параметр указывает, что именно делать с телом:
0 - приложить силу. По заветам товарища Ньютона, a=F/m, ускорение, которое получает материальная точка, прямо пропорционально приложенной силе и обратно пропорционально массе, т.е. чем большую силу приложат к телу, тем большее оно приобретёт ускорение, но чем больше масса тела, тем это самое ускорение будет меньше. Собсно, этой командой мы прилагаем силу, а тело, в зависимости, от своей массы, приобретает то или иное ускорение. При одной и той же силе тело массой 1 улетит дальше чем тело с массой 10. На деле будет выглядеть, как будто тело куда-то тянут или толкают. Используется для постоянного толкания тела. Например, для перемещения персонажа.
1 - приложить импульс. Здесь происходит резкий толчок. Ускорение так же зависит от массы. По определению импульса, P=m*V, то есть импульс, применённый на тело с аргументом-вектором P, будет выглядеть так, как будто в тело врезалось тело с массой m с мгновенной скоростью V, но так, что вектор mV = вектору P. Очень удобно, если по каким-то причинам нужно смоделировать столкновение тел, а моделировать одно их них нельзя или не желательно. Например, выстрел пулей в ящик можно сымитировать, описав пулю шариком и пульнуть им в ящик, чтобы они столкнулись. Но зачем нам моделировать ещё одно лишнее физическое тело, когда достаточно применить на ящик импульс, чтобы он отскочил?
2 - действие на скорость (или change of speed, как гласит документация). Это импульс, игнорирующий массу объекта. Эффект будет тот же, как если бы у тела, на которое применяют сиё, была бы масса 1. То есть если к двум телам (массы 1 и 100) сответственно применить импульс, скажем, 10, то первое подскочит, а второе еле дрогнет. А если к тем же самым телам применить точно такой же change of speed, то оба подпрыгнут совершенно одинаково.
3 - smooth impulse, сглаженный импульс. Тот же импульс что и с параметром 1, но применяется более плавно. Если честно, от обычного отличается не слишком сильно, хотя выглядит и правда мягче. Думаю, для имитации попадания пули подойдёт простой импульс, а для толчка ногой - сглаженный.
4 - smooth change of speed - собственно, тот же change of speed, но более мягкий. Аналогично паре 1 и 3.
5 - ускорение. Та же сила (параметр 0), но игнорирующая массу, аналогично паре 1 и 2.

Кстати в дальнейшем слова "сила" и "импульс" часто будут синонимами (если не указано обратное) т.к. с нашей, юзерской точки зрения, это почти одно и то же.

В теоретической физике вышеописанное - разные вещи, но на практике мы просто получим сообщение разного ускорения телу для разных случаев. Используйте то, что будет более удобно в конкретной ситуации. Естественно никто не станет кратковременно применять огромную силу на тело, чтобы заставить его подпрыгнуть, и не станет пытаться толкать тело вперёд маленькими импульсами, когда можно долговременно применять на него силу. И незачем использовать change of speed, когда придётся вычислять значение исходя из массы вручную, когда impulse учтёт эту массу автоматом.

Фактически, чаще всего используются только режимы 0 (сила) и 1 (импульс), остальные - крайне редко.

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

If KeyHit(57) pxBodyAddForce Cube\body,0,10,0,1
Запускаем, смотрим, тело и правда прыгает. Даже может взлететь, т.к. когда оно в воздухе, то прыгать дальше ему всё равно не запрещают. Конечно, тут надо определять, находится ли тело на земле, если да - то разрешить прыгать, если нет - то нет... Но это отдельная больная тема, о которой я расскажу в дальнейшем. А пока что я рассказываю не об этом и такого "прыжка" вполне хватит для опытов.

Теперь попробуем применять силу. Заставим тело толкаться в разные стороны по нажатию клавиш WASD.
Код:

If KeyDown(17) pxBodyAddForce Cube\body,0,0,10,0
По нажатию W я применил силу с вектором вперёд. Тестируем. Работает. Кстати, попробуйте понажимать пробел и W - прямо запуск космического корабля. Хотя если задуматься - там двигатели могут давать постоянную тягу вперёд и толчки вверх, так что в таком случае наблюдаемая сцена вполне реальна.
Добавим другие клавиши:
Код:

If KeyDown(17) pxBodyAddForce Cube\body,0,0,10,0
If KeyDown(31) pxBodyAddForce Cube\body,0,0,-10,0
If KeyDown(30) pxBodyAddForce Cube\body,-10,0,0,0
If KeyDown(32) pxBodyAddForce Cube\body,10,0,0,0

Теперь кубик можно толкать в разных направлениях.

Помните игру Ballance? Собственно, с PhysX'ом вот так всё просто. Вставить сюда вместо кубика шар, убрать прыжки, загрузить уровень в trimesh - и будет вам Ballance :cool:

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

Вот, собственно, и всё о pxBodyAddForce. Теперь настала пора познакомиться с её сестрой-близнецом:

Цитата:

pxBodyAddForceAtLocalPos(body%,vx#,vy#,vz#,px#,py# ,pz#,mode%)
Применяет силу/импульс к телу в позиции его локальных координатах.

body - тело, на которое применяется сила (импульс, ...), vx, vy, vz - вектор силы,px,py,pz - локальные координаты точки куда прилагать силу, mode - режим.

Как вы помните, в pxBodyAddForce сила применялась к центру массы тела. Тут же можно применить силу в какую угодно точку тела, указав её локальные координаты. Это очень удобно, т.к. если тело перевернётся, то не придётся заново вычислять координаты точки куда прилагать силу в соответствии с поворотом тела, т.к. она локальна.
Все остальные параметры 100% совпадают с pxBodyAddForce.

Попробуем заюзать на практике. Давайте сначала уберём всё, что было ранее связано с WASD:
Код:

If KeyDown(17) pxBodyAddForce Cube\body,0,0,10,0
If KeyDown(31) pxBodyAddForce Cube\body,0,0,-10,0
If KeyDown(30) pxBodyAddForce Cube\body,-10,0,0,0
If KeyDown(32) pxBodyAddForce Cube\body,10,0,0,0

И давайте теперь попробуем "подвешивать" куб за один из его углов. Для этого изменим код "прыжка". Теперь будем использовать pxBodyAddForceAtLocalPos, во-вторых, режим пускай будет 0 (простая сила), а в-третьих, передадим туда локальные координаты правого верхнего угла:
Код:

If KeyDown(57) pxBodyAddForceAtLocalPos Cube\body,0,10,0,1,1,1,0
Прямо как на крючке))
Кстати, можно указывать позицию не только на поверхности тела, но и за его пределами.

Теперь рассмотрим другую команду:

Цитата:

pxBodyAddLocalForceAtLocalPos(body%,vx#,vy#,vz#,px #,py#,pz#,mode%)
Применяет локальную силу/импульс к телу в позиции его локальных координатах.

body - тело, на которое применяется сила (импульс, ...), vx, vy, vz - локальный вектор силы, px,py,pz - локальные координаты точки куда прилагать силу, mode - режим.

Собственно, это аналог предыдущей функции, но теперь и сила применяется локально. То есть если мы укажем вектор 0,0,1, то тело будет двигаться вперёд. Есви оно перевернётся, то оно будет двигаться туда, где перед у тела. Если pxBodyAddForce - это в некоторой степени аналог TranslateEntity, то pxBodyAddLocalForce - аналог MoveEntity.

Так, как же нам может оно здесь пригодиться? Давате включим фантазию. Представим, что наш кубик - это катер на воздушной подушке над ледяной пустыней. У такого катера сзади 2 огромных вентилятор - один справа, другой слева. Когда работают оба, катер движетя вперёд. Когда один из них (например, правый) снижает обороты или вовсе замолкает, то левый поворачивает весь катер вправо. Или как, например, если обе гусеницы танка крутятся одинаково, то танк едет прямо, а если одна едет быстрее другой, то танк поворачивается в ту сторону, которая едет медленней.

Дык вот, сделаем нашему кубику такие "двигатели". Пускай левый работает по клавише A, а правый - по клавише D. Для этого будем применять силы в тех точках, где бы располагались "турбины". Пускай они располагаются на 10 влево и вправо соответствующий. Так далеко - чтобы результат был более явным. Сила каждого - 10 по оси Z. Вот что у меня вышло:
Код:

If KeyDown(30) pxBodyAddLocalForceAtLocalPos Cube\body,0,0,10,-10,0,0,0
If KeyDown(32) pxBodyAddLocalForceAtLocalPos Cube\body,0,0,10,10,0,0,0

Также я думаю, надо бы удлиннить кубик при создании, чтобы видеть эффект:
Код:

ScaleEntity pxC\Mesh, 1,1,5
pxC\body = pxBodyCreateCube(1,1,5,1)
pxBodySetPosition pxC\body,x,y,z

Вот, теперь запускаем и давим сначала обе клавиши - едет прямо. Отпускаем одну - поворачивается. Типа, гоночки :cool: Конечно, до гонок далеко: не хватает, в первую очередь, колёс, да и ускорения просто дикие. Но главное - теперь вы знаете как использовать локальную силу в локальной позиции.

ABTOMAT 01.02.2009 03:59

Ответ: Учебник по PhysX Wrapper для Blitz3D
 
Вложений: 1
Теперь последнее и самое интересное
Цитата:

pxBodyAddForceAtPos(body%,vx#,vy#,vz#,px#,py#,pz#, mode%)
Прилагает к телу глобальную силу в глобальных координатах в точку в глобальных координатах. Все параметры аналогичны параметрам вышерассмотренных функций:

body - тело, на которое применяется сила, vx, vy, vz - глобальный вектор силы, px,py,pz - глобальные координаты точки куда прилагать силу, mode - режим.

А чем же эта функция так интересна? А тем, что позволяет вытворять очень интересные вещи.

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

Но всё по порядку. Для начала опять уберём всё что связано с созданием кубика, движением кубика силами, его подпрыгиванием и масштабированием, то есть вернём его в исходное состояние. Проще взять обратно пример №2.

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

Код:

Function control(ent) ; Функция управления WASD
        If KeyDown(17) MoveEntity ent,0,0,1
        If KeyDown(31) MoveEntity ent,0,0,-1
        If KeyDown(30) MoveEntity ent,-1,0,0
        If KeyDown(32) MoveEntity ent,1,0,0
End Function

Function mouselook(ent) ; Функция обзора мышью

        mxspd#=MouseXSpeed()*0.25
        myspd#=MouseYSpeed()*0.25

        MoveMouse GraphicsWidth()/2,GraphicsHeight()/2       
       
        campitch#=EntityPitch(ent)+myspd#
       
        If campitch#<-85 Then campitch#=-85 ; ограничения поворота, чтобы камера не крутилась до бесконечности вверх и вниз, а останавливалася глядя вниз
        If campitch#>85 Then campitch#=85

        RotateEntity ent,campitch#,EntityYaw(ent)-mxspd#,EntityRoll(ent)
End Function

Пусть игрок свободно перемещается по сцене:
Код:

control cam : mouselook cam
Ну, и стрелочку мыши уберём заодно
Код:

HidePointer()
Так, окей. Теперь подумаем, что мы ходим сделать. Выстрелы, как, например, в HL2 (с физической точки зрения). Значит пуля будет лететь в центр экрана (или близко к тому). Как же применять филы? План таков:
  • Пикаем из камеры то, что находится перед ней
  • На пикнутый объект применяем силу в координатах, где пикнуло и по направлению куда смотрит камера.

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

Но ведь в физиксе уже есть встроенный пик по лучу! Давайте ознакомимся с ним поближе.

Для начала надо создать луч. Это делает команда:
Цитата:

pxCreateRay()
Она создаёт луч. Возвращает его хендл.
Создадим тоже луч, пусть он будет глобальный:
Код:

Global ray = pxCreateRay()
Теперь надо сделать функцию выстрела, где как раз и будет пикаться всё что нам надо и применяться сила. Пусть эта функция будет называться Shoot.
Итак, сначала пикаем. Для этого нужно переместить луч в позицию камеры. Это просто:
Цитата:

pxRaySetPosition(ray%, x#, y#, z#)

Команда устанавливает луч в указанные координаты.
Аргументы:
ray - хендл луча
x,y,z - координаты для него.

Итак, я устанавливаю луч в позицию камеры:
Код:

        pxRaySetPosition(ray, EntityX(cam,1), EntityY(cam,1),EntityZ(cam,1))
Теперь надо повернуть луч так же как камера. А вот с этим сложнее. Ведь мы располагаем только командой:
Цитата:

pxRaySetDir(ray%, nx#, ny#, nz#)

Устанавливает направлекние лучу.
Аргументы:
ray - луч
nx,ny,nz - нормализованный вектор направления (т.е. длина вектора = 1)

Итак, как же получить нужный нам вектор, имея только поворот камеры?
Нам поможет блитзовский TFormVector:

Код:

TFormVector 0,0,1,cam, 0
Многие про эту функцию не знают вовсе, поэтому постараюсь объяснить тоже чтоб было понятно.
Она трансформирует вектор из одной матрицы в другую, т.е. например как в данном случае у камеры был локальный вектор 0,0,1, но камера повёрнута, и нам нужно узнать глобальное направление того вектора. Тогда помогает эта функция. четвёртый и пятый её аргументы - это исходный ентити и конечный ентити, чтобы указывать, что относительно чего трансформировать. Если выставить 0 - то будет считаться за мировую систему координат (т.е. глобальные значения).
Короче благодаря ей мы нашли направление куда смотрит камера. Значения снимаются при помощи TFormedX()#,TFormedY()#,TFormedZ()#. Для порядку а также чтоб легче было дебажить запишем их в паременные:

Код:

DirX# = TFormedX()
DirY# = TFormedY()
DirZ# = TFormedZ()

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

Код:

pxRaySetDir(ray,DirX,DirY,DirZ)
Вот, луч повёрнут куда надо, но прежде чем выполнять дальнейшие действия, проверим, тыкает ли луч вообще во что-то. Для этого посмотрим, что из себя представляет пикнутое тело:

Цитата:

pxRayGetBody(ray%, mode%)
Возвращает тыкнутый объект. Если ничего не тыкнулось, возвращает 0.
Аргументы:
ray - луч
mode - режим:
0 - тыкать все объекты
1 - тыкать только динамические объекты
2 - тыкать только статику

Получаем хендл тела в переменную (чтобы потом в случае чего опять его не пикать) и проверяем на нуль:

Код:

Body = pxRayGetBody(ray,1)
if Body Then

Нам нужно получить координаты, куда луч ткнул. Вот это нам пригодится:

Цитата:

pxRayGetPickX [Y,Z] (ray%, mode%)
Возвращает координату X [Y,Z] того места, куда ткнул ray.
Аргументы:
ray - луч
mode - режим:
0 - тыкать все объекты
1 - тыкать только динамические объекты
2 - тыкать только статику

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

Получим значения в переменные:
Код:

PosX# = pxRayGetPickX(ray, 1)
PosY# = pxRayGetPickY(ray, 1)
PosZ# = pxRayGetPickZ(ray, 1)

Итак, теперь у нас теперь есть все необходимые параметры, чтобы применить импульс! Делаем это:

Код:

pxBodyAddForceAtPos body, DirX*10,DirY*10,DirZ*10, PosX, PosY,PosZ,2
Обратите внимание: я умножил передаваемый вектор на 10 - т.к. он выходит нормализованным и очень маленьким, так что его надо непременно усилять.

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

Код:

If MouseHit(1) Then Shoot()
Готово! Запускаем, кидаем кубики и расстреливаем их 8)

Чего-то не хватает. Ну что ж, последний штрих: прицел.

Код:

Color 200,0,0
Line (GraphicsWidth() Shr 1) - 15, (GraphicsHeight() Shr 1), (GraphicsWidth() Shr 1) + 15, (GraphicsHeight() Shr 1)
Line (GraphicsWidth() Shr 1), (GraphicsHeight() Shr 1) - 15, (GraphicsWidth() Shr 1), (GraphicsHeight() Shr 1) +15
Oval (GraphicsWidth() Shr 1) - 10, (GraphicsHeight() Shr 1)-10, 20,20,0



Извините что картинок маловато, но тут уже скринами ничего не покажешь: нужно видеть в динамике.

Ну что ж, рассказ о силах и импульсах закончен! У лучей есть ещё некоторые фичи, о них я буду рассказывать далее по ходу.
Полный код примера вы найдёте в аттаче "PhysXExample12.zip"
У меня всё, надеюсь, материал оказался вам полезен. У меня на сегодня всё ;)

З.Ы. Полез опечатки исправлять - при сохранении пишет что пост слишком короткий, увеличьте до 4 символов. Пришлось разрезать на 2 поста, тогда глюк прошёл. СабЗиро, где ты?.. :(

ABTOMAT 09.02.2009 00:09

Ответ: Учебник по PhysX Wrapper для Blitz3D
 
Вложений: 1
Сочленения

Итак, сочленения. По-буржуйски - джойнты (joints)
Сочленения - это объекты, которые могут определённым образом "сцеплять" тела. То есть например, в том же пресловутом Ragdoll'е все физ. тела для костей соединены между собой сочленениями. Если бы этого не было, что все части тела разлетелись бы кто куда и никаког орегдолла не получилось бы.
Джойнтов (во враппере, по крайней мере) существует достаточно много, рассмотреть их в одной статье не удастся, поэтому начну с самых основных.

Сферический джойнт.

Это самый распространённый вид сочленения. По сути, это шарнир, то есть имеет лимит в виде конуса. Чтобы лучше понять, как он будет работать, посмотрите на эту картинку:

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

Переходим к практике. Возьмём за основу Пример 2.
Будем создавать теперь уже не один кубик, а два вытянутых "кирпича", да ещё исоединённые между собой джойнтами.

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

Но лучше поступим по-иному. Будем создавать сначала два pxCube'а, а потом между ними - джойнт.

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

Код:

Function CreateBunch(x#,y#,z#)
        Cube1.pxCube = CreatepxCube(x#,y#+5,z#)
        Cube2.pxCube = CreatepxCube(x#,y#-5,z#)       
End Function

Function CreatepxCube.pxCube(x#,y#,z#)
        pxC.pxCube = New pxCube
        pxC\mesh = CreateCube()
        ScaleEntity pxC\mesh,1,5,1
        pxC\body = pxBodyCreateCube(1,5,1,1)
        pxBodySetPosition pxC\body,x,y,z
        Return pxC
End Function

CreateBunch создаёт 2 куба с разницей по высоте в 5. Теперь надо сцеплять их сферическим джойнтом. Делать это будем при помощи команды
Цитата:

pxJointCreateSpherical( body1%, body2%, x#, y#, z#, nx#, ny#, nz#)

Создаёт сферический джойнт (он же шарнир), возвращает его хендл.
Аргументы:
body1, body2 - тела, которые будем сцеплять.
x, y, z - координаты джойнта при его создании.
nx, ny, nz - нормализованный вектор направления оси джойнта. Попробую объяснить проще: помните серый конус с картинки выше? Эти параметры как раз указывают, куда будет направлен этот конус. Например, при 0,1,0 цилиндр будет направлен вверх.

Внимание! Тела body1, body2 должны быть динамическими (т.е. иметь массу <> 0). Если вы хотите привязать тело к статике, то подставьте 0 вместо статического тела.

Привяжем 2 наших кубика один к другому:

Код:

pxJointCreateSpherical Cube1\Body,Cube2\Body,x,y,z,0,1,0
Джойнт в данном случае нужно располагать посередине между двумя телами. Т.к. оба куба смещены по вертикали то соответственно и "конус" будет направлен вверх.

Теперь по пробелу уже создаём связку:

Цитата:

If KeyHit(57) Then CreateBunch(0,20,0)
Тыкаем на пробел, смотрим на результат.
Обратите внимание: два сцеплённых друг с другом тела не коллизятся друг с другом!
Можно включить коллизию.

Цитата:

pxJointSphericalSetCollision(joint%)

Команда заставляет рассчитывать коллизию между двумя телами, сцеплёнными сферическим джойнтом. (для других типов джойнтов - свои версии этой команды!) Аргументы: joint - хенджл джойнта.

Попробуем и в нашей проге это сделать. Так как нам теперь нужен хендл джойнта, то получим его в переменную и на него применим вышеописанную команду:

Код:

Joint = pxJointCreateSpherical (Cube1\Body,Cube2\Body,x,y,z,0,1,0)
pxJointSphericalSetCollision(Joint)

Ага, теперь они коллизятся. Только почти не поворачиваются и это понятно: т.к. куба сцеплены вплотную друг к другу, то они мешают друг другу вертеться. В функции создания поменяем расстояние между создаваемыми кубами. Вместо 5 возьмём 6.

Код:

Cube1.pxCube = CreatepxCube(x#,y#+6,z#)
Cube2.pxCube = CreatepxCube(x#,y#-6,z#)

Теперь всё хорошо видно. И вертятся и коллизятся. Но всё-таки ограничение по коллизии - это не вариант. Зачем точно рассчитывать коллизию хуллов, когда можно поступить гораздо проще - ограничить конус вращения?

Итак, уберём включение коллизии, вернём расстояние между кубами на 5 и рассмотрим, как можно по-иному ограничить вращение.

Цитата:

pxJointSphericalSetLimitAngle(joint%, angle#, hardn#, restit#)

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

Аргументы:

joint - хендл джойнта
angle - угол лимита в градусах
hardn - жёсткость лимита. Диапазон: [0;1] По умолчанию: 1.
restit - упругость лимита. Диапазон: [0;1] По умочанию: 0.

*Очень странно, но при любых значениях hardn и restit получается один и тот же эффект. :4to: При значениях за пределами указанного диапазона лимит отключается вовсе. Use it wisely.

Укажем лимит в 90°, остальные параметры оставим как по дефолту и посмотрим, что выйдет:

Код:

pxJointSphericalSetLimitAngle(Joint, 90,1, 0)
Вот, теперь тела не могут вращаться более чем на 90° от изначального положения.



*Это важно: в школьной геометрии (11-й класс) углом конуса считается угол при вершине его осевого сечения, что логически верно. Но в PhysX'е указывается только половина этого угла. Взгляните на картинку: угол, задавемый параметром angle вышеописанной функции обозначен синим:

Будьте бдительны и не ведитесь на дезинформацию минобразования РФ =-)))

Тела крутятся вокруг своей оси. А что, если это например нога человека? Ведь она же не может крутиться на 360° ? Это дело тоже можно ограничить.

Цитата:

pxJointSphericalSetLimitTwist(joint%,mintwist#,max twist#,spr#,damp#,targetVal#)

Ограничивает кручение вокруг оси джойнта. Аргументы:
joint - джойнт
mintwist,maxtwist - минимально и максимально возможное кручение
spr - упругость. Диапазон: [0;бесконечность), по умолчанию: 0
damp - "вязкость"
targetVal - значение, к которому будет стремиться поворот.

Создадим ограничение +- 10° с некоторой упругостью и вязкость. Целевое значение пусть будет 0
Код:

pxJointSphericalSetLimitTwist(Joint,-10,10,10,1,0)
Вот, мы ограничили вращение в разумных пределах.

Рассмотрим ещё одну команду для сферического джойнта:

Цитата:

pxJointSphericalSetLimitSpring(joint%, spr#, damp#, targetVal#)

Указывает растяжимость соединения. Аргументы:
joint - джойнт
spr - эластичность. По дефолту 0, может принимать значения от 0 до бесконечности.
damp - вязкость
targetVal - значение к которому стремиться. По дефолту 0.

Установим эту самую растяжимость:

Код:

pxJointSphericalSetLimitSpring(Joint, 10, 1, 0)
И последнее. Чтобы удалить джойнт используйте

Цитата:

pxDeleteJoint (joint)

Тупо удаляет джойнт.
Аргументы: joint - джойнт

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

Код:

Type Joint
        Field Joint
End Type

Function FreeAllJoints()
        For j.Joint = Each Joint
                pxDeleteJoint(J\Joint)
                Delete J
        Next
End Function

.......
Function CreateBunch(x#,y#,z#)
        Cube1.pxCube = CreatepxCube(x#,y#+5,z#)
        Cube2.pxCube = CreatepxCube(x#,y#-5,z#)               
        Joint = pxJointCreateSpherical (Cube1\Body,Cube2\Body,x,y,z,0,1,0)
        pxJointSphericalSetLimitAngle(Joint, 30,1, 0)
        pxJointSphericalSetLimitTwist(Joint,-10,10,10,1,0)
        pxJointSphericalSetLimitSpring(Joint, 10, 1, 0)
        J.Joint = New Joint
        J\Joint = Joint
End Function

Вызов по левому шифту:

Код:

If KeyHit(42) Then FreeAllJoints()
Теперь если нажать шифт, то все кубики разваливаются)
Кстати, обратите внимание на то, что при уничтожении джойнта коллизия между двумя телами снова включается и они "выскакивают" друг из друга. Поэтому если вы собрались в процессе игры разрушать некоторые джойнты, то коллизию всё-таки лучше включить.

Полный код примера вы найдёте в аттаче "PhysXExample13.zip"

В этот раз хотел написать ещё и про хиндж, да не успеваю :( в СЗИП СПБГУТД на этой неделе будет тестирование, по итогам которого соответственно повышаются шансы на поступление, так что надо готовиться. В-общем, кто учился в 11-м классе - тот поймёт. Не могу сказать точно, будет ли следующий пост через неделю. Если нет - напишу позже. На сегодня у меня всё ;)

viper86 10.02.2009 17:43

Материалы
 
Вложений: 1
Материалы в PhysX

Так как АВТОМАТ по учебным обстоятельствам не может часто повествовать, то я решил помочь ему в этом нелёгком деле :) . Я попробую рассказать вам о материалах, которые можно назначать на физические тела.

Собственно материал - это коэффициенты трения и упругости тела. Что означают эти термины я думаю все помнят из школьного курса физики :) . Для создания материала служит след функция:

Код:

pxCreateMaterial()
Она создаёт обычный материал, в котором можно назначить трение(для статических и динамических тел отдельно) и упругость:

Код:

pxMaterialSetDyFriction(mat%, fric#)       

fric=[0,inf]

для динамических тел

Код:

pxMaterialSetStFriction(mat%, fric#)       

fric=[0,inf]

для статических тел

Код:

pxMaterialSetRestitution(mat%,rest#)       

rest=[0,1]

для любых тел

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

Давайте попробуем создать свой материал. Создание окна, камеры, света, физического мира (и т.п.) подробно описывать, я думаю, нет смысла, тут и так всё понятно:

Код:

Graphics3D 800,600,32,2
SetBuffer BackBuffer()

pxCreateWorld(1,"")
pxSetGravity(0,-10,0)

cam=CreateCamera()
PositionEntity cam,15,7,-15
RotateEntity cam,20,35,0

light=CreateLight()
PositionEntity light,10,10,-10

While Not KeyDown(1)

pxRenderPhysic(60,0)
RenderWorld
Flip
Wend
End

Создадим статический куб (с массой = 0), растянем его и повернём, чтобы получился склон (образно назовём её горка):

Код:

gorka_body=pxBodyCreateCube(10,0.2,10,0)
pxBodySetRotation gorka_body,0,0,-25
pxBodySetPosition gorka_body,0,5,0
gorka=CreateCube()
ScaleEntity gorka,10,0.2,10
pxBodySetEntity gorka,gorka_body

Так как тело статическое, то в цикле не нужно обновлять позицию меша gorka (pxBodySetEntity gorka,gorka_body), достаточно раз написать при создании. Конечно, можно вместо этого использовать PositionEntity и RotateEntity такие же, как и gorka_body, но это дольше и больше кода.

Должно получится приблизительно так:



Для большей наглядности создадим зеркальный пол:

Код:

plane=CreatePlane()
EntityAlpha plane,0.5
CreateMirror()

Теперь приступим непосредственно к созданию материала. Напишем следующее (сразу после создания физического мира и гравитации):

Код:

mat_stat1=pxCreateMaterial()
pxMaterialSetStFriction(mat_stat1,0.1)
pxMaterialSetRestitution(mat_stat1,0.5)

Применим наш материал к горке:

Код:

pxMaterialSetToBody(gorka_body,mat_stat1)
Вуаля! ))) Теперь наша горка имеет заданный нами материал. Но этого не заметно без взаимодействия с другими обьектами. Сделаем так: напишем функцию для создания кубиков и будем бросать 3 кубика с разными материалами на нашу горку, чтоб нам нагляднее была видна разница. Создаём тип кубиков и пишем функции создания и обновления:

Код:

Type pxCube
        Field mesh
        Field body
        Field material
End Type

Function CreatepxCube(x#,y#,z#,mat%=0)
        pxC.pxCube = New pxCube
        pxC\mesh = CreateCube()
        pxC\body = pxBodyCreateCube(1,1,1,1)
        pxC\material = mat
        If mat<>0 Then pxMaterialSetToBody(pxC\body,pxC\material)       
        pxBodySetPosition pxC\body,x,y,z       
End Function

Function UpdatepxCubes()
        For pxC.pxCube = Each pxCube
                pxBodySetEntity pxC\mesh, pxC\body
        Next
End Function

Здесь всё должно быть понятно, так как функции взяты с прошлых примеров. Только мы добавили поле material и применение материала к телу с помощью pxMaterialSetToBody(). Теперь у нас функция CreatepxCube() передаёт 4 параметра:позицию по x,y,z и необязательный материал (если = 0 то не применять никакой материал). Теперь создаём 3 новых материала в том же месте, где и первый (статический):

Код:

mat_dyn1=pxCreateMaterial()
pxMaterialSetDyFriction(mat_dyn1,0.1)
pxMaterialSetRestitution(mat_dyn1,0.1)

mat_dyn2=pxCreateMaterial()
pxMaterialSetDyFriction(mat_dyn2,50)
pxMaterialSetRestitution(mat_dyn2,0.2)

mat_dyn3=pxCreateMaterial()
pxMaterialSetDyFriction(mat_dyn3,0.1)
pxMaterialSetRestitution(mat_dyn3,0.9)

А в главном цикле пропишем создания 3х кубиков при нажатии на enter:

Код:

While Not KeyDown(1)

if keyhit(28) Then
        CreatepxCube(-3,10,-3,mat_dyn1)
        CreatepxCube(-3,10,0,mat_dyn2)
        CreatepxCube(-3,10,3,mat_dyn3)
End If

pxRenderPhysic(60,0)
UpdatepxCubes()
RenderWorld
Flip
Wend
End

Жмём F5, а затем enter и смотри, как ведут себя кубики. Если вы задавали те же коэффициенты, что я и давал, то у вас должна получиться такая картина:



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

Ещё в PhysX есть такая очень полезная штука, как комбинированный режим коэффициентов. Для этого существуют специальные функции:

Код:

pxMaterialSetFrictionCombineMode(mat%, mode%)

pxMaterialSetRestitutionCombineMode(mat%, mode%)


где mode:

0 - среднее значение: (a+b)/2
1 - минимум: min(a,b)
2 - умножение: a*b
3 - максимум: max(a,b)
4 - какой-то непонятный режим, скорее всего тестовый и нигде не используемый

Попробуем добавить ещё 3 материала, только теперь используя комбинированный режим. Что бы лучше увидеть разницу, коэффициенты оставим те же:

Код:

mat_dyn4=pxCreateMaterial()
pxMaterialSetDyFriction(mat_dyn4,0.1)
pxMaterialSetRestitution(mat_dyn4,0.1)
pxMaterialSetFrictionCombineMode(mat_dyn4,1)
pxMaterialSetRestitutionCombineMode(mat_dyn4,1)

mat_dyn5=pxCreateMaterial()
pxMaterialSetDyFriction(mat_dyn5,50)               
pxMaterialSetRestitution(mat_dyn5,0.2)               
pxMaterialSetFrictionCombineMode(mat_dyn5,2)               
pxMaterialSetRestitutionCombineMode(mat_dyn5,2)               

mat_dyn6=pxCreateMaterial()
pxMaterialSetDyFriction(mat_dyn6,0.1)               
pxMaterialSetRestitution(mat_dyn6,0.9)               
pxMaterialSetFrictionCombineMode(mat_dyn6,3)               
pxMaterialSetRestitutionCombineMode(mat_dyn6,3)

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

Код:

While Not KeyDown(1)
       
        If KeyHit(57) Then state=1-state
               
        If KeyHit(28) Then       
                DeletepxCubes()       
                Select state
                        Case 0
                        CreatepxCube(-3,10,-3,mat_dyn1)
                        CreatepxCube(-3,10,0,mat_dyn2)
                        CreatepxCube(-3,10,3,mat_dyn3)                       
                       
                        Case 1
                        CreatepxCube(-3,10,-3,mat_dyn4)
                        CreatepxCube(-3,10,0,mat_dyn5)
                        CreatepxCube(-3,10,3,mat_dyn6)
                End Select               
        End If
       
        pxRenderPhysic(60,0)
        UpdatepxCubes()
        RenderWorld
       
        If state=0 Then
                Text 10,10,"Normal mode"
                Else
                Text 10,10,"Combine mode"
        EndIf
       
        Flip
Wend

Function DeletepxCubes()
        For pxC.pxCube = Each pxCube
        FreeEntity pxC\mesh
        pxDeleteBody pxC\body
        Delete pxC               
        Next
End Function

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



Полный код приведёт в прикреплённом файле.

Также ещё существуют так называемые анизотропные материалы:

Код:

pxCreateAnisotripicMaterial(body%, nx#, ny#, nz#)

где nx#, ny#, nz# - направление вектора анизотропии

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

Для анизотропного материала можно применять особый тип трения:

Код:

pxMaterialSetFrictionV(mat%, sfric#, dfric#)

где:
sfric - статический коэффициент трения [0,inf]
dfric - динамический коэффициент трения [0,inf]

sfric означает насколько тяжело сдвинуть тело с места, а dfric - насколько тяжело в движении.


Надеюсь я смог доходчиво обьяснить изложенный материал и если меня не закидают помидорами :) и АВТОМАТ не будет против, то я буду продолжать помогать ему писать учебник:). Впереди ещё много интересного (магниты, ткани, мягкие тела и тд) :super:

viper86 18.02.2009 17:37

Ответ: Учебник по PhysX Wrapper для Blitz3D
 
Вложений: 1
Магниты

Итак, продолжим нашу экскурсию в удивительный мир PhysX :) Сегодня тема пойдёт о магнитах. Штука эта намного полезнее, чем пожет показаться на первый взгляд. Почти в любой игре, в которой используется физика используются и магниты. Любые эффекты, типа телекинеза, гравипушки, чёрной дыры и даже взрывов - это всё дело рук магнитов :) Так что постараемся подробнее разобраться в принципах их действия.

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

Код:

pxCreateMagnet(minforce#, middleforce#, maxforce#)

где:
minforce# - минимальное усилие
middleforce# - среднее усилие
maxforce# - максимальное усилие

Эта функция создаёт магнит с тремя значениями силы. Сила считается по формуле : Force = minforce + middleforce/distance + maxforce/(distance*distance). Установка одного из значений в ноль убирает соответствующее слагаемое. Сила считается между центром магнита и центром тела. Может быть направлена как к магниту(притягивает), так и от него(отталкивает), в зависимости от знака силы. Действует на все тела в сцене.

Код:

pxMagnetActivate(mdata%, mmode%, fmode%)

где:
mdata% - магнит
mmode% - тип магнита
fmode% - тип силы

mmode - тип магнита.
1 - магнит не имеющий области остановки
2 - с областью остановки
3 - тела притягиваются не по силе, а по скоросте (формула для скорости аналогична формуле по силе)
fmode - тип силы (как в pxBodyAddForce и подобных)

Данная функция активирует магнит. Её необходимо запускать в каждом цикле программы, если нужен постоянный магнит (более подробно мы рассмотрим дальше).

И собсно функция для позиционирования магнита:

Код:

pxMagnetSetPosition(mdata%, pos_x#, pos_y#, pos_z#)

где:
mdata% - магнит
pos_x#, pos_y#, pos_z# - координаты

Тут я думаю все понятно. Итак, попробуем создать обычный магнит. Делаем свет, камеру, физический мир, функции для создания, обновления кубика и тд. :

Код:

Graphics3D 800,600,32,2
SetBuffer BackBuffer()

SeedRnd MilliSecs()

SetFont LoadFont(Arial,12)

; Инициализация физики
pxCreateWorld(1,"")
pxSetGravity(0,-10,0)

; Камера
cam=CreateCamera()
PositionEntity cam,15,10,-25
RotateEntity cam,20,35,0

; Свет
light=CreateLight()
PositionEntity light,10,10,-10

; Плоскость
plane=CreatePlane()
EntityAlpha plane,0.8
EntityColor plane,150,255,255
CreateMirror()


While Not KeyDown(1)
       
        pxRenderPhysic(60,0)
        UpdatepxCubes()
        RenderWorld
       
        Flip
Wend
End

Type pxCube
        Field mesh
        Field body       
End Type

Function CreatepxCube(x#,y#,z#)
        pxC.pxCube = New pxCube
        pxC\mesh = CreateCube()
        pxC\body = pxBodyCreateCube(1,1,1,1)       
        pxBodySetPosition pxC\body,x,y,z       
End Function

Function UpdatepxCubes()
        For pxC.pxCube = Each pxCube
                pxBodySetEntity pxC\mesh, pxC\body
        Next
End Function

Function DeletepxCubes()
        For pxC.pxCube = Each pxCube
        FreeEntity pxC\mesh
        pxDeleteBody pxC\body
        Delete pxC               
        Next
End Function

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

Код:

For i=0 To 15
        CreatepxCube(Rnd(-10,10),15,Rnd(-10,10))
Next

А теперь создаём магнит (тоже до цикла):

Код:

mag1=pxCreateMagnet(1,5,10)                ; создаём магнит
pxMagnetSetMaxRadius(mag1, 15)    ; устанавливаем макс радиус действия
pxMagnetSetPosition(mag1,-10,0,0)  ; устанавливаем позицию

И в цикле активируем магнит:

Код:

pxMagnetActivate(mag1,0,0)
Запускаем и любуемся:-) :



Теперь сделаем, чтобы магнит можно было двигать стрелочками на клаве и чтоб он активировался только когда держим пробел. Это пишем до цикла (создаём синий шар, чтоб мы могли видеть перемещение магнита):

Код:

sp =CreateSphere ()
ScaleEntity sp,0.5,0.5,0.5
EntityColor sp,0,0,255

x#=-10
z#=0

А это уже в цикле (управление магнитом)

If KeyDown (205) x# = x#+0.2
If KeyDown (203) x# = x#-0.2
If KeyDown (200) z# = z#+0.2
If KeyDown (208) z# = z#-0.2
If KeyDown(57) pxMagnetActivate(mag1,0,0)
pxMagnetSetPosition(mag1, x, y, z)
PositionEntity sp, x,y,z
[/code]

Попробуем сделать, чтобы по enter магнит менял полярность (отталкивал) и шарик менял цвет. В итоге получаем:

Код:

x#=-10
z#=0
p%=1

While Not KeyDown(1)       
       
        If KeyDown (205) x# = x#+0.2
        If KeyDown (203) x# = x#-0.2
        If KeyDown (200) z# = z#+0.2
        If KeyDown (208) z# = z#-0.2
               
        If KeyDown(57) pxMagnetActivate(mag1,0,0)
       
        If KeyHit(28) p=-p
       
        pxMagnetSetMinForce(mag1, 1*p)
        pxMagnetSetMidForce(mag1, 5*p)
        pxMagnetSetMaxForce(mag1, 10*p)
        pxMagnetSetPosition(mag1, x, y, z)
       
        PositionEntity sp, x,y,z
        If p=1
                EntityColor sp,0,0,255
                Else
                EntityColor sp,255,0,0
        EndIf
                           
       
        pxRenderPhysic(60,0)
        UpdatepxCubes()
        RenderWorld
       
        Flip
Wend

Теперь давайте сделаем взрыв. Создадим новый магнит (ниже первого):

Код:

explode=pxCreateMagnet(0,-20,-50) 
pxMagnetSetMaxRadius(mag1, 15)

и сферу для его обозначения:

Код:

explode_sp=CreateSphere()
EntityColor explode_sp,250,200,100

В цикле пропишем активацию взрыва по кнопке "e" и анимацию сферы:

Код:

If KeyHit(18) And boom=0
        pxMagnetActivate(explode,0,1)
        boom=1
EndIf
               
If boom=1
        rad=rad+2
        ScaleEntity explode_sp,rad,rad,rad
        If rad>200 boom=0
Else
        rad=Abs(Sin(MilliSecs()*0.2))+0.5
        ScaleEntity explode_sp,rad,rad,rad   
EndIf

Естественно, переменные boom и rad лучше обьявить в начале программы (boom%=0 rad#=0). Добавим рестарт появления кубиков по кнопке Tab:

Код:

If KeyHit(15)
        DeletepxCubes()
        For i=0 To 15
                CreatepxCube(Rnd(-10,10),15,Rnd(-10,10))
        Next
EndIf

И теперь мы сможем управлять и взрывом, и магнитом плюс это наглядно видно:



Ниже приведу список функций, которые интуитивно понятны и не требуют разьяснения:

Код:

pxMagnetSetMinRadius(mdata%,minradius#)
pxMagnetSetMaxForce(mdata%,maxforce#)
pxMagnetSetMidForce(mdata%,middleforce#)
pxMagnetSetMinForce(mdata%,minforce#)

Функции для получения (возврата) значений:

Код:

pxMagnetGetPositionX(x#)
pxMagnetGetPositionY(y#)
pxMagnetGetPositionZ(z#)
pxMagnetGetMaxRadius(maxradius#)
pxMagnetGetMinRadius(minradius#)
pxMagnetGetMaxForce(maxforce#)
pxMagnetGetMidForce(middleforce#)
pxMagnetGetMinForce(minforce#)

И собсно функция удаления магнита:

Код:

pxMagnetDelete(mdata%)
Внимание! Функция удаления физического мира pxDestroyWorld() не удаляет магниты.


И последнее, что я хотел рассказать. Те, кто уже в уме представляет себе, как он будет делать в своей игре взрывы и телекинез:) , спросит у меня - а как же быть, если в игре не нужно притягивать/отталкивать все предметы, а наоборот - лишь единичные? А с помощью pxBodySetFlagMagniteble(body%, stat%) и масок, отвечу я вам. Остановимся поподробнее. Итак, функция pxBodySetFlagMagniteble() позволяет нам ставить любому телу флаг (stat), т.е. если stat=0 - магниты не влияют на тело и stat=1 - соответственно влияют. Но такой способ не всегда удобен. Потому существуют так называемые маски:
Код:

pxMagnetSetMask(mdata%,mask%)
pxBodySetMagnetMask(body%,mask%)

Как работают маски? Мы задаём маску любому телу и любому магниту. Если маски тела и магнита равны - магнит действует на тела с этой маской. Если маска тела и мегнита не равны - магнит действует на тела с меньшей маской.
Изменим немного функцию создания кубиков, добавим случайное разделение на металл(белый с маской 2) и неметалл(красный с маской 1):

Код:

Function CreatepxCube(x#,y#,z#)
        pxC.pxCube = New pxCube
        pxC\mesh = CreateCube()
        pxC\body = pxBodyCreateCube(1,1,1,1)       
        pxBodySetPosition pxC\body,x,y,z
       
        Metall=Rand(0,1)
        If Metall
                EntityColor pxC\mesh,230,232,215
                pxBodySetMagnetMask(pxC\body, 2) ;bin(10)
        Else
                EntityColor pxC\mesh,128,64,64
                pxBodySetMagnetMask(pxC\body, 1) ;bin(01)
        EndIf
               
End Function

Теперь поставим маски нашим магнитам:

Код:

pxMagnetSetMask(mag1,2)
pxMagnetSetMask(explode,3)

И наблюдаем. Получаеться у нас магнитом притягиваются только белые кубики (т.к. маски одинако=2), а взрыв берёт всех (т.к. маска взрыва 3 не равна не 2, ни 1 и больше их).

Полный код как обычно в аттаче

ABTOMAT 21.06.2009 20:57

Ответ: Учебник по PhysX Wrapper для Blitz3D
 
Вложений: 1
Хауди хоу, мои невероятные друзья, я снова с вами! Впрочем, это немного не из моего репертуара. После долгого перерыва статьи в этом топике продолжаются, постараюсь писать почаще. Сегодня на повестке дня Хиндж.

Hinge.

Это тоже очень распространённый вид сочленения. Это уже не шарнир (как сферический джойнт), а рычаг, который имеет лимит в виде плоского сектора. Посмотрите на картинку:



Как видно из неё, красный цилиндр уже не может свободно болтаться, а может только вращаться в серой плоскости.

Применяется там, где требуется болтание только в одной плоскости, например, маятник от часов, или дверь в косяке.

Приступим к практике. Возьмём предыдущий пример (13-й, если мне память не изменяет) и заменим сферический джойнт на хиндж, а заодно увидим разницу в динамике.

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

Цитата:

pxJointCreateHinge%( body1%, body2%,x#,y#,z#,nx#,ny#,nz#)
Создаёт Хиндж, возвращает его хендл (если всё ок), или 0 (если почему-то не создалось) или BSOD, кому как повезёт :-) (насчёт BSOD - шутка)

Параметры идентичны pxJointCreateSpherical, а именно:

body1, body2 - тела, которые будем сцеплять.
x, y, z - координаты джойнта при его создании.
nx, ny, nz - нормализованный вектор направления оси джойнта.

Внимание! Тела body1, body2 должны быть динамическими (т.е. иметь массу <> 0). Если вы хотите привязать тело к статике, то подставьте 0 вместо статического тела.

Теперь с чистой совестью заменяем Сферикал на Хиндж:

Код:

Joint = pxJointCreateHinge (Cube1\Body,Cube2\Body,x,y,z,0,1,0)
В принципе можно уже запустить и посмотреть на результат (не забудьте убрать то, что относится к сферикалу, иначе получите форточку)

Таак, крутится-то не в той плоскости. Меняем вектор направления оси хинджа:

Код:

Joint = pxJointCreateHinge (Cube1\Body,Cube2\Body,x,y,z,0,0,1)
Ну вот, теперь вращение намного более заметно. Как вы можете видеть, оно происходит только в одной плоскости.



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

Коллизию, как вы уже догадались, можно при необходимости врубить, вот так:

Цитата:

pxJointHingeSetCollision(joint%)
joint - джойнт, между телами которого надо включать коллизию.

юзаем:

Код:

pxJointHingeSetCollision(joint)
Как и в случае с любым другим джойнтом, лучше ограничивайте угол лимита, бо результат тот же, а жрёт меньше, если только по-другому ну совсем уж никак.

А вот так ограничивается угол:

Цитата:

pxJointHingeSetLimit(joint%,min#,max#)
Аргументы тут попроще, чем в случае со сферическим. Указывается только максимальное и минимальное значение углов.

joint - джойнт, с которым будет производиться манипуляция
min - минимальный угол
max - максимальный угол

Посему коллизию убираем, а юзаем лимит:

Код:

pxJointHingeSetLimit(joint,-30,30)
Контроля кручения вокруг своей оси по понятным причинам в Хиндже нет.
А вот растяжимость менять можно:

Цитата:

pxJointHingeSetLimitSpring(joint%,spr#,targetVal#)
Указывает растяжимость соединения. Аргументы те же, что и у сферического джойнта, вот только дампа нету:
joint - джойнт
spr - эластичность. По дефолту 0, может принимать значения от 0 до бесконечности.
targetVal - значение к которому стремиться. По дефолту 0.

Варнинг! Когда я попытался запустить это, у меня вылезла ошибка, мол, Function not found. Произошло это от того, что функция в хелпе есть, а в деклзе - нет. Путём ковыряния в либе Hex-редактором (да простят меня афторы: ведь не корысти ради, а народа для!) я нашёл решение траблы. Если у вас будет то же самое, то делаем такие действия: лезем в папку с блицом -> userlibls -> Blitzpx.decls (открывать Виндосовским Блокнотом) и где-нибудь (желательно рядом с остальными командами, имеющими отношение к джойнтам) прописываем такую строчку:
Код:

pxJointHingeSetLimitSpring(joint%, spr#, targetVal#):"_pxJointHingeSetSpring@12"
Теперь всё ок, можно запускать.
З.Ы. Написал Рендеру, обещал в ближайшей версии исправить.


Юзаем:

Код:

pxJointHingeSetLimitSpring(joint,10,0)
Удаляется Хиндж точно так же, как и Сферикал, то есть так:
Код:

pxDeleteJoint (joint)
Так что удаление джойнтов по левому шифту будет работать без проблем.

Полный код примера вы найдёте в аттаче "PhysXExample14.zip"

Следующий пост будет посвящён регдоллу ;) (напишу пост после получения аттестата)

ABTOMAT 28.06.2009 00:50

Ответ: Учебник по PhysX Wrapper для Blitz3D
 
Вложений: 3
Ragdoll


Знакомая картина, правда? Для того, чтобы игрок не наблюдал торчащие из стены ноги, существует технология под названием Ragdoll (рас. англ. - "тряпичная кукла"). Суть её такова: все кости трупа являются физическими объектами, в результетае этого они не станут
свободно проходить сквозь стены (как если использовать анимацию смерти). Плюс к этому мы получаем всегда разные позы трупов
(а не всего лишь 2-3 разных анимации), ну и отскакивание их от пуль, гранат и вообще реалистичное поведение. Хорошие примеры игр,
где используется регдолл: Half-Life 2, Painkiller, ну и вообще почти все современные игры (кроме, разве что, стратегий, хотя и там встречается) не обходятся без регдолла. Другое дело, что частенько регдолл сделан паршиво, чисто "для галочки", как, например, в Сталкере или CoD4, или отключены такие плюшки, как реагирование регдоллов на выстрелы (Crysis). Политкорректность, мать вашу :wild:

Давайте вообще делать игры только про пушистых кроликов, причём не менее 50% из них должны быть афроамериканцами, женщинами, людьми с нетрадиционной сексуальной ориентацией, чтобы избежать обвинений в дискриминации, и стрелять они друг в друга должны исключительно водяными пистолетиками? Сумасшедшим американским мамашам очень понравится.

Создаём простейший регдолл

Ну, начнём с простого. Суть данного семпла такова: можно создавать регдоллы в сцене. Пока всё. Как будем делать? Регдолл делается так:
1. Для каждой кости создаётся физическое тело (я буду делать Hull). Можно также делать и сферы, кубы и всё прочее (только не Тримеш), но это всё несерьёзно) ИМХО лучше использовать хуллы. Главное не переборщите с их детализацией, и всё будет ок.
2. Создаются джойнты в тех местах, где кости должны быть соединены. Джойнты настраиваются соответствующим образом. Кроме того, для плечей и голеней лучше использовать Сферические джойнты, т.к. эти сутставы у человека могут крутиться в разные стороны, а вот колени и локти - только в одной плоскости, поэтому для них используем Хиндж.
3. Все кости отпарентиваются (!!!). Это очень важно, т.к. даже если физические тела будут созданы как надо, то представьте, что будет при обновлении (установка кости в позицию и в поворот соотв. тела): обновили чайлд, затем обновили родитель, а т.к. чайлд к нему привязан, то он ушёл с той позиции, где он должен быть, в неизвестное направление, т.к. сдвинулся и повернулся его парент. Ну, а если у того чайлда были тоже чайлды, то понятно, что данный эффект усилится ещё больше и на выходе мы получим не регдолл, а чёрт знает что.

Но для начала нам нужна модель с костями. В качестве подопытного я выбрал спецназовца из Counter-Strike: Condition Zero. Можно было бы взять и из 1.6, но там нет русского спецназа :-) Для того, чтобы упростить тутор, я сделал для него новый скелет, попрошче. Уважаемые про-моделлеры, я знаю, что модель ужасна и скелет тоже, но статья не об этом.



Кроме того, нам понадобится физическая модель для каждой кости:



Обратите внимание: центр каждого объекта совпадает координатами и коротом с центром каждой кости! Это важно (угадайте с одного раза почему) Кроме того, я решил назвать объекты, из которых потом будем делать хуллы, таким образом: его имя - это имя кости, к которой он тоностися + "px". Например, если кость называлась "Bone01", то меш для её физики будет называться "Bone01px". Это нужно для того, чтобы можно было приставив к имени "px" найти нужный меш. В-общем, вооружаемся методом copy-paste (многим из читателей не привыкать ;)) и дописывам где надо px. И да, экспортил без костей.
З.Ы. Если возникнут трудности с выставлением центров объектов в максе, могу уточнить. Пишите в обсуждении.

Так, модельки нашли. Теперь, наконец, пишем код. Нафиг предыдущие примеры, пишем заново. Итак:

Код:

Graphics3D 800,600,32,2
SetBuffer BackBuffer()

cam = CreateCamera()
PositionEntity cam,0,100,-200
CameraClsColor cam,128,128,128
CreateLight()

plane = CreatePlane()
EntityColor plane, 0,128,128

Repeat
        RenderWorld()
        Flip
Until KeyHit(1)
End

Надеюсь, это объяснять не надо. Подрубаем физикс:
Код:

pxCreateWorld(1,"http://forum.boolean.name/")
И рендер физики в цикле:
Код:

pxRenderPhysic(30,0)
Так-с, физику подрубили. Теперь нам нужна наша модель. Загрузим её, вернее, их, и тут же скроем, шоб не мешали.

Код:

Spetsnaz = LoadAnimMesh("Spetsnaz.b3d")
SpetsnazPX = LoadAnimMesh("SpetsnazPX.b3d")

HideEntity Spetsnaz
HideEntity SpetsnazPX

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

Код:

Function CreateRagdoll(man, px,x#,y#,z#)
        man = CopyEntity(man)
        PositionEntity man,x,y,z       
End Function

При нажатии на пробел создаём регдолл в рандомной точке перед камерой.

Код:

If KeyHit(57) Then CreateRagdoll(Spetsnaz, SpetsnazPX,Rand(-100,100),50,Rand(0,100))
Запускаем, давим на кнопку. Челы создаются в рандомной точке, что собсно, от них и требовалось. Продолжаем разговор.
Думаю, нам потребуется тип для регдолла. Описываем его:

Type Ragdoll
Field Mesh ; Моделька
Field Joints[100] ; Хендлы джойнтов
Field Bodies[100] ; Хендлы тел
Field Bones[100] ; Хендлы костей
End Type

Собсно, так как регдолл - это несколько тел, а не одно, как в предыдущих примерах, то мы создаём уже массив для хендлов тел и ентитей. И плюсь ещё храним хендлы джойнтов, на случай, если нам придётся удалять "куклу" (а джойнты тоже надо удалить). Теперь при создании регдолла создаём новый объект типа Регдолл и пихаем сразу туда хендл модельки.

Код:

R.Ragdoll = New Ragdoll
R\Mesh = Man

А вот со всем остальным сложнее. Хитрый план таков: перебираем все чайлды, читаем ихние имена, прибавляем к ним "px" и ищем объект с таким именем в модельке для физики. Если он такой есть, то создаём из него хулл. Функцию создания хулла я уже приводил в четвёртом посте, вот она:

Код:

Function BodyCreateHull%(mesh%, mass#)

    Local nsurf = CountSurfaces(mesh)
    Local nvert = 0
    For ns = 1 To nsurf
        Local surf = GetSurface(mesh,ns)
        nvert = nvert + CountVertices(surf)
    Next
        vbank = CreateBank(nvert*4*3)
    nv = 0
    For ns = 1 To nsurf
        surf = GetSurface(mesh,ns)
        nvv = CountVertices(surf)
        For nvc = 0 To nvv - 1
            PokeFloat vbank,nv*12+0,VertexX(surf,nvc)
            PokeFloat vbank,nv*12+4,VertexY(surf,nvc)
            PokeFloat vbank,nv*12+8,VertexZ(surf,nvc)
            nv = nv+1
        Next
    Next
    Local bbb%= pxBodyCreateHull(vbank, nvert, mass)
    FreeBank vbank
    Return bbb
End Function

Стоп, но ведь у костей тоже есть свои чайлды? Значит, без рекурсии не обойтись. Делаем ещё одну функцию.

Код:

Function CreateHullsForAllChilds(mesh, pxmesh)
        HullMesh = FindChild(pxmesh, EntityName(mesh)+"px")
        If HullMesh Then
                Hull = BodyCreateHull(HullMesh, 10)
                pxBodySetPosition Hull, EntityX(Mesh,1), EntityY(Mesh,1), EntityZ(Mesh,1)
                pxBodySetRotation Hull, EntityPitch(Mesh,1), EntityYaw(Mesh,1), EntityRoll(Mesh,1)
        End If
        For i=1 To CountChildren(mesh)
                CreateHullsForAllChilds(GetChild(mesh,i), pxmesh)
        Next
End Function

Вот, создали для каждой кости нужный хулл с массой 10. Конечно, каждая часть тела весит по-разному, но мы делаем лишь основу, так что пока не станем забивать себе этим голову.
А как же с джойнтами? Давайте разбираться. Если у кости был парент (другая кость), то,
создав для такой кости хулл, его нужно присоединить к хуллу парента. Отсюда видно, что нужно передавать ещё и хендл родительского хулла, если таковой есть. Назовём его batya :ok:
Переделываем функцию, чтобы создавала нам и джойнты:

Код:

Function CreateHullsForAllChilds(mesh, pxmesh, batya=0)
        HullMesh = FindChild(pxmesh, EntityName(mesh)+"px")
        If HullMesh Then
                Hull = BodyCreateHull(HullMesh, 10)
                pxBodySetPosition Hull, EntityX(Mesh,1), EntityY(Mesh,1), EntityZ(Mesh,1)
                pxBodySetRotation Hull, EntityPitch(Mesh,1), EntityYaw(Mesh,1), EntityRoll(Mesh,1)
                If Batya Then
                        Joint = pxJointCreateSpherical (batya,Hull,pxBodyGetPositionX(Hull),pxBodyGetPositionY(Hull),pxBodyGetPositionZ(Hull),0,1,0)
                        pxJointSphericalSetLimitAngle(Joint, 30,1, 0)
                        pxJointSphericalSetLimitTwist(Joint,-10,10,10,1,0)
                        pxJointSphericalSetLimitSpring(Joint, 10, 1, 0)
                End If
        End If
        For i=1 To CountChildren(mesh)
                CreateHullsForAllChilds(GetChild(mesh,i), pxmesh, R, Hull)
        Next
End Function

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

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

Function CreateHullsForAllChilds(mesh, pxmesh, R.Ragdoll, batya=0)
        HullMesh = FindChild(pxmesh, EntityName(mesh)+"px")
        If HullMesh Then
                Hull = BodyCreateHull(HullMesh, 10)
                pxBodySetPosition Hull, EntityX(Mesh,1), EntityY(Mesh,1), EntityZ(Mesh,1)
                pxBodySetRotation Hull, EntityPitch(Mesh,1), EntityYaw(Mesh,1), EntityRoll(Mesh,1)
               
                For i=0 To 100
                        If R\Bodies[i] = 0 Then
                                R\Bodies[i] = Hull
                                R\Bones[i] = Mesh
                                Exit
                        End If
                Next               
                If Batya Then
                        Joint = pxJointCreateSpherical (batya,Hull,pxBodyGetPositionX(Hull),pxBodyGetPositionY(Hull),pxBodyGetPositionZ(Hull),0,1,0)
                        pxJointSphericalSetLimitAngle(Joint, 30,1, 0)
                        pxJointSphericalSetLimitTwist(Joint,-10,10,10,1,0)
                        pxJointSphericalSetLimitSpring(Joint, 10, 1, 0)
                        For i=0 To 100
                        If R\Joints[i] = 0 Then
                                R\Joints[i] = Joint
                                Exit
                        End If
                Next       
                End If
        End If
        For i=1 To CountChildren(mesh)
                CreateHullsForAllChilds(GetChild(mesh,i), pxmesh, R, Hull)
        Next
End Function

Ну, и при создании регдолла юзаем эту функцию:

Код:

Function CreateRagdoll(man, px,x#,y#,z#)
        man = CopyEntity(man)
        PositionEntity man,x,y,z       
        R.Ragdoll = New Ragdoll
        R\Mesh = Man       
        CreateHullsForAllChilds(man, px, R)
End Function

Что там у нас дальше по списку? Ага, отпарентивание всех чилдов.
Ну, это уже к физиксу не относится :) Но для полноты картины примеду и эту функцию:

Код:

Function DeparentAllChilds(mesh)
        Repeat
                If CountChildren(mesh) = 0 Then Exit
                DeparentAllChilds(GetChild(mesh,1))       
        Forever
        EntityParent mesh,0       
End Function

Почему GetChild (mesh,1) а не GetChild (mesh,i) ? Потому что когда в блице удаляется (отпарентивается) чайлд, то нумерация чайлдов обновляется заново. То есть, например, если мы возьмём колоду карт и будем вытаскивать всё время самую нижнюю, (первую) карту, то в конце концов вытащим их все. А если мы, начиная с нижней, будем вытаскивать 1-ю, 2-ю, 3-ю и т.д., то у нас ничего не получится, схватим еррор (когда будем пытаться вытащить 11-ю карту, когда осталось только 10). Так же и тут.

Применяем при создании регдолла.

Код:

Function CreateRagdoll(man, px,x#,y#,z#)
        man = CopyEntity(man)
        PositionEntity man,x,y,z       
        R.Ragdoll = New Ragdoll
        R\Mesh = Man       
        CreateHullsForAllChilds(man, px, R)
        DeparentAllChilds(man)
End Function

И да, посмотрите внимательно: мы передаём в R\Mesh хендл модели спецназовца, но ведь сам скин спецназовца является парентом этого объекта. Или не является, смотря как экспортить (виновата галочка Scene Root) Как же сделать это дело поуниверсальнее? Я решил делать так. Scene Root - это Pivot, а Скин - это Mesh. Значит, проверяем, если man у нас - это пивот, то ищем в нём первый же чайлд (который является в таком случае скином, а сам man удаляем. Здесь нам нужна блицовская команда EntityClass$(ent%), которая возвращает тип ентити, который в неё передали. Кстати, нет, чтобы возвращать целочисленную константу, дак нет, возвращает строчку (в рот Марку ноги!). Вот что она может возвращать:

Цитата:

Pivot
Light
Camera
Mirror
Listener
Sprite
Terrain
Plane
Mesh
MD2
BSP
Итак, сделаем проверку:

Function CreateRagdoll(man, px,x#,y#,z#)
man = CopyEntity(man)
PositionEntity man,x,y,z
R.Ragdoll = New Ragdoll
If EntityClass (man) <> "Mesh" Then
R\Mesh = GetChild(man,1)
EntityParent R\Mesh,0
FreeEntity man
Else
R\Mesh = Man
End If
CreateHullsForAllChilds(R\Mesh, px, R)
DeparentAllChilds(R\Mesh)
End Function

Так, а как быть с обновлением?
Ну, тут уже намного всё проще. Если вы внимательно читали всё предыдущее, то и сами догадаетесь, как это сделать. В-общем, перебираем все регдоллы:

Код:

Function UpdateRagdolls()
        For R.Ragdoll = Each Ragdoll
       
        Next
End Function

И затем перебираем ихние массивы с костями и бодями, ставя их на место. Если наткнулись на 0, то всё, выход из цикла:

Код:

Function UpdateRagdolls()
        For R.Ragdoll = Each Ragdoll
                For i= 0 To 100
                        If R\Bodies[i] = 0 Then Exit
                        pxBodySetEntity R\Bones[i], R\Bodies[i]
                Next
        Next
End Function

Так, ну вроде всё, можно запускать. Не забудьте только поставить в цикл обновление регдоллов.
Смотри: надо бы им и повороты задать рандомныя. Делаем:

Код:

        If KeyHit(57) Then CreateRagdoll(Spetsnaz, SpetsnazPX,Rand(-100,100),50,Rand(0,100), Rand(0,360), Rand(0,360), Rand(0,360))
Ну и соотв. изменения в фукнции создания:
Код:

Function CreateRagdoll(man, px,x#,y#,z#, pitch#,yaw#,roll#)
...
        RotateEntity man,Pitch,Yaw,Roll

Ну вот, вполне себе бодренько дёргают ручками-ножками:

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

Полный код примера а также все необходимые для запуска ресурсы вы найдёте в аттаче "PhysXRagdollExample1.zip"

PhysX Remote Debugger

Недавно Рендер познакомил меня со штукой с тким названием. Очень полезная вещь, скажу я вам, а именно она визуализирует всё, что творится в физическом мире. Пользоваться так: запускаем сначала дебаггер, а затем вашу игру с PhysX'ом, можно прямо из Блица. Настройки там элементарные, а польза впечатляет: если вы раньше недоумевали, почему ваш персонаж натыкается на невидимую стену, то теперь вы без турда её увидите. И да, иногда наблюдать забавно.
Весёлые картинки:




Сам дебаггер лежит в аттаче "RemoteDebugger.zip"

Ragdoll и анимация

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

Код:

Walk = LoadAnimSeq(Spetsnaz, "Walk.b3d")
Ногами не пинать, анимировал сам за 10 минут.
И попробуем сделать так, чтобы человеки некоторое время анимировались (т.е. бегали, стреляли в игре). Для этого я завёл новый тип для "живых" спецназовцев.

Код:

Type Man
        Field Mesh
        Field pxMesh
End Type

Из него же вытекает и создание новых человеков (тут, надеюсь, всё понятно):
Код:

Function CreateMan(Mesh, pxMesh, x#,y#,z#)
        M.Man = New Man
        M\Mesh = CopyEntity(Mesh)
        PositionEntity M\Mesh,x,y,z
        Animate M\Mesh,1,1,1
        M\PXMesh = PXMesh
End Function

Не забудьте UpdateWorld в главном цикле!
Теперь при нажатии на пробел создаём не сами регдоллы, а Man'ов:

If KeyHit(57) Then CreateMan(Spetsnaz, SpetsnazPX,Rand(-100,100),50,Rand(0,100))

Вот они, кросавчеги, шлёпають:



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

Код:

Function CreateRagdoll(man, px)
        Animate man,0,0,1
        R.Ragdoll = New Ragdoll
        If EntityClass (man) <> "Mesh" Then       
                R\Mesh = GetChild(man,1)
                EntityParent R\Mesh,0
                FreeEntity man
        Else
                R\Mesh = Man
        End If
        CreateHullsForAllChilds(R\Mesh, px, R)
        DeparentAllChilds(R\Mesh)
End Function

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

Function RagDollAllMen()
For M.Man = Each Man
CreateRagdoll(M\Mesh, M\pxMesh)
Delete M
Next
End Function

Ну, и вызываем по левому шифту.
Код:

If KeyHit(42) Then RagDollAllMen()
Челочвечки исправно падают. Но давайте для пушчего веселья сделаем обрегдолливание по клику мышкой. Для этого Man'ам надо добавить сферу для пика. Вообще, можно было сделать и прямо для анимированных моделек, но центр у них находится в ногах, и потом пик будет крайне неточным. Добавляем новый филд для пикательного пивота:
Код:

Field PickPivot
Теперь надо как-то найти радиус и позицию пикательного пивота. Как это сделать наглядно? А давайте вместо пивота поместим прозрачную сферу и отмасштабируем её так же, как задаём EntityRadius, так мы увидим, куда будет пикаться.
Код:

        M\PickPivot = CreateSphere()
        EntityColor M\PickPivot,0,0,255
        EntityAlpha M\PickPivot,0.3
        ScaleEntity M\PickPivot,15,35,15
       
        PositionEntity M\PickPivot,x,y+35,z
        EntityPickMode M\PickPivot, 1
        EntityRadius M\PickPivot,15,40

При значениях, как в этом куске кода пикательная сфера садится наиболее удачно. Убираем сферу, заменяя её пивотом, колор и альфу тоже фтопку.
При удалении Man'ов не забываем удалять и пивот:
Код:

FreeEntity M\PickPivot
Теперь готовим функцию Shoot:

Код:

Function Shoot(cam, x,y)
        CameraPick cam, x,y       
        If PickedEntity() Then
                For M.Man = Each Man
                        If M\PickPivot = PickedEntity() Then
                                CreateRagdoll(M\Mesh, M\pxMesh)
                                FreeEntity M\PickPivot
                                Delete M
                                Exit
                        End If
                Next
        End If
End Function

Ну, тут, думаю, всё ясно.
Вызываем в цикле по нажатию мышки:
Код:

If MouseHit(1) Then Shoot(cam, MouseX(), MouseY())
Ну, ещё можно украсить всякими шутками типа звука выстрела - и вот вам осовремененный аналог "Курошлёпа".
Полный код примера и ресурсы для его запуска вы найдёте в аттаче "PhysXExample2.zip"
Для тех, кто просто мимо проходил, есть откомпиленная версия в аттаче "PhysXExample2bin.zip" - оп! Чегой-то в аттач загружать наотрез отказывается: Загрузка файла прошла неудачно. Админы, ау! Пришлось положить сюда.

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

LLI.T.A.L.K.E.R. 06.03.2012 01:11

Ответ: Учебник по PhysX Wrapper для Blitz3D
 
Вложений: 1
Физическое передвижение игрока с видом как от первого, так и от третьего лица.
(моя свободная наработка, скорее всего финальная для примера версия, так как думаю соблюдены все физ. моменты и условия по ходьбе, прыжке и приседании)



Полное физическое взаимодействие с другими объектами и землёй.
Мне она сравнима с той, что была в игре TES IV Oblivion.

Камера от третьего лица не заходит за объекты, а "выталкивается" перед ними:
идёт проверка двух лучей по двум сторонам ширины игрока.
(смена вида камеры - клавиша С)

Скачать: PhysX-B3D-Movement.zip
(исходник, exe и библиотеки прилагаются)


Часовой пояс GMT +4, время: 13:36.

vBulletin® Version 3.6.5.
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Перевод: zCarot