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! !!
Надеюсь, этот пост оказался вам полезен.