Объект Crowd (толпа) управляет поведением объектов делегатов Delegates. По своей сути Crowd является многоагентной системой. Агентами являются делегаты, преследующие одну или несколько целей. Делегаты могут входить в разные команды. Целеполагание достигается за счет назначения делегатам или командам делегатов одного или нескольких видов поведений. Возможные виды поведений перечислены на рис. 1.
Рис. 1. Виды поведений делегатов
Русские названия видов поведений следующие: избегать, ориентироваться, придерживаться пути, отталкивать, под управлением программы, искать, использовать пространственные возмущения, изменять скорость, прибытие на объект, следование за объектом, отталкивание от стены, поиск стены и блуждание.
Интерактивное употребление толпы рассмотрим на примере управления двумя делегатами, представляемыми в сцене в виде красных пирамид (рис. 2).
Рис. 2. Толпа с двумя делегатами
Также в сцену введены конусы и полусфера, покоящиеся на плоскости.
Назначим делегатам 2 следующих вида поведений: Avoid – избегать конусов и Seek – искать полусферу и воспроизведем анимацию. Предварительно, однако, ассоциируем с каждым делегатом по одному объекту, например куб и сферу. Именно они и будут отображаться в процессе анимации. Сами делегаты (пирамиды) могут быть скрыты.
Последовательность действий следующая:
Рис. 3. Создана команда из двух делегатов
Рис. 4. Команде Team0 назначены поведения Avoid и Seek
Рис. 5. Ассоциация делегатов и объектов
Рис. 6. После ассоциирования делегатов со сферой и кубом
Рис. 7. Параметры решателя
В рассмотренном примере создание команды Team0 необязательно. Достаточно назначить подходящие виды поведений каждому делегату (рис. 8).
Рис. 8. Поведения назначаются делегатам
Если необходимо, чтобы делегат (связанный с ним объект) не покидал найденную полусферу, то следует добавить поведение Surface Arrive с параметром Distance (расстояние), равным, например, 5 (задается в списке прибытие Arrival).
В примере создаются четыре сферических делегата, которые разбиваются на две команды.
Каждой команде назначается поведение Avoid, в качестве обходимых препятствий назначаются конусы (в сцене пять конусов).
При h = 1 в качестве второго поведения назначается Seek, а при ином значении h – поведение Surface Arrive. Объекты, которые нужно находить или к которым нужно прибыть, – это красный и черный кубы.
h = 1
aRng = 300
delete $*
sliderTime = 0f
viewport.ActiveViewport = 4
max vpt persp user
viewport.SetGridVisibility 4 false
animationRange = interval 0f aRng
timeConfiguration.PlaybackLoop = false
vp0 = 150
vp = 0.75 * vp0 / 2
vp2 = 0.5 * vp0
zd = 30
cn = cone radius1:8 radius2:4 height:30 wireColor: [135, 110, 8]
bx = box length:20 width:20 height:20 wireColor:red pos:[-vp, -vp, 0]
bx2 = box length:20 width:20 height:20 wireColor:black pos:[vp, -vp, 0]
pln = plane length:vp0 width:vp0
w = 10
dlg = delegate width:w depth:w height:w pos:[vp2, w, zd] wireColor:red
dlg2 = delegate width:w depth:w height:w pos:[vp2, -w, zd] wireColor:green
dlg3 = delegate width:w depth:w height:w pos:[-vp2, w, zd] wireColor:blue
dlg4 = delegate width:w depth:w height:w pos:[-vp2, -w, zd] wireColor:black
sp = sphere radius:5 wireColor:dlg.WireColor pos:dlg.pos parent:dlg
sp2 = sphere radius:5 wireColor:dlg2.WireColor pos:dlg2.pos parent:dlg2
sp3 = sphere radius:5 wireColor:dlg3.WireColor pos:dlg3.pos parent:dlg3
sp4 = sphere radius:5 wireColor:dlg4.WireColor pos:dlg4.pos parent:dlg4
arrDlgs = #(dlg, dlg2)
arrDlgs2 = #(dlg3, dlg4)
avd = avoidBehavior() -- showHardRadius:on
-- Создаем поведения делегатов (Seek или Surface Arrive)
if h == 1 then (
s = seekBehavior targets:#(bx) name:"sk"
s2 = seekBehavior targets:#(bx2) name:"sk2"
)
else (
s = surfaceArriveBehavior surfaces:#(bx) name:"sArv" \
disableAfterArriving:off facing:on
s2 = surfaceArriveBehavior surfaces:#(bx2) name:"sArv2" \
disableAfterArriving:off facing:on
)
arrBh = #(avd, s, s2)
-- Распределяем делегатов по двум командам
tm = crowdTeam members:arrDlgs name:"tm"
tm2 = crowdTeam members:arrDlgs2 name:"tm2"
crd = crowd behaviors:arrBh teams:#(tm, tm2) solveEnd:aRng \
deleteKeys:on update:off pos:[0, 0, 70] isSelected:off
-- Тиражируем препятствия (конусы), используя объект Scatter,
-- доступ к которому обеспечивает одноименное свойство
crdSct = crd.Scatter
crdSct.CloneObject = cn
crdSct.NumClones = 4
crdSct.CloneType = 0 -- Copy
crdSct.PositionSpace = 3 -- On Surface
crdSct.PositionObject = pln
crowds.GenClones crd
crowds.GenLocations crd
cn.Pos = [0, 0, 0]
arrObstcls = for ob in geometry where substring ob.Name 1 4 == "Cone" collect ob
-- Задаем препятствия поведения Avoid
avd.Obstacles = arrObstcls
-- Назначаем поведения командам
crdSsgn = crowdAssignment team:tm behavior:avd
crdSsgn2 = crowdAssignment team:tm behavior:s
crdSsgn3 = crowdAssignment team:tm2 behavior:avd
crdSsgn4 = crowdAssignment team:tm2 behavior:s2
crd.Assignments = #(crdSsgn, crdSsgn2, crdSsgn3, crdSsgn4)
-- Ассоциируем сферы с делегатами
crd.ObjAssoc.Objects = #(sp, sp2, sp3, sp4)
crd.ObjAssoc.Delegates = #(dlg, dlg2, dlg3, dlg4)
-- Запускаем решатель
crowds.Solve crd
hide #(pln)
max tool zoomExtents
playAnimation()
Один из кадров анимации приведен на рис. 9.
Рис. 9. Толпа из четырех делегатов
В примере толпа управляет поведением четырех сферических делегатов. Целью делегатов является перемещение вдоль пути, заданного сплайном (рис. 10).
Рис. 10. Толпа с четырьмя делегатами, пытающимися двигаться вдоль сплайна
Кроме того, каждый делегат должен избегать столкновений с другими делегатами системы. Для решения последней задачи создается команда из всех делегатов с видом поведения Avoid.
aRng = 300
delete $*
sliderTime = 0f
viewport.ActiveViewport = 4
max vpt persp user
viewport.SetGridVisibility 4 false
animationRange = interval 0f aRng
timeConfiguration.PlaybackLoop = false
-- Создаем путь для делегатов
pth = splineShape pos:[0, 0, 0] wireColor:black \
render_renderable:on render_thickness:2.0
x = 100
dx = 15
y = -10
arrP = #([-x, y, 0], [-80, -50, 0], [-60, -115, 0], [60, -115, 0], [80, -40, 0], [x, y, 0])
addNewSpline pth
for k = 1 to arrP.Count do addKnot pth 1 #smooth #curve arrP[k]
updateShape pth
w = 10
-- Помещаем два делегата в начальной и два делегата конечной точках пути
dlg = delegate width:w depth:w height:w pos:[-x - dx, y, 0] wireColor:red
dlg2 = delegate width:w depth:w height:w pos:[-x + dx, y, 0] wireColor:green
dlg3 = delegate width:w depth:w height:w pos:[x - dx, y, 0] wireColor:blue
dlg4 = delegate width:w depth:w height:w pos:[x + dx, y, 0] wireColor:black
arrDlgs = #(dlg, dlg2, dlg3, dlg4)
rotate arrDlgs 180 [0, 0, 1]
sp = sphere radius:5 wireColor:dlg.WireColor pos:dlg.pos parent:dlg
sp2 = sphere radius:5 wireColor:dlg2.WireColor pos:dlg2.pos parent:dlg2
sp3 = sphere radius:5 wireColor:dlg3.WireColor pos:dlg3.pos parent:dlg3
sp4 = sphere radius:5 wireColor:dlg4.WireColor pos:dlg4.pos parent:dlg4
-- Задаем поведение делегатов
pthF = pathFollowBehavior path:pth start:2 direction:0 name:"pth"
pthF2 = pathFollowBehavior path:pth start:1 direction:1 name:"pth2"
avd = avoidBehavior showHardRadius:off
-- Препятствуем столкновениям между делегатами
avd.Obstacles = arrDlgs
arrBh = #(pthF, pthF2, avd)
-- Создаем команды и назначаем виды поведений
tm = crowdTeam members:#(dlg, dlg2) name:"tm"
tm2 = crowdTeam members:#(dlg3, dlg4) name:"tm2"
tm3 = crowdTeam members:arrDlgs name:"tm3"
crd = crowd behaviors:arrBh teams:#(tm, tm2, tm3) solveEnd:aRng \
deleteKeys:on update:off pos:[0, 0, 70] \
isSelected:on
crdSsgn = crowdAssignment team:tm behavior:pthF
crdSsgn2 = crowdAssignment team:tm2 behavior:pthF2
crdSsgn3 = crowdAssignment team:tm3 behavior:avd active:on
crd.Assignments = #(crdSsgn, crdSsgn2, crdSsgn3)
-- Ассоциируем сферы с делегатами
crd.ObjAssoc.Objects = #(sp, sp2, sp3, sp4)
crd.ObjAssoc.Delegates = #(dlg, dlg2, dlg4, dlg4)
-- Запускаем решатель
crowds.Solve crd
playAnimation()
Один кадр анимации приведен на рис. 10.
С делегатами можно связать двуногих. Обязательным условием такой связи является общий поток движений (Motion Flow) у всех двуногих, ассоциируемых с делегатами.
В примере делегаты и связанные с ними двуногие обладают поведением Wander (блуждать). Двуногих хранит массив arrBs, а делегатов – массив arrDlgs. Двуногие имеют общий поток движений (Shared Motion Flow).
Массив arrDlgs создается функцией mkBp до запуска кода, отвечающего за создание и расчет толпы. Функция и все предшествующие ее запуску операции рассматриваются ниже.
aRng = 500
-- Удаляем всех помощников
for ob in helpers do
if classOf ob == Crowd or classOf ob == Delegate do delete ob
sliderTime = 0f
viewport.ActiveViewport = 4
max vpt persp user
viewport.SetGridVisibility 4 false
animationRange = interval 0f aRng
timeConfiguration.PlaybackLoop = false
x = -45
dx = 30
w = 10
-- Создаем 4-х делегатов и определяем значения их свойств
dlg = delegate width:w depth:(2 * w) height:w pos:[x, 0, 0] wireColor:red
dlg2 = delegate width:w depth:(2 * w) height:w pos:[x + dx, 0, 0] wireColor:green
dlg3 = delegate width:w depth:(2 * w) height:w pos:[x + 2 * dx, 0, 0] wireColor:blue
dlg4 = delegate width:w depth:(2 * w) height:w pos:[x + 3 * dx, 0, 0] wireColor:black
arrDlgs = #(dlg, dlg2, dlg3, dlg4)
for dlg in arrDlgs do (
dlg.StartClip = 1
dlg.MaxAccel = random 0.1 0.3
dlg.RandID = random 1 10
dlg.MaxBank = random 20.0 30.0
dlg.InclineDecelAngle = random 10.0 20.0
dlg.DeclineAccelAngle = random 10.0 20.0
dlg.AverageSpeed = random 1.5 3.5
)
-- Создаем поведение Wander
wndB = wanderBehavior()
arrBh = #(wndB)
-- Создаем команду из делегатов и толпу
tm = crowdTeam members:arrDlgs name:"tm"
crd = crowd behaviors:arrBh teams:#(tm) solveEnd:aRng \
deleteKeys:on update:off pos:[0, 0, 70] isSelected:on
-- Назначаем поведение команде
crdSsgn = crowdAssignment team:tm behavior:wndB
crd.Assignments = #(crdSsgn)
max modify mode
msg = "Array of bipeds not ready yet"
if arrBs == undefined then
messageBox(msg)
else (
if arrBs.Count == 0 then
ntBp = true
else (
ntBp = false
for k = 1 to arrBs.Count do (
b = arrBs[k]
if classOf b == Biped_Object then (
dlg = arrDlgs[k]
dlg.UseBiped = on
dlg.Biped = b
)
else
ntBp = true
)
)
if ntBp then
messageBox(msg)
else
crd.SolveForBipeds = on
)
max tool zoomExtents
crowds.Solve crd
playAnimation()
Один кадр анимации приведен на рис. 11.
Рис. 11. Толпа двуногих
Поток движений создадим на базе трех видов движений: вперед, влево и вправо. Для создания этих видов движений употребим следующий код:
-- Удаляем всех двуногих
delete $bip*
-- Массив углов поворотов в режиме Foot Step
arrBnd = #(0, 20, -20)
-- Число шагов в каждом виде движений
arrNs = #(8, 8, 8)
for k = 1 to arrBnd.Count do (
b = biped.CreateNew 60 -90 [0, 0, 60]
select b
bc = b.Controller
max motion mode
walk = biped.GetMultipleFSParams #walk
walk.NumFootsteps = arrNs[k]
bc.FootStepMode = true
biped.AddMultipleFootprints bc walk
biped.BendFootprints bc arrBnd[k]
biped.NewFootprintKeys bc
bc.FootStepMode = false
-- Путь может быть иным
f = "G:\\bp" + (k as string) + ".bip"
biped.SaveBipFile bc f
)
Результат приведен на рис.12; виды движений сохранены в 3-х BIP-файлах.
Рис. 12. Три вида движений
Поток движений на базе созданных выше видов движений создаст следующий код:
delete $bip*
b = biped.CreateNew 60 -90 [0, 0, 60]
select b
bc = b.Controller
max motion mode
bc.MotionMode = true
bmf = bc.MotionFlow
snp = newSnippet bmf "G:\\b1.bip" [100, 30] redraw:true load:true
snp2 = newSnippet bmf "G:\\b2.bip" [50, 120] redraw:true load:true
snp3 = newSnippet bmf "G:\\b3.bip" [150, 120] redraw:true load:true
snp.ClipName = "bp"
snp2.ClipName = "bp2"
snp3.ClipName = "bp3"
addTransition snp snp2 true
addTransition snp2 snp3 true
addTransition snp3 snp true
addTransition snp snp true
Граф, отвечающий созданному потоку движений, приведен на рис. 13 (интерактивно создается для выбранного двуногого в режиме Motion Flow Mode).
Рис. 13. Поток из трех видов движений
Находясь в режиме Motion Flow Mode, откроем форму редактирования графа потока движений (кнопка Show Graph), выделим клип bp и нажмем на кнопку Select Random Start Clips. После этого рис. 13 преобразуется к следующему виду (рис. 14):
Рис. 14. Первым будет исполнен клип bp
Сохраним поток в файле crwd.mfe, нажав в секции Motion Flow на кнопку Save File или выполнив метод
saveMoFlowFile bmf "G:/crwd.mfe"
Создание массива arrBs обеспечит следующий код:
delete $bip*
n = 4
x = -75
dx = 30
arrBs = #()
for k = 1 to n do (
x += dx
b = biped.CreateNew 60 -90 [x, 0, 60]
append arrBs b
)
b = arrBs[1]
select b
bc = b.Controller
max motion mode
bc.MotionMode = true
messageBox("Define Shared Motion Flow now")
Откроем диалог Shared Motion Flow, нажав на одноименную кнопку секции Motion Flow (рис .15).
Рис. 15. Четыре двуногих обладают одним потоком движений
Нажмем на кнопку New, затем на кнопку Load .mfe и загрузим заготовленный ранее MFE-файл, далее нажмем на кнопку Add и выберем четырех двуногих – OK. Сформируем и рассчитаем толпу двуногих, выберем любого и посмотрим созданный поток скриптов Script Flow (рис. 16).
Рис. 16. Поток скриптов двуногих, созданный решателем толпы