Извините, ничего не найдено.

Не расстраивайся! Лучше выпей чайку!
Регистрация
Справка
Календарь

Вернуться   forum.boolean.name > Программирование игр для компьютеров > Blitz3D > FAQ

FAQ Туториалы и часто задаваемые вопросы

Ответ
 
Опции темы
Старый 05.01.2009, 06:20   #1
ABTOMAT
Ференька
 
Аватар для ABTOMAT
 
Регистрация: 26.01.2007
Адрес: улица Пушкина дом Колотушкина
Сообщений: 10,741
Написано 5,461 полезных сообщений
(для 15,675 пользователей)
Учебник по 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'е, я постараюсь объяснить новичкам как им пользоваться, а заодно, возможно, и сам приобрету что-то новое. Итак, вперёд!
__________________
Мои проекты:
Анальное Рабство
Зелёный Слоник
Дмитрий Маслов*
Различие**
Клюква**

* — в стадии разработки
** — в стадии проектирования
Для проектов в стадии проектирования приведены кодовые имена


Последний раз редактировалось moka, 06.12.2009 в 19:12.
(Offline)
 
Ответить с цитированием
Эти 44 пользователя(ей) сказали Спасибо ABTOMAT за это полезное сообщение:
3dr1aN (09.01.2009), ACTIVATOR (20.01.2009), Виктр (08.02.2009), Alex_Noc (09.01.2009), Andvrok (21.10.2009), Arles (24.07.2009), baton4ik (01.02.2010), Blender (17.01.2010), Brain (15.01.2010), DeadElf (27.07.2010), den (27.07.2010), Diablomania (14.08.2009), Dream (05.01.2009), EvilChaotic (28.08.2009), Ичигорь (01.03.2009), falcon (28.03.2011), Fatalix3d (31.05.2009), FireOwl (09.02.2010), FrankH (17.08.2009), h1dd3n (31.01.2009), Hurrit (16.05.2010), Максим (09.01.2009), impersonalis (09.01.2009), is.SarCasm (20.02.2010), krlmisha (01.06.2011), Main Cry (23.04.2009), mihailkirillov (18.02.2012), MOrtAl44444 (14.02.2011), m_512 (13.02.2009), Nex (29.04.2009), paxlich (31.01.2009), Reks888 (08.07.2010), SashaRX (06.01.2009), Slavik (17.06.2009), strayhnd (30.06.2010), Tadeus (05.01.2009), tirarex (19.01.2012), tormoz (05.01.2009), viper86 (08.02.2009), WhiteBlack (27.07.2010), YaLAS (05.11.2010), Zer0n (21.06.2010), Данил (05.01.2009), ІГРОГРАЙКО (02.07.2009)
Старый 05.01.2009, 06:28   #2
ABTOMAT
Ференька
 
Аватар для ABTOMAT
 
Регистрация: 26.01.2007
Адрес: улица Пушкина дом Колотушкина
Сообщений: 10,741
Написано 5,461 полезных сообщений
(для 15,675 пользователей)
Ответ: Учебник по PhysX Wrapper для Blitz3D

Начало работы. Что понадобится? Первое простое приложение на ФизикСе.

Вам понадобятся:
  • Блитз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
(я сам по нему когда-то учился )

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

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"

Ну что ж, я описал базовые знания об этом враппере. Думаю, они окажутся вам полезными и вы прочитаете продолжение, которое я вскоре напишу. Однако не бойтесь экспериментировать и читать хелп самостоятельно. Ладно, уже скоро полшестого утра по Москве, пойду спать.
Вложения
Тип файла: zip PhysXExample1.zip (362 байт, 2180 просмотров)
Тип файла: zip PhysXExample2.zip (475 байт, 2180 просмотров)
__________________
Мои проекты:
Анальное Рабство
Зелёный Слоник
Дмитрий Маслов*
Различие**
Клюква**

* — в стадии разработки
** — в стадии проектирования
Для проектов в стадии проектирования приведены кодовые имена


Последний раз редактировалось ABTOMAT, 05.01.2009 в 14:18.
(Offline)
 
Ответить с цитированием
Эти 37 пользователя(ей) сказали Спасибо ABTOMAT за это полезное сообщение:
3dr1aN (09.01.2009), ACTIVATOR (20.01.2009), Alex_Noc (09.01.2009), Andvrok (21.10.2009), baton4ik (01.02.2010), Blender (17.01.2010), Brain (15.01.2010), DeadElf (27.07.2010), den (27.07.2010), Diablomania (14.08.2009), Dream (05.01.2009), Dzirt (08.04.2009), EvilChaotic (28.08.2009), FireOwl (27.09.2009), FrankH (17.08.2009), h1dd3n (31.01.2009), HolyDel (05.01.2009), Hurrit (16.05.2010), Максим (09.01.2009), is.SarCasm (20.02.2010), krlmisha (01.06.2011), Main Cry (23.04.2009), m_512 (13.02.2009), Nex (07.01.2009), PackegerX (03.02.2010), robox (23.08.2009), SashaRX (06.01.2009), Slavik (17.06.2009), soneek (31.03.2009), strayhnd (30.06.2010), Tadeus (05.01.2009), tormoz (05.01.2009), viper86 (08.02.2009), vlactelin (05.09.2011), WhiteBlack (27.07.2010), Данил (05.01.2009), ІГРОГРАЙКО (02.07.2009)
Старый 06.01.2009, 05:25   #3
ABTOMAT
Ференька
 
Аватар для ABTOMAT
 
Регистрация: 26.01.2007
Адрес: улица Пушкина дом Колотушкина
Сообщений: 10,741
Написано 5,461 полезных сообщений
(для 15,675 пользователей)
Сообщение Другие примитивы.

Другие примитивы.
Итак, вчера я рассказал как создавать кубики. Но кроме них в ФизикСе есть ещё и другие примитивы. Думаю, будет полезно с ними кратко познакомиться.
Первым будет цилиндр:
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"

Что-то уже поздновато
Думаю, на сегодня всё. Завтра я расскажу о хуллах и тримешах.
Продолжайте экспериментировать.
Вложения
Тип файла: zip PhysXExample3.zip (571 байт, 1977 просмотров)
Тип файла: zip PhysXExample4.zip (727 байт, 1988 просмотров)
__________________
Мои проекты:
Анальное Рабство
Зелёный Слоник
Дмитрий Маслов*
Различие**
Клюква**

* — в стадии разработки
** — в стадии проектирования
Для проектов в стадии проектирования приведены кодовые имена


Последний раз редактировалось ABTOMAT, 07.01.2009 в 01:40.
(Offline)
 
Ответить с цитированием
Эти 32 пользователя(ей) сказали Спасибо ABTOMAT за это полезное сообщение:
3dr1aN (09.01.2009), ACTIVATOR (20.01.2009), Alex_Noc (09.01.2009), Andvrok (21.10.2009), baton4ik (01.02.2010), Blender (17.01.2010), Brain (15.01.2010), DeadElf (27.07.2010), den (27.07.2010), Diablomania (14.08.2009), Dzirt (08.04.2009), EvilChaotic (28.08.2009), h1dd3n (26.01.2009), HolyDel (17.01.2009), Hurrit (16.05.2010), Максим (09.01.2009), is.SarCasm (20.02.2010), Main Cry (23.04.2009), m_512 (13.02.2009), Nex (07.01.2009), PackegerX (03.02.2010), robox (23.08.2009), rr333 (11.02.2009), SashaRX (06.01.2009), Sashka007 (06.01.2009), SBJoker (06.01.2009), Slavik (18.06.2009), strayhnd (30.06.2010), viper86 (08.02.2009), vlactelin (05.09.2011), WhiteBlack (27.07.2010), ІГРОГРАЙКО (02.07.2009)
Старый 06.01.2009, 22:33   #4
ABTOMAT
Ференька
 
Аватар для ABTOMAT
 
Регистрация: 26.01.2007
Адрес: улица Пушкина дом Колотушкина
Сообщений: 10,741
Написано 5,461 полезных сообщений
(для 15,675 пользователей)
Ответ: Учебник по PhysX Wrapper для Blitz3D

Хулл, или как сделать физическую модель в 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 (о да, я знаю, что у меня богатая фантазия на выбор массы ), тогда код создания будет выглядеть так:
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"

В другой раз я расскажу о тримешах и компаундах. Хотел описать их в этом посте, но тема вышла уж очень обширной. Напомнию, все вопросы и предложения можно запостить здесь.
Вложения
Тип файла: zip PhysXExample5.zip (2.5 Кб, 1926 просмотров)
Тип файла: zip PhysXExample6.zip (3.1 Кб, 1874 просмотров)
__________________
Мои проекты:
Анальное Рабство
Зелёный Слоник
Дмитрий Маслов*
Различие**
Клюква**

* — в стадии разработки
** — в стадии проектирования
Для проектов в стадии проектирования приведены кодовые имена


Последний раз редактировалось ABTOMAT, 07.01.2009 в 01:43.
(Offline)
 
Ответить с цитированием
Эти 44 пользователя(ей) сказали Спасибо ABTOMAT за это полезное сообщение:
3dr1aN (09.01.2009), ACTIVATOR (20.01.2009), Alex_Noc (09.01.2009), Andvrok (21.10.2009), Arles (25.08.2009), baton4ik (01.02.2010), Blender (17.01.2010), Brain (15.01.2010), CRASHER (18.01.2009), den (27.07.2010), Diablomania (14.08.2009), Dream (09.01.2009), Eugenes (01.07.2012), EvilChaotic (28.08.2009), h1dd3n (31.01.2009), H@NON (09.01.2009), Harter (07.01.2009), HolyDel (17.01.2009), Hurrit (16.05.2010), Максим (09.01.2009), is.SarCasm (20.02.2010), johnk (07.01.2009), laaqiq (18.01.2010), le-den (09.01.2009), Main Cry (23.04.2009), Mind (03.04.2011), m_512 (13.02.2009), Nex (07.01.2009), NitE (07.01.2009), PackegerX (03.02.2010), radiantstudio (07.04.2011), Randomize (20.01.2009), SashaRX (09.01.2009), Sashka007 (07.01.2009), SBJoker (06.01.2009), Slavik (18.06.2009), strayhnd (30.06.2010), Tadeus (09.01.2009), tirarex (12.12.2011), tormoz (06.01.2009), viper86 (08.02.2009), WhiteBlack (27.07.2010), Zer0n (21.06.2010), ІГРОГРАЙКО (02.07.2009)
Старый 09.01.2009, 20:19   #5
ABTOMAT
Ференька
 
Аватар для ABTOMAT
 
Регистрация: 26.01.2007
Адрес: улица Пушкина дом Колотушкина
Сообщений: 10,741
Написано 5,461 полезных сообщений
(для 15,675 пользователей)
Ответ: Учебник по PhysX Wrapper для Blitz3D

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одвинем камеру подальше, а то великовата вышла кастрюля

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



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

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

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

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



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

PotMeshPhys = LoadMesh("PotPhys.b3d")
Global PotHull = BodyCreateMesh(PotMeshPhys, 0)
pxBodySetFlagCollision(PotHull, 0)
FreeEntity PotMeshPhys
Тут всё аналогично хуллу.

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

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


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

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

Надеюсь, этот пост оказался вам полезен.
Вложения
Тип файла: zip PhysXExample7.zip (11.8 Кб, 1952 просмотров)
__________________
Мои проекты:
Анальное Рабство
Зелёный Слоник
Дмитрий Маслов*
Различие**
Клюква**

* — в стадии разработки
** — в стадии проектирования
Для проектов в стадии проектирования приведены кодовые имена

(Offline)
 
Ответить с цитированием
Эти 37 пользователя(ей) сказали Спасибо ABTOMAT за это полезное сообщение:
3dr1aN (09.01.2009), ACTIVATOR (20.01.2009), Alex_Noc (09.01.2009), Arles (25.08.2009), baton4ik (01.02.2010), Blender (17.01.2010), Brain (15.01.2010), CRASHER (18.01.2009), den (27.07.2010), Diablomania (14.08.2009), Dream (09.01.2009), Eugenes (01.07.2012), h1dd3n (31.01.2009), H@NON (09.01.2009), Harter (15.10.2009), HolyDel (17.01.2009), Hurrit (16.05.2010), Максим (09.01.2009), is.SarCasm (20.02.2010), Isono (17.01.2009), laaqiq (18.01.2010), Main Cry (23.04.2009), mr.DIMAS (11.01.2009), m_512 (13.02.2009), Nex (23.06.2009), NitE (10.01.2009), PackegerX (03.02.2010), Randomize (20.01.2009), rr333 (12.02.2009), SBJoker (09.01.2009), Slavik (24.06.2009), strayhnd (30.06.2010), Tadeus (09.01.2009), tormoz (09.01.2009), viper86 (08.02.2009), WhiteBlack (27.07.2010), ІГРОГРАЙКО (02.07.2009)
Старый 17.01.2009, 19:35   #6
ABTOMAT
Ференька
 
Аватар для ABTOMAT
 
Регистрация: 26.01.2007
Адрес: улица Пушкина дом Колотушкина
Сообщений: 10,741
Написано 5,461 полезных сообщений
(для 15,675 пользователей)
Ответ: Учебник по PhysX Wrapper для Blitz3D

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

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

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



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



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

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

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

Следующий пост ждите примерно через неделю
Вложения
Тип файла: zip PhysXExample8.zip (708 байт, 1890 просмотров)
__________________
Мои проекты:
Анальное Рабство
Зелёный Слоник
Дмитрий Маслов*
Различие**
Клюква**

* — в стадии разработки
** — в стадии проектирования
Для проектов в стадии проектирования приведены кодовые имена


Последний раз редактировалось ABTOMAT, 17.01.2009 в 19:54.
(Offline)
 
Ответить с цитированием
Эти 30 пользователя(ей) сказали Спасибо ABTOMAT за это полезное сообщение:
3dr1aN (17.01.2009), ACTIVATOR (20.01.2009), baton4ik (01.02.2010), Blender (17.01.2010), Brain (15.01.2010), CRASHER (18.01.2009), den (27.07.2010), Diablomania (14.08.2009), Egor Rezenov (17.10.2010), h1dd3n (31.01.2009), H@NON (18.01.2009), Harter (04.11.2009), HolyDel (17.01.2009), Hurrit (16.05.2010), is.SarCasm (20.02.2010), Main Cry (23.04.2009), m_512 (13.02.2009), Nex (23.06.2009), NitE (17.01.2009), PackegerX (03.02.2010), Randomize (20.01.2009), Sashka007 (17.01.2009), SBJoker (17.01.2009), Slavik (24.06.2009), strayhnd (30.06.2010), Tadeus (17.01.2009), tormoz (17.01.2009), viper86 (08.02.2009), WhiteBlack (27.07.2010), ІГРОГРАЙКО (02.07.2009)
Старый 24.01.2009, 22:35   #7
ABTOMAT
Ференька
 
Аватар для ABTOMAT
 
Регистрация: 26.01.2007
Адрес: улица Пушкина дом Колотушкина
Сообщений: 10,741
Написано 5,461 полезных сообщений
(для 15,675 пользователей)
Сообщение Ответ: Учебник по PhysX Wrapper для Blitz3D

Другие примитивы и хуллы в компаунде.

Итак, в прошлый раз я рассказал о том, как сделать компаунд из кубиков. Теперь надо рассказать о других примитивах, там всё по аналогии. Итак, давайте возьмём за основу пример 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)
Теперь порядок. Можно сделать угол поворота побольше:
Теперь совсем рандомщина



Давайте теперь сделаем капсулу не такую толстую:
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"

Собственно, хуллов, как и примитивов, в дескрипторе (-> в компаунде) можно создавать несколько и комбинировать различными путями. Возможно в середине недели напишу "довесочек" типа создания уже реального предмета из хуллов. В следующую субботу напишу статью, наконец, про силы и импульсы - это самые шишечки На сегодня у меня всё, спасибо за внимание.
Вложения
Тип файла: zip PhysXExample9.zip (830 байт, 1658 просмотров)
Тип файла: zip PhysXExample10.zip (678 байт, 1663 просмотров)
Тип файла: zip PhysXExample11.zip (3.2 Кб, 1681 просмотров)
__________________
Мои проекты:
Анальное Рабство
Зелёный Слоник
Дмитрий Маслов*
Различие**
Клюква**

* — в стадии разработки
** — в стадии проектирования
Для проектов в стадии проектирования приведены кодовые имена

(Offline)
 
Ответить с цитированием
Эти 22 пользователя(ей) сказали Спасибо ABTOMAT за это полезное сообщение:
3dr1aN (25.01.2009), baton4ik (01.02.2010), Blender (17.01.2010), Brain (15.01.2010), den (27.07.2010), Diablomania (14.08.2009), h1dd3n (26.01.2009), H@NON (01.02.2009), HolyDel (27.07.2010), indri (05.06.2009), Main Cry (23.04.2009), m_512 (13.02.2009), Nex (23.06.2009), Randomize (25.01.2009), Reizel (21.01.2010), Slavik (24.06.2009), strayhnd (30.06.2010), Tadeus (24.01.2009), tormoz (24.01.2009), viper86 (08.02.2009), WhiteBlack (27.07.2010), ІГРОГРАЙКО (02.07.2009)
Старый 01.02.2009, 03:11   #8
ABTOMAT
Ференька
 
Аватар для ABTOMAT
 
Регистрация: 26.01.2007
Адрес: улица Пушкина дом Колотушкина
Сообщений: 10,741
Написано 5,461 полезных сообщений
(для 15,675 пользователей)
Ответ: Учебник по 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

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

Вот, собственно, и всё о 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
Вот, теперь запускаем и давим сначала обе клавиши - едет прямо. Отпускаем одну - поворачивается. Типа, гоночки Конечно, до гонок далеко: не хватает, в первую очередь, колёс, да и ускорения просто дикие. Но главное - теперь вы знаете как использовать локальную силу в локальной позиции.
__________________
Мои проекты:
Анальное Рабство
Зелёный Слоник
Дмитрий Маслов*
Различие**
Клюква**

* — в стадии разработки
** — в стадии проектирования
Для проектов в стадии проектирования приведены кодовые имена


Последний раз редактировалось ABTOMAT, 01.02.2009 в 04:00.
(Offline)
 
Ответить с цитированием
Эти 26 пользователя(ей) сказали Спасибо ABTOMAT за это полезное сообщение:
3dr1aN (01.02.2009), baton4ik (02.02.2010), Blender (17.01.2010), Brain (15.01.2010), Buraki (02.02.2009), den (27.07.2010), Diablomania (14.08.2009), H@NON (01.02.2009), HolyDel (01.02.2009), Hurrit (16.05.2010), indri (05.06.2009), Main Cry (23.04.2009), Mr_F_ (20.07.2009), m_512 (13.02.2009), Nex (23.06.2009), NitE (01.02.2009), PackegerX (03.02.2010), Randomize (17.10.2009), Slavik (24.06.2009), strayhnd (30.06.2010), Tadeus (01.02.2009), tormoz (01.02.2009), TxN (15.08.2009), viper86 (08.02.2009), WhiteBlack (27.07.2010), ІГРОГРАЙКО (02.07.2009)
Старый 01.02.2009, 03:59   #9
ABTOMAT
Ференька
 
Аватар для ABTOMAT
 
Регистрация: 26.01.2007
Адрес: улица Пушкина дом Колотушкина
Сообщений: 10,741
Написано 5,461 полезных сообщений
(для 15,675 пользователей)
Ответ: Учебник по PhysX Wrapper для Blitz3D

Теперь последнее и самое интересное
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()
Готово! Запускаем, кидаем кубики и расстреливаем их

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

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 поста, тогда глюк прошёл. СабЗиро, где ты?..
Вложения
Тип файла: zip PhysXExample12.zip (1.1 Кб, 1818 просмотров)
__________________
Мои проекты:
Анальное Рабство
Зелёный Слоник
Дмитрий Маслов*
Различие**
Клюква**

* — в стадии разработки
** — в стадии проектирования
Для проектов в стадии проектирования приведены кодовые имена

(Offline)
 
Ответить с цитированием
Эти 28 пользователя(ей) сказали Спасибо ABTOMAT за это полезное сообщение:
3dr1aN (01.02.2009), baton4ik (02.02.2010), Blender (17.01.2010), Brain (15.01.2010), Buraki (02.02.2009), CRASHER (10.02.2009), den (27.07.2010), Diablomania (14.08.2009), h1dd3n (01.02.2009), H@NON (01.02.2009), HolyDel (01.02.2009), Hurrit (16.05.2010), Iv-FeliS (03.04.2009), Main Cry (23.04.2009), m_512 (13.02.2009), Nex (23.06.2009), PackegerX (03.02.2010), Randomize (10.02.2009), Slavik (24.06.2009), St.AnGer (09.06.2009), strayhnd (30.06.2010), Tadeus (01.02.2009), tirarex (01.03.2012), tormoz (01.02.2009), TxN (15.08.2009), viper86 (08.02.2009), WhiteBlack (27.07.2010), ІГРОГРАЙКО (02.07.2009)
Старый 09.02.2009, 00:09   #10
ABTOMAT
Ференька
 
Аватар для ABTOMAT
 
Регистрация: 26.01.2007
Адрес: улица Пушкина дом Колотушкина
Сообщений: 10,741
Написано 5,461 полезных сообщений
(для 15,675 пользователей)
Сообщение Ответ: Учебник по PhysX Wrapper для Blitz3D

Сочленения

Итак, сочленения. По-буржуйски - джойнты (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 получается один и тот же эффект. При значениях за пределами указанного диапазона лимит отключается вовсе. 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-м классе - тот поймёт. Не могу сказать точно, будет ли следующий пост через неделю. Если нет - напишу позже. На сегодня у меня всё
Вложения
Тип файла: zip PhysXExample13.zip (683 байт, 1589 просмотров)
__________________
Мои проекты:
Анальное Рабство
Зелёный Слоник
Дмитрий Маслов*
Различие**
Клюква**

* — в стадии разработки
** — в стадии проектирования
Для проектов в стадии проектирования приведены кодовые имена


Последний раз редактировалось ABTOMAT, 09.02.2009 в 19:54.
(Offline)
 
Ответить с цитированием
Эти 26 пользователя(ей) сказали Спасибо ABTOMAT за это полезное сообщение:
3dr1aN (10.02.2009), baton4ik (02.02.2010), Blender (17.01.2010), Brain (15.01.2010), CRASHER (10.02.2009), DeadElf (27.07.2010), den (27.07.2010), Diablomania (14.08.2009), h1dd3n (13.02.2009), H@NON (09.02.2009), Harter (08.10.2009), HolyDel (09.02.2009), Hurrit (16.05.2010), Main Cry (23.04.2009), m_512 (13.02.2009), Nex (23.06.2009), Randomize (10.02.2009), Render (09.02.2009), Slavik (24.06.2009), strayhnd (30.06.2010), St_AnGer (25.09.2016), tormoz (09.02.2009), TxN (15.08.2009), viper86 (10.02.2009), WhiteBlack (27.07.2010), ІГРОГРАЙКО (02.07.2009)
Старый 10.02.2009, 17:43   #11
viper86
Нуждающийся
 
Аватар для viper86
 
Регистрация: 19.11.2008
Адрес: Украина, Луганск
Сообщений: 72
Написано 34 полезных сообщений
(для 123 пользователей)
Материалы

Материалы в 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 - насколько тяжело в движении.


Надеюсь я смог доходчиво обьяснить изложенный материал и если меня не закидают помидорами и АВТОМАТ не будет против, то я буду продолжать помогать ему писать учебник. Впереди ещё много интересного (магниты, ткани, мягкие тела и тд)
Вложения
Тип файла: zip materials.zip (1.2 Кб, 1648 просмотров)
__________________
Лечим заражение... одна пуля - один больной.

Последний раз редактировалось viper86, 10.02.2009 в 19:49.
(Offline)
 
Ответить с цитированием
Эти 26 пользователя(ей) сказали Спасибо viper86 за это полезное сообщение:
3dr1aN (12.02.2009), ABTOMAT (10.02.2009), baton4ik (02.02.2010), Brain (15.01.2010), den (27.07.2010), Diablomania (14.08.2009), Dream (13.02.2009), Ичигорь (01.03.2009), FireOwl (10.10.2009), h1dd3n (13.02.2009), Harter (08.10.2009), HolyDel (18.02.2009), Hurrit (16.05.2010), Isono (11.02.2009), Main Cry (23.04.2009), m_512 (13.02.2009), Nex (23.06.2009), Randomize (13.02.2009), Slavik (24.06.2009), strayhnd (30.06.2010), Tadeus (18.02.2009), tirarex (01.03.2012), tormoz (10.02.2009), TxN (15.08.2009), WhiteBlack (27.07.2010), ІГРОГРАЙКО (02.07.2009)
Старый 18.02.2009, 17:37   #12
viper86
Нуждающийся
 
Аватар для viper86
 
Регистрация: 19.11.2008
Адрес: Украина, Луганск
Сообщений: 72
Написано 34 полезных сообщений
(для 123 пользователей)
Сообщение Ответ: Учебник по PhysX Wrapper для Blitz3D

Магниты

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

Полный код как обычно в аттаче
Вложения
Тип файла: zip magnets.zip (1.3 Кб, 1600 просмотров)
__________________
Лечим заражение... одна пуля - один больной.
(Offline)
 
Ответить с цитированием
Эти 25 пользователя(ей) сказали Спасибо viper86 за это полезное сообщение:
3dr1aN (19.02.2009), ABTOMAT (18.02.2009), baton4ik (02.02.2010), Blender (17.01.2010), Brain (15.01.2010), den (27.07.2010), Diablomania (14.08.2009), Ичигорь (01.03.2009), h1dd3n (18.02.2009), Harter (08.10.2009), HolyDel (18.02.2009), Hurrit (12.06.2009), Мик Данди (21.11.2009), indri (05.06.2009), ISKATEL (27.05.2009), Main Cry (23.04.2009), Nex (23.06.2009), PackegerX (03.02.2010), Randomize (27.02.2009), Slavik (24.06.2009), strayhnd (30.06.2010), Tadeus (18.02.2009), tormoz (18.02.2009), WhiteBlack (27.07.2010), ІГРОГРАЙКО (02.07.2009)
Старый 21.06.2009, 20:57   #13
ABTOMAT
Ференька
 
Аватар для ABTOMAT
 
Регистрация: 26.01.2007
Адрес: улица Пушкина дом Колотушкина
Сообщений: 10,741
Написано 5,461 полезных сообщений
(для 15,675 пользователей)
Ответ: Учебник по PhysX Wrapper для Blitz3D

Хауди хоу, мои невероятные друзья, я снова с вами! Впрочем, это немного не из моего репертуара. После долгого перерыва статьи в этом топике продолжаются, постараюсь писать почаще. Сегодня на повестке дня Хиндж.

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"

Следующий пост будет посвящён регдоллу (напишу пост после получения аттестата)
Вложения
Тип файла: zip PhysXExample14.zip (661 байт, 1598 просмотров)
__________________
Мои проекты:
Анальное Рабство
Зелёный Слоник
Дмитрий Маслов*
Различие**
Клюква**

* — в стадии разработки
** — в стадии проектирования
Для проектов в стадии проектирования приведены кодовые имена

(Offline)
 
Ответить с цитированием
Эти 21 пользователя(ей) сказали Спасибо ABTOMAT за это полезное сообщение:
3dr1aN (21.06.2009), Arles (27.10.2010), Blender (17.01.2010), Brain (15.01.2010), den (27.07.2010), Diablomania (14.08.2009), FireOwl (11.02.2010), h1dd3n (22.06.2009), Harter (08.10.2009), Hurrit (16.05.2010), Main Cry (28.06.2009), Nex (23.06.2009), PackegerX (03.02.2010), Randomize (03.07.2009), Slavik (28.06.2009), strayhnd (30.06.2010), Tadeus (22.06.2009), tormoz (21.06.2009), viper86 (16.07.2009), WhiteBlack (27.07.2010), ІГРОГРАЙКО (02.07.2009)
Старый 28.06.2009, 00:50   #14
ABTOMAT
Ференька
 
Аватар для ABTOMAT
 
Регистрация: 26.01.2007
Адрес: улица Пушкина дом Колотушкина
Сообщений: 10,741
Написано 5,461 полезных сообщений
(для 15,675 пользователей)
Сообщение Ответ: Учебник по PhysX Wrapper для Blitz3D

Ragdoll


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

Давайте вообще делать игры только про пушистых кроликов, причём не менее 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
Переделываем функцию, чтобы создавала нам и джойнты:

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
Ну вот, вполне себе бодренько дёргают ручками-ножками:

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

Полный код примера а также все необходимые для запуска ресурсы вы найдёте в аттаче "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" - оп! Чегой-то в аттач загружать наотрез отказывается: Загрузка файла прошла неудачно. Админы, ау! Пришлось положить сюда.

На этом всё. По всем вопросам пишите в обсуждение. Надеюсь, кому-то этот пост оказался полезен. В ближайших постах я расскажу, как настроить регдолл получше, а также, как делаь "капсюльную" физику игрока.
Вложения
Тип файла: zip PhysXRagdollExample1.zip (233.8 Кб, 1889 просмотров)
Тип файла: zip RemoteDebugger.zip (873.5 Кб, 1779 просмотров)
Тип файла: zip PhysXRagdollExample2.zip (370.8 Кб, 1966 просмотров)
__________________
Мои проекты:
Анальное Рабство
Зелёный Слоник
Дмитрий Маслов*
Различие**
Клюква**

* — в стадии разработки
** — в стадии проектирования
Для проектов в стадии проектирования приведены кодовые имена

(Offline)
 
Ответить с цитированием
Эти 63 пользователя(ей) сказали Спасибо ABTOMAT за это полезное сообщение:
3dr1aN (28.06.2009), Alex_Noc (29.06.2009), Android (08.07.2010), Arles (25.08.2009), baton4ik (02.02.2010), Blender (17.01.2010), Brain (15.01.2010), cahekp (01.07.2009), cancel (20.01.2010), Coks (05.04.2010), Coover (01.03.2012), CRASHER (30.06.2009), Damp (27.10.2009), DeeJex (24.02.2010), den (09.07.2010), Diablomania (14.08.2009), Diffuse13 (28.12.2010), Dream (20.02.2010), Dzirt (28.06.2009), Ичигорь (23.08.2010), falcon (28.06.2009), fanblitz (25.07.2009), FireOwl (08.10.2009), FrankH (17.08.2009), h1dd3n (04.07.2009), H@NON (28.06.2009), Harter (08.10.2009), HolyDel (27.07.2010), Hurrit (16.05.2010), Мик Данди (21.11.2009), impersonalis (28.06.2009), Leowey (02.01.2011), Main Cry (28.06.2009), MaxEDn2 (21.08.2009), Mhyhr (28.06.2009), MisterAlex (06.08.2010), Mr.Death (04.10.2010), MrFrosT1 (15.02.2011), m_512 (28.06.2009), Nex (28.06.2009), NitE (28.06.2009), PackegerX (03.02.2010), Randomize (03.07.2009), Reizel (30.12.2009), Reks888 (08.07.2010), robox (23.08.2009), rr333 (28.06.2009), Sashka007 (10.08.2009), SBJoker (28.06.2009), Slavik (28.06.2009), Spiderman (03.03.2010), St.AnGer (08.07.2009), strayhnd (30.06.2010), St_AnGer (24.08.2012), Tadeus (28.06.2009), Taugeshtu (28.06.2009), tirarex (25.11.2011), tormoz (28.06.2009), Townboy (05.11.2009), TxN (20.09.2009), viper86 (16.07.2009), WhiteBlack (27.07.2010), ІГРОГРАЙКО (02.07.2009)
Старый 06.03.2012, 01:11   #15
LLI.T.A.L.K.E.R.
Мастер
 
Аватар для LLI.T.A.L.K.E.R.
 
Регистрация: 24.06.2009
Адрес: Набережные Челны
Сообщений: 930
Написано 292 полезных сообщений
(для 504 пользователей)
Ответ: Учебник по PhysX Wrapper для Blitz3D

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



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

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

Скачать: PhysX-B3D-Movement.zip
(исходник, exe и библиотеки прилагаются)
Вложения
Тип файла: 7z 3D-Movement.7z (1,011.0 Кб, 1581 просмотров)

Последний раз редактировалось Randomize, 06.03.2012 в 01:38. Причина: приделал аттачем так как на ifolder могут удалить со временем
(Offline)
 
Ответить с цитированием
Эти 3 пользователя(ей) сказали Спасибо LLI.T.A.L.K.E.R. за это полезное сообщение:
Randomize (06.03.2012), St_AnGer (24.08.2012), tirarex (08.05.2013)
Ответ


Опции темы

Ваши права в разделе
Вы не можете создавать темы
Вы не можете отвечать на сообщения
Вы не можете прикреплять файлы
Вы не можете редактировать сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.

Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
.Newton Wrapper for Blitz3D itmbin Библиотеки 162 15.11.2017 23:13
PhysX wrapper Render Библиотеки 271 28.09.2016 09:26
Обсужение учебника по PhysX Wrapper для Blitz3D ABTOMAT 3D-программирование 309 24.02.2014 08:46
Русский учебник по PhysX ArtemkA Переводы 38 16.03.2011 11:52
Капсульный игрок в PhysX Wrapper JohnAustin 3D-программирование 10 17.12.2009 20:50


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


vBulletin® Version 3.6.5.
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Перевод: zCarot
Style crйe par Allan - vBulletin-Ressources.com