Список примеров

Примеры программирования системы частиц Particle Flow

Содержание

Введение

Из частиц создаются снеговик и другие фигуры. Для получения результата использованы система частиц PF Source (поток частиц) и язык программирования MAXScript.
Системы частиц позволяют моделировать явления реального мира и воплощать разного рода вымышленные образы.
MAXScript не только предоставляет дополнительные возможности при работе с системами частиц, но и укоряет процесс получения результата. Кроме того, код компактно описывает моделируемую сцену, что весьма часто позволяет оценить генерируемые им образы, не имея под рукой 3ds Max.
Эти и иные преимущества будут ощутимы при наличии определенных навыков и достаточно продолжительной тренировки.
В качестве иллюстрации тезиса о компактности представления модели приведем код, обеспечивающей решение следующей задачи:

  1. Обеспечить генерацию цилиндрических частиц из ребер полигонального объекта.
  2. В качестве объекта употребить текст с модификатором Extrude (выдавить).
  3. Выполнить анимацию масштабирования частиц.

Получение результата обеспечивает следующий код:

-- Block 1
-- Очищаем сцену и устанавливаем анимационный интервал из 100 кадров
delete $*
animationRange = interval 0f 100f
ng = 10.0
-- Формируем текст и выполняем его выдавливание
t = text text:"Some T" size:100 rotation:((eulerAngles -ng ng 0) as quat)
addModifier t (extrude amount:0.5)
-- Цилиндр, используемый при определении формы частиц
cl = cylinder radius:1 height:40
-- Прячем текст и цилиндр
hide #(t, cl)
-- Block 2
-- Создаем источник частиц и направляем иконку эмиттера вверх с небольшим наклоном
pF = PF_Source enable_Particles:true quantity_Viewport:100 \
 rotation:((eulerAngles -ng (180 - ng) 0) as quat) isSelected:on
particleFlow.BeginEdit()
-- Обеспечивает одномоментно рождение 600 частиц
opBth = birth emit_Start:0 emit_Stop:0 type:0 amount:600
-- Частицы будут рождаться на ребрах выдавленного текста
opPO = position_Object emitter_Objects:#(t) delete:on location:2 \
 apart_Placement:on apart_Distance:8
-- Устанавливает объект (цилиндр cl), форму которого будут иметь частицы
opSI = shape_Instance shape_Object:cl
-- Отвечает за масштабирование частиц
opSP = scaleParticles type:2 constrain_Scale:off
-- Вызывает поворот частиц
opRt = rotation direction:2 euler_X:ng euler_Y:-ng
-- Позволяет отобразить частицы программой воспроизведения
opRP = renderParticles type:2
-- Отвечает за отображение сцены в видовом порте
opDP = displayParticles color:red type:6
evn = event()
particleFlow.EndEdit()
evn.AppendAction opBth
evn.AppendAction opPO
evn.AppendAction opSI
evn.AppendAction opSP
evn.AppendAction opRt
evn.AppendAction opDP
pF.AppendAction opRP
pF.AppendInitialActionList evn
-- Block 3
-- Задаем ключи анимации масштабирования частиц по оси Z
-- Подавляем перерисовку сцены
with redraw off (
 max tool animmode; set animate on
 sliderTime = 0f; opSP.Z_Scale_Factor = 0
 sliderTime = 50f; opSP.Z_Scale_Factor = 100
 sliderTime = 100f; opSP.Z_Scale_Factor = 50
 max tool animmode; set animate off
 sliderTime = 0f
)
max modify mode
max tool zoomExtents
playAnimation()

Последний кадр анимации приведен на рис. 1.

PF Source: источник частиц – ребра объекта

Рис. 1. Частицы на ребрах текста

В основной программе три блока.
Первый блок подготовительный. Он очищает сцену, создает Mesh в виде выдавленного текста t и цилиндра cl, форма которого будет затем использована для отображения частиц.
Второй блок отвечает за создание источника частиц. Источник содержит одно событие с 6-ю операторами (рис. 2).

PF Source: операторы источника частиц с одним событием

Рис. 2. Источник частиц в задаче Текст из частиц

Чтобы открыть диалог Particle View (обозреватель систем частиц) и просмотреть состав источника, достаточно выбрать источник частиц PF Source, перейти на вкладку Modify командного окна и нажать на кнопку Particle View группы Setup сопутствующего диалога. Выбор источника и переход на вкладку Modify предусмотрены в приведенном выше коде (свойство источника IsSelected = on и выполнена команда max modify mode).
Созданный источник pF (употреблен конструктор PF_Source) размещен в начале координат и повернут (свойство Rotation) относительно осей X и Y соответственно на 10° и 170°. Причем относительно оси X поворот выполнен по часовой стрелке, относительно оси Y – против часовой стрелки.
Оператор Birth обеспечивает рождение всех 600 частиц (свойство Amount = 600) в нулевой момент времени (Emit_Start = Emit_Stop = 0).
Оператор Position_Object указывает на то, что частицы будут рождаться на ребрах выдавленного текста (Emitter_Objects = #(t), Location = 2). Кроме того, при размещении частиц на ребрах объекта будет приниматься во внимание значение свойства Apart_Distance, регулирующее расстояние между частицами.
Оператор Shape_Instance устанавливает объект (цилиндр cl), форму которого будут иметь частицы.
Оператор ScaleParticles отвечает за масштабирование частиц (выполняется анимация масштабирования по оси Z).
Оператор Rotation вызывает поворот всех частиц относительно осей X и Y соответственно на 10° и -10°. Этот поворот обеспечит перпендикулярность частиц тексту, используемого в качестве эмиттера.
Оператор RenderParticles, задаваемый для всей системы единожды, позволит отобразить частицы средствами одной из доступных программ воспроизведения.
Оператор DisplayParticles отвечает за отображение сцены в видовом порте. В нашем случае оператор устанавливает для частиц режим вывода Geometry (геометрия, type = 6) и красный цвет частиц (color = red).
В третьем блоке программы в точках 0f, 50f и 100f задаются ключи анимации масштабирования частиц по оси Z. Обеспечивается полная видимость сцены (max tool zoomExtents) и ее анимация (PlayAnimation).
Приведенный пример просто реализуется в диалоге Particle View и без применения какого-либо кода.

Запуск программы

Запуск программы выполняется в 3ds Max в следующем порядке:

  1. Открыть редактор кода (меню MAXScript – MAXScript Editor).
  2. Перейти в открывшийся диалог и при необходимости создать новую вкладку (Ctrl + N).
  3. Скопировать код, приведенный в уроке, в чистую вкладку редактора.
  4. Позиционироваться в любом месте скопированного кода и нажать Ctrl + E, либо воспользоваться меню редактора Tools – Evaluate All.

Снежинка

Реализуется следующая сцена: четыре потока частиц в виде разноцветных снежинок пересекаются в одной точке и порождают сферический источник снежинок, медленно удаляющихся от его центра (рис. 3).

PF Source: снежинка из частиц с формой Нedra

Рис. 3. Большая снежинка

Задача решается по следующей схеме:

  1. Создаются 4 источника частиц: по одному на каждый поток. В каждом источнике частицы генерируются в одной точке и после рождении двигаются в начало мировой системы координат (в этой точке потоки частиц пересекаются).
  2. Форма частиц перенимается у стандартного объекта Hedra семейства Star 2 (разновидность объемной звезды).
  3. В декоративных целях в точках генерации частиц отображаются конусы, ориентированные вдоль соответствующих траекторий частиц.
  4. Частицы, оказавшиеся в точке пересечения потоков частиц, удаются.
  5. В точке пересечения потоков частиц создается еще один, пятый источник частиц. В качестве объекта, испускающего частицы берется сфера с небольшим числом сегментов (segs = 8). Частицы исходят из вершин этой сферы. Вектор скорости частиц направлен по радиусу сферы, проведенному из ее центра до соответствующей вершины (свойство Direction оператора Speed равно 1).
  6. Форма частиц 5-го источника перенимается у другого объекта Hedra семейства Star 1.
  7. Материал (цвет) частиц каждого источника задается оператором Material_Frequency и выбирается случайным образом из 10 материалов. Для этих целей создается материал Multimaterial с 10-ю компонентами (рис. 4).
Материал Multimaterial для оператора Material_Frequency

Рис. 4. Образец материала для частиц

Эту схему реализует следующий код:

-- Готовит сцену
fn prps = (
 delete $*
 sliderTime = 0f
 animationRange = interval 0f 500f
 viewport.SetLayout #layout_4
 viewport.ActiveViewport = 4
 max vpt persp user
 viewport.SetGridVisibility 4 false
 backgroundColor = black
)
-- Создает при каждом обращении один источник частиц
fn mkPF pRt ps pRtCn snwFlk mlt = (
 local pF, opDP
 rt = (eulerAngles pRt[1] pRt[2] 0) as quat
 rtCn = (eulerAngles pRtCn[1] pRtCn[2] 0) as quat
 h = 7
 cone radius1:1 radius2:2 height:-h rotation:rtCn pos:ps wireColor:white
 pF = PF_Source enable_Particles:true emitter_Type:0 rotation:rt \
  pos:ps quantity_Viewport:100 show_Logo:off show_Emitter:off
 particleFlow.BeginEdit()
 -- Обеспечивает рождение одной частицы каждые 5 кадров
 opBth = birth emit_Start:0 emit_Stop:(350 * 160) type:1 rate:5
 -- Указывает системе на необходимость генерировать частицы в базовой точке эмиттера
 opPI = position_Icon location:0
 opSI = shape_Instance shape_Object:snwFlk
 opRt = rotation direction:0
 -- Задает угловую скорость частиц и характер ее изменения
 opSpn = spin rate:360 variation:0 direction:0
 -- Устанавливает скорость частицы и ее направление
 opSpd = speed speed:30 direction:0
 opSO = script_Operator proceed_Script:"
 on channelsUsed pCont do pCont.UsePosition = true
 on proceed pCont do (
  nP = pCont.NumParticles()
  for k = 1 to nP do (
   pCont.ParticleIndex = k
   p = pCont.ParticlePosition
   -- Удаляем частицу с отрицательной Z-координатой
   if p[3] < 0 do pCont.DeleteParticle k
  )
 )"
 -- Задает материал частиц
 opMFrq = material_Frequency assigned_Material:mlt \
  mtl_ID_1:10 mtl_ID_2:10 mtl_ID_3:10 mtl_ID_4:10 \
  mtl_ID_5:10 mtl_ID_6:10 mtl_ID_7:10 mtl_ID_8:10 \
  mtl_ID_9:10 mtl_ID_10:10 show_In_Viewport:on
 opDP = displayParticles type:6
 opRP = renderParticles type:2
 evn = event()
 particleFlow.EndEdit()
 evn.AppendAction opBth
 evn.AppendAction opPI
 evn.AppendAction opSI
 evn.AppendAction opRt
 evn.AppendAction opSpn
 evn.AppendAction opSpd
 evn.AppendAction opSO
 evn.AppendAction opMFrq
 evn.AppendAction opDP
 pF.AppendAction opRP
 pF.AppendInitialActionList evn
)
-- Block 1
-- Подготовка сцены
prps()
-- Массив arrStd хранит 10 компонентов материала Multimaterial
arrStd = for k = 1 to 10 collect \
 standard diffuse:[random 100 200, random 100 200, random 100 200] showInViewport:on
mlt = multimaterial numsubs:10 materialList:arrStd
meditMaterials[1] = mlt
-- Создаем две 3d-звезды, форму которых перенимают частицы
hdr = hedra radius:2 family:4    -- Star 2
hdr2 = hedra radius:3 family:3    -- Star 1
lght = sphere radius:10 segs:8 wireColor:[200, 200, 200]
hide #(hdr, hdr2, lght)
zP = 100
arrPs = #([-100, 0, zP], [0, 100, zP], [100, 0, zP], [0, -100, zP])
ng = 45
arrRt = #([0, ng], [ng, 0], [0, -ng], [-ng, 0])
ngCn = -135
arrRtCn = #([0, ngCn], [ngCn, 0], [0, -ngCn], [-ngCn, 0])
-- Block 2
-- Создем 4 источника частиц
for k = 1 to 4 do mkPF arrRt[k] arrPs[k] arrRtCn[k] hdr mlt
-- Block 3
-- Создаем пятый, расположенный в начале координат источник частиц
pF = PF_Source enable_Particles:true quantity_Viewport:100 show_Logo:off show_Emitter:off
particleFlow.BeginEdit()
opBth = birth emit_Start:(150 * 160) emit_Stop:(500 * 160) type:1 rate:25
opPO = position_Object emitter_Objects:#(lght) location:1
opSI = shape_Instance shape_Object:hdr2
opRt = rotation direction:0
opSpn = spin rate:360 variation:0 direction:0
opSpd = speed speed:1.0 direction:1  -- Icon center out
opMFrq = material_Frequency assigned_Material:mlt \
 mtl_ID_1:10 mtl_ID_2:10 mtl_ID_3:10 mtl_ID_4:10 \
 mtl_ID_5:10 mtl_ID_6:10 mtl_ID_7:10 mtl_ID_8:10 \
 mtl_ID_9:10 mtl_ID_10:10 show_In_Viewport:on
opDP = displayParticles type:6
opRP = renderParticles type:2
evn = event()
particleFlow.EndEdit()
evn.AppendAction opBth
evn.AppendAction opPO
evn.AppendAction opSI
evn.AppendAction opRt
evn.AppendAction opSpn
evn.AppendAction opSpd
evn.AppendAction opMFrq
evn.AppendAction opDP
pF.AppendAction opRP
pF.AppendInitialActionList evn
max tool zoomExtents
playAnimation()

В основной программе три блока.
В первом блоке функцией prps подготавливается сцена. Затем создается материал Multimaterial с 10-ю компонентами, хранимыми массивом arrStd. Каждый элемент массива – это стандартный материал. RGB-составляющие диффузионной компоненты каждого материала генерируются случайным образом из диапазона 100 – 200 функцией Random.
Далее создаются объекты hdr и hdr2 (3d-звезды), форму которых перенимают частицы, и сфера lght, употребляемая в качестве эмиттера частиц.
В массивы arrPs и arrRt заносятся координаты и углы поворота источников пересекающихся потоков частиц, обеспечивающие пересечение потоков в начале мировой системы координат.
Во втором блоке 4 раза вызывается функция mkPF, создающая при каждом обращении один источник частиц. Кроме того, функция вводит в сцену конус, имитирующий излучатель частиц. С источником частиц этот конус никакой связи не имеет. Функция принимает позицию arrPs[k], углы поворота источника arrRt[k] и конуса arrRtCn[k], ссылки на объект с формой и на материал для частиц. (Углы поворота задаются относительно осей мировой системы координат.)
Оператор Birth обеспечивает рождение одной частицы каждые 5 кадров (свойство Rate = 5). Частицы генерируются на отрезке 0 – 350f. Время при задании свойств Emit_Start и Emit_Stop указывается в тиках: в одном кадре 160 тиков. (Число рождаемых частиц определяется в примере свойством Rate оператора Birth и свойствами оператора Position_Icon.)
Оператор Position_Icon указывает системе на необходимость генерировать частицы в одной точке – в базовой точке эмиттера (Location = 0).
Оператор Spin задает угловую скорость частиц и характер ее изменения.
Оператор Speed устанавливает скорость частицы (свойство Speed) и ее направление вдоль стрелки иконки эмиттера (Direction = 0).
Оператор Script_Operator содержит обработчики ChannelsUsed и Proceed, определяемые в свойстве Proceed_Script оператора.
Каждый обработчик получает в качестве параметра ссылку pCont на контейнер частиц системы. Благодаря этой ссылке мы получаем доступ к свойствам и методам контейнера в целом, а также возможность оперировать каждой частицей контейнера, читая и изменяя ее свойства, такие, как скорость, позиция, форма и др.
В обработчике ChannelsUsed указываются каналы обмена данными между обработчиками и контейнером частиц (активизируется канал передачи информации о позициях частиц, UsePosition = true). Второй обработчик (Proceed) постоянно вызывается в процессе работы системы частиц. В нашем случае в нем частица, Z-координата которой меньше нуля, покидает сцену (метод pCont.DeleteParticle k). Для доступа к частице с номером k в for-цикле выполняется присваивание pCont.ParticleIndex = k. Без этого выражения обработчик неработоспособен.
Оператор Material_Frequency задает материал частиц (свойство Assigned_Material): для каждой частицы ее материал случайным образом берется из 10 компонентом созданного ранее материала mlt. Вероятность выбора каждого компонента одинакова и равна 0.1 (Mtl_ID_1 = Mtl_ID_2 = … = Mtl_ID_10 = 10).
Третий блок создает пятый, расположенный в начале координат источник частиц. Частицы появляются в вершинах сферы lght (свойство Location оператора Position_Object равно 1) и с небольшой скоростью удаляются от сферы по радиальному направлению (свойство Direction оператора Speed равно 1). Назначение прочих операторов этого источника пояснено в ранее рассмотренных примерах.
При интерактивной реализации этого примера объем кодирования незначителен (см. свойство Proceed_Script оператора Script_Operator).
В следующем примере объем обязательного кодирования существенно выше.

Снеговик

В примере из частичек (снежинок) лепится снеговик (рис. 5).

Материал для снеговика поставляется четырьмя источниками частиц PF Source

Рис. 5. Сделан из снежинок

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

  1. Создаются 4 источника частиц в позициях [-100, 0, 100], [0, -100, 100], [100, 0, 100] и [0, 100, 100]. Как и ранее, частицы перенимают форму у введенного в сцену стандартного примитива Hedra. Для отображения эмиттера употребляется сфера, выводимая в позиции источника.
  2. Анимационный интервал (его длина 1000f) разбивается на 4 части: в первых трех по триста кадров, а в последнем 100.
  3. На первой части анимационного интервала частицы, покидающие эмиттер, направляются в центр нижней части снеговика, на второй – в центр средней части снеговика, на третьей – в центр верхней части снеговика. Эти действия выполняются за счет задания надлежащего вектора скорости частиц (свойство ParticleSpeed). Центр нижней части снеговика расположен в точке [0, 0, 0].
  4. Частица, приобретя вектор скорости, направляется в центр соответствующей части снеговика. После прохождения центра скорость частицы обнуляется, а сама частица помещается на поверхность соответствующей части фигуры (свойство ParticlePosition) и остается в этой позиции до конца анимации.
  5. На четвертой части анимационного интервала снеговик дополняется носом, глазами и шапкой.

Приведенную последовательность действий реализует следующий код:

-- Готовит сцену
fn prps = (
 delete $*
 sliderTime = 0f
 animationRange = interval 0f 1000f
 timeConfiguration.PlaybackLoop = false
 viewport.SetLayout #layout_4
 viewport.ActiveViewport = 4
 max vpt persp user
 viewport.SetGridVisibility 4 false
 backgroundColor = white
)
-- Создает при каждом обращении один источник частиц
fn mkPF ps snwFlk mlt scrpt = (
 local pF
 sph = sphere radius:2 pos:ps wireColor:white
 pF = PF_Source enable_Particles:true emitter_Type:0 \
  pos:ps quantity_Viewport:100 show_Logo:off show_Emitter:off
 particleFlow.BeginEdit()
 opBth = birth emit_Start:0 emit_Stop:(900 * 160) type:1 rate:6
 opPO = position_Object emitter_Objects:#(sph) location:0
 opSI = shape_Instance shape_Object:snwFlk
 opRt = rotation direction:0
 opSpn = spin rate:360 variation:0 direction:0
 opSO = script_Operator proceed_Script:scrpt
 opMFrq = material_Frequency assigned_Material:mlt \
  mtl_ID_1:10 mtl_ID_2:10 mtl_ID_3:10 mtl_ID_4:10 \
  mtl_ID_5:10 mtl_ID_6:10 mtl_ID_7:10 mtl_ID_8:10 \
  mtl_ID_9:10 mtl_ID_10:10 show_In_Viewport:on
 opDP = displayParticles type:6
 opRP = renderParticles type:2
 evn = event()
 particleFlow.EndEdit()
 evn.AppendAction opBth
 evn.AppendAction opPO
 evn.AppendAction opSI
 evn.AppendAction opRt
 evn.AppendAction opSpn
 evn.AppendAction opSO
 evn.AppendAction opMFrq
 evn.AppendAction opDP
 pF.AppendAction opRP
 pF.AppendInitialActionList evn
)
-- Block 1
prps()
-- Создаем материал mlt для частиц и объект Hedra, определяющий форму частиц
arrStd = for k = 1 to 10 collect \
 standard diffuse:[random 150 250, random 150 250, random 150 250] showInViewport:on
mlt = multimaterial numsubs:10 materialList:arrStd
hR = 2
hdr = hedra radius:hR family:3
hide hdr
global dt = 300 * 160
-- Начальные точки второй и третьей частей анимационного интервала
global tm2 = dt, tm3 = tm2 + dt
-- Радиусы r, r2 и r3 частей снеговика
global r = 10, r2 = 6, r3 = 3
-- Удаленность (d2 и d3) по оси Z центров второй и третьей частей снеговика от начала координат
global d2 = r + r2 + 2 * hR, d3 = d2 + r2 + r3 + 2 * hR
scrpt0 = "
 on channelsUsed pCont do (
  pCont.UsePosition = true
  pCont.UseSpeed = true
  pCont.UseAge = true
 )
"
scrpt = scrpt0 + "
 on proceed pCont do (
  nP = pCont.NumParticles()
  seed nP
  if nP > 0 then (
   pCont.ParticleIndex = 1
   sTm = pCont.ParticleAge as integer
  )
  else
   sTm = 0
   -- Значения векторов скорости частиц для каждой части анимационного интервала
  pSpd = [0.01, 0, -0.01]
  pSpd2 = [0.01, 0, -0.01 + d2 * 0.0001]
  pSpd3 = [0.01, 0, -0.01 + d3 * 0.0001]
  for k = 1 to nP do (
   pCont.ParticleIndex = k
   p = pCont.ParticlePosition
   -- Управляем скоростью частицы в зависимости от положения на временной оси
   case of (
    (sTm > tm3 and p[3] > 99): pCont.ParticleSpeed = pSpd3
    (sTm > tm2 and p[3] > 99): pCont.ParticleSpeed = pSpd2
    (p[3] > 99): pCont.ParticleSpeed = pSpd
   )
   if p[1] > 0 do (
    pSpd = pCont.ParticleSpeed
    case pSpd of (
     pSpd2: (rc = r2; z = d2)
     pSpd3: (rc = r3; z = d3)
     default: (rc = r; z = 0)
    )
    pCont.ParticleSpeed = [0, 0, 0]
    pNg = random [-90, 0] [90, 360]
    sn = rc * sin pNg[2]
    -- Помещаем частицу на поверхности снеговика
    pCont.ParticlePosition = [sn * cos pNg[1], sn * sin pNg[1], z + rc * cos pNg[2]]
   )
  )
)"
scrpt2 = scrpt0 + "
 on proceed pCont do (
  nP = pCont.NumParticles()
  seed nP
  if nP > 0 then (
   pCont.ParticleIndex = 1
   sTm = pCont.ParticleAge as integer
  )
  else
   sTm = 0
  pSpd = [0, 0.01, -0.01]
  pSpd2 = [0, 0.01, -0.01 + d2 * 0.0001]
  pSpd3 = [0, 0.01, -0.01 + d3 * 0.0001]
  for k = 1 to nP do (
   pCont.ParticleIndex = k
   p = pCont.ParticlePosition
   case of (
    (sTm > tm3 and p[3] > 99): pCont.ParticleSpeed = pSpd3
    (sTm > tm2 and p[3] > 99): pCont.ParticleSpeed = pSpd2
    (p[3] > 99): pCont.ParticleSpeed = pSpd
   )
   if p[2] > 0 do (
    pSpd = pCont.ParticleSpeed
    case pSpd of (
     pSpd2: (rc = r2; z = d2)
     pSpd3: (rc = r3; z = d3)
     default: (rc = r; z = 0)
    )
    pCont.ParticleSpeed = [0, 0, 0]
    pNg = random [-90, 0] [90, 360]
    sn = rc * sin pNg[2]
    pCont.ParticlePosition = [sn * cos pNg[1], sn * sin pNg[1], z + rc * cos pNg[2]]
   )
  )
)"
scrpt3 = scrpt0 + "
 on proceed pCont do (
  nP = pCont.NumParticles()
  seed nP
  if nP > 0 then (
   pCont.ParticleIndex = 1
   sTm = pCont.ParticleAge as integer
  )
  else
   sTm = 0
  pSpd = [-0.01, 0, -0.01]
  pSpd2 = [-0.01, 0, -0.01 + d2 * 0.0001]
  pSpd3 = [-0.01, 0, -0.01 + d3 * 0.0001]
  for k = 1 to nP do (
   pCont.ParticleIndex = k
   p = pCont.ParticlePosition
   case of (
    (sTm > tm3 and p[3] > 99): pCont.ParticleSpeed = pSpd3
    (sTm > tm2 and p[3] > 99): pCont.ParticleSpeed = pSpd2
    (p[3] > 99): pCont.ParticleSpeed = pSpd
   )
   if p[1] < 0 do (
    pSpd = pCont.ParticleSpeed
    case pSpd of (
     pSpd2: (rc = r2; z = d2)
     pSpd3: (rc = r3; z = d3)
     default: (rc = r; z = 0)
    )
    pCont.ParticleSpeed = [0, 0, 0]
    pNg = random [-90, 0] [90, 360]
    sn = rc * sin pNg[2]
    pCont.ParticlePosition = [sn * cos pNg[1], sn * sin pNg[1], z + rc * cos pNg[2]]
   )
  )
)"
scrpt4 = scrpt0 + "
 on proceed pCont do (
  nP = pCont.NumParticles()
  seed nP
  if nP > 0 then (
   pCont.ParticleIndex = 1
   sTm = pCont.ParticleAge as integer
  )
  else
   sTm = 0
  pSpd = [0, -0.01, -0.01]
  pSpd2 = [0, -0.01, -0.01 + d2 * 0.0001]
  pSpd3 = [0, -0.01, -0.01 + d3 * 0.0001]
  for k = 1 to nP do (
   pCont.ParticleIndex = k
   p = pCont.ParticlePosition
   case of (
    (sTm > tm3 and p[3] > 99): pCont.ParticleSpeed = pSpd3
    (sTm > tm2 and p[3] > 99): pCont.ParticleSpeed = pSpd2
    (p[3] > 99): pCont.ParticleSpeed = pSpd
   )
   if p[2] < 0 do (
    pSpd = pCont.ParticleSpeed
    case pSpd of (
     pSpd2: (rc = r2; z = d2)
     pSpd3: (rc = r3; z = d3)
     default: (rc = r; z = 0)
    )
    pCont.ParticleSpeed = [0, 0, 0]
    pNg = random [-90, 0] [90, 360]
    sn = rc * sin pNg[2]
    pCont.ParticlePosition = [sn * cos pNg[1], sn * sin pNg[1], z + rc * cos pNg[2]]
   )
  )
)"
-- Block 2
mkPF [-100, 0, 100] hdr mlt scrpt
mkPF [0, -100, 100] hdr mlt scrpt2
mkPF [100, 0, 100] hdr mlt scrpt3
mkPF [0, 100, 100] hdr mlt scrpt4
-- Block 3
-- Создаем в начале координат такие части снеговика, как нос, глаза и шапка
std = standard diffuse:red showInViewport:on
std2 = standard diffuse:black showInViewport:on
r32 = r3 + hR
-- Нос
cn = cone radius1:0.75 radius2:0 height:r32 material:std
rotate cn -90 [0, 1, 0]
-- Шапка
cn2 = cone radius1:(0.75 * r32) radius2:(0.5 * r32) height:(r32 + hR) material:std
-- Глаза
sp = sphere radius:0.5 material:std2
sp2 = copy sp
-- Перемещаем нос, глаза и шапку в надлежащие позиции на голове снеговика
animate on (
 at time 0 (
  cn.Pos = cn2.Pos = sp.Pos = sp2.Pos = [0, 0, 0]
  std.Opacity = std2.Opacity = 0
 )
 at time 900 (
  cn.Pos = cn2.Pos = sp.Pos = sp2.Pos = [0, 0, 0]
  std.Opacity = std2.Opacity = 0
 )
 at time 1000 (
  cn.Pos = [-r32, 0, d3]
  cn2.Pos = [0, 0, d3 + (0.75 * r32)]
  pNg = [-25, -75]
  sn = r32 * sin pNg[2]
  sp.Pos = [sn * cos pNg[1], sn * sin pNg[1], d3 + r32 * cos pNg[2]]
  sp2.Pos = [sn * cos pNg[1], -sn * sin pNg[1], d3 + r32 * cos pNg[2]]
  std.Opacity = std2.Opacity = 100
 )
)
max tool zoomExtents
playAnimation()

В основной программе три блока.
В первом блоке создаются материал mlt для частиц и объект Hedra, определяющий форму частиц. Далее задаются начальные точки второй и третьей частей анимационного интервала (tm2 и tm3). Длина каждой части равна dt (время указывается в тиках).
Вслед вводятся параметры сцены – это радиусы r, r2 и r3 частей снеговика, расстояния d2 и d3 по оси Z центров второй и третьей частей снеговика от начала координат (центр первой части снеговика расположен в начале мировой системы координат).
Далее записаны скрипты, используемые оператором Script_Operator систем частиц.
Все скрипты однотипны и различаются лишь значениями векторов скоростей и условиями торможения частиц.
Каждый скрипт начинается фрагментом scrpt0, в котором определяются каналы обмена данными между обработчиками и контейнером частиц (делаются доступными каналы Position, Speed и Age):

scrpt0 = "
 on channelsUsed pCont do (
  pCont.UsePosition = true
  pCont.UseSpeed = true
  pCont.UseAge = true
 )
"

К этому фрагменту присоединяется код proceed-обработчика. В каждом proceed-обработчике определяется число частиц nP источника, меняется затравка датчика случайных чисел (seed nP), вычисляется текущее время sTm в тиках (sTm берется равным возрасту первой частицы) и задаются значения векторов скорости частиц pSpd, pSpd2 и pSp3 для каждой части анимационного интервала.
В for-цикле обработчика каждой только что покинувшей эмиттер частице назначается вектор скорости. Факт близости частицы к эмиттеру устанавливается условием p[3] > 99, где p[3] – это Z-координата частицы (напомним, что Z-координата каждого источника частиц равна 100). Используемое значение pSpd, pSpd2 или pSp3 вектора скорости зависит от текущего времени: если sTm находится в первой части анимационного интервала, то берется pSpd, если – во второй, то берется pSpd2, если – в третьей, то берется pSpd3.
Далее в этом же цикле проверяется, достигла ли частица точку назначения (центр соответствующей части снеговика). Если да (в первом скрипте оценивается выражение p[1] > 0, где p[1] – это X-координата частицы), то определяется, к какой анимационной части относится частица (это выполняется по значению вектора скорости частицы). В результате для последующих вычислений определяются надлежащие значения переменных rc и z (радиуса соответствующий части снеговика и Z-координаты его центра). Скорость частицы обнуляется, и частица помещается на поверхность соответствующей сферы. Для нахождения позиции частицы используется параметрическое уравнение сферы (с радиусом R и с центром в точке [x0, y0, z0]):

Расчет позиций PF Source частиц в on proceed-обаботчике оператора Script_Operator

Значения углов тригонометрических функций этого уравнения определяются при помощи датчика случайных чисел.
Во втором блоке основной программы в результате обращений к функции mkPF создаются источники частиц. Все используемые в источниках операторы нам уже встречались.
В третьем блоке создаются и анимируются такие части снеговика, как нос, глаза и шапка. Первоначально они создаются в начале координат и делаются невидимыми за счет обнуления свойства Opacity (непрозрачность) каждого из примененных для этих частей материалов. К концу анимации каждая часть занимает положенное ей место на голове снеговика, а материалы std и std2 становятся совершенно непрозрачными (Opacity = 100).

Оптимизация кода

Число строк, потраченных на запись скриптов scrpt, scrpt2, scrpt3 и scrpt4 операторов Script_Operator, можно сократить, выделив совпадающие части в отдельные куски scrpt0, scrpt01, scrpt02 и scrpt03:

scrpt0 = "
 on channelsUsed pCont do (
  pCont.UsePosition = true
  pCont.UseSpeed = true
  pCont.UseAge = true
 )
"
scrpt01= "
 on proceed pCont do (
  nP = pCont.NumParticles()
  seed nP
  if nP > 0 then (
   pCont.ParticleIndex = 1
   sTm = pCont.ParticleAge as integer
  )
  else
   sTm = 0
"
scrpt02= "
  for k = 1 to nP do (
   pCont.ParticleIndex = k
   p = pCont.ParticlePosition
   case of (
    (sTm > tm3 and p[3] > 99): pCont.ParticleSpeed = pSpd3
    (sTm > tm2 and p[3] > 99): pCont.ParticleSpeed = pSpd2
    (p[3] > 99): pCont.ParticleSpeed = pSpd
   )
"
scrpt03= "
    pSpd = pCont.ParticleSpeed
    case pSpd of (
     pSpd2: (rc = r2; z = d2)
     pSpd3: (rc = r3; z = d3)
     default: (rc = r; z = 0)
    )
    pCont.ParticleSpeed = [0, 0, 0]
    pNg = random [-90, 0] [90, 360]
    sn = rc * sin pNg[2]
    pCont.ParticlePosition = [sn * cos pNg[1], sn * sin pNg[1], z + rc * cos pNg[2]]
   )
  )
)"
scrpt = scrpt0 + scrpt01 + "
  pSpd = [0.01, 0, -0.01]
  pSpd2 = [0.01, 0, -0.01 + d2 * 0.0001]
  pSpd3 = [0.01, 0, -0.01 + d3 * 0.0001]" + scrpt02 + "if p[1] > 0 do (" + scrpt03
scrpt2 = scrpt0 + scrpt01 + "
  pSpd = [0, 0.01, -0.01]
  pSpd2 = [0, 0.01, -0.01 + d2 * 0.0001]
  pSpd3 = [0, 0.01, -0.01 + d3 * 0.0001]" + scrpt02 + "if p[2] > 0 do (" + scrpt03
scrpt3 = scrpt0 + scrpt01 + "
  pSpd = [-0.01, 0, -0.01]
  pSpd2 = [-0.01, 0, -0.01 + d2 * 0.0001]
  pSpd3 = [-0.01, 0, -0.01 + d3 * 0.0001]" + scrpt02 + "if p[1] < 0 do (" + scrpt03
scrpt4 = scrpt0 + scrpt01 + "
  pSpd = [0, -0.01, -0.01]
  pSpd2 = [0, -0.01, -0.01 + d2 * 0.0001]
  pSpd3 = [0, -0.01, -0.01 + d3 * 0.0001]" + scrpt02 + "if p[2] < 0 do (" + scrpt03

Имитация пламени

Пламя воспроизводится посредством четырех систем частиц PF Source. Каждая имеет одно событие, одинаковые операторы. Системы различаются значениями параметров своих операторов.
Частицы генерируются на полусфере и перемещаются вверх под действием ветра.
Генерируемые частицы преобразовываются в Mesh посредством составного объекта BlobMesh, визуализация которого и обеспечивает результат (рис. 6).

PF Source-пламя с материалом RaytraceMaterial и Fog-картой Falloff

Рис. 6. BlobMesh-пламя на базе четырех PF Source

Цветовые характеристики BlobMesh обеспечиваются материалом RaytraceMaterial с Fog-картой Falloff.
Описанный подход известен и достаточно широко представлен в виде интерактивной реализации в Интернете и иных источниках. Здесь же приводится MAXScript-решение описанной задачи.
Использованы следующие операторы:

Параметры операторов записаны в массивы.
Операторы ScaleParticles, Position_Object и DisplayParticles одинаковы в каждом источнике частиц.
Первый источник генерирует наибольшее число частиц и предназначен для получения основной массы пламени, второй – большое пламя, третий – малое пламя, а последний – куски пламени. Вклад каждого источника приведен на рис. 7.

PF Source: пламя образуется одновременным действием всех источников частиц

Рис. 7. Вклады в результат различных источников частиц

Результат обеспечивает следующий код:

delete $*
-- Установки единиц измерения
max unitSetup
units.DisplayType = #Generic
units.SystemType = #Inches
-- Длина анимационного интервала
aRng = 100
sliderTime = 0f
animationRange = interval 0f aRng
timeConfiguration.PlaybackLoop = false
-- Формируем карту и материал
flf = falloff color1:yellow color2:(color 255 100 10)
rtr = raytraceMaterial transparecy:white index_of_Refraction:1 specular_Level:0 glossiness:0 \
 fog_Enable:on Fog_Start:0 fog_End:100 fog_Amount:15 fogColorMap:flf
-- Добавляем карту диффузионной компоненте материала
rtr.DiffuseMap = flf
rtr.DiffuseMapEnable = true
rtr.DiffuseMapAmount = 10
meditMaterials[1] = rtr
-- Формируем составной объект BlobMesh и назначаем ему созданный выше матери-ал
blb = blobMesh tension:0.5 renderCoarseness:5 material:rtr
-- Полусфера. Используется в качестве поверхности излучения частиц
sph = sphere radius:20 segs:32 hemiSphere:0.5 isHidden:on
-- Ветер, приводящий частицы в движение
wnd = wind strength:0.1 decay:0 turbulence:0.15 frequency:0.15
-- Массивы с параметрами операторов систем частиц
-- Birth: Amount (Emit Start = 0, Emit Stop = aRng)
arrBPr = #(3000, 600, 500, 500)
-- Shape: Size (Shape = Tetra)
arrShpPr = #(2.5, 2.5, 3.5, 3)
-- Force: Influence (SyncBy = Particle Age)
arrFPr = #(600, 800, 700, 850)
-- DeleteParticles: Life Span, Variation (Remove = By Particle Age)
arrDPr = #(#(20, 4), #(30, 6), #(20, 4), #(30, 6))
-- Keep_Apart: Force, Accel Limit, Range = Absolute Size, Core Radius, Fallof Zone
arrKPPr = #(#(-0.3, 1000, 5, 5), #(-0.3, 1000, 2, 2), #(-0.5, 200, 1.5, 1),#(-0.01, 500, 1, 1))
-- Создаем 4 источника частиц
for k = 1 to 4 do (
 pF = PF_Source enable_Particles:true quantity_Viewport:100 isHidden:off
 particleFlow.BeginEdit()
 -- Формируем операторы текущего события, беря их параметры из массивов
 opBth = birth emit_Start:0 emit_Stop:(160 * aRng) amount:arrBPr[k]
 opShp = shapeStandard shape:0 size:arrShpPr[k]
 opScl = scaleParticles constrain_Scale:off X_Scale_Factor:75 Y_Scale_Factor:75 Z_Scale_Factor:200
 opPO = position_Object emitter_Objects:#(sph) location:2 \
  apart_Placement:on apart_Distance:8
 opFrc = force force_Space_Warps:#(wnd) influence:arrFPr[k] sync:1
 opDlt = deleteParticles type:2 life_Span:(160 * arrDPr[k][1]) variation:(160 * arrDPr[k][2])
 -- Scope_Type = 0 (Current Event) (Scope_Type = 3; Selected Particle Systems - may be used)
 opKP = keep_Apart force:arrKPPr[k][1] accel_Limit:arrKPPr[k][2] core_Size:arrKPPr[k][3] \
  falloff_Size:arrKPPr[k][4] scope_Type:0 -- selected_Systems:#(pF.Handle)
 opDP = displayParticles type:6 -- 2 - Ticks; color:red
 evn = event()
 particleFlow.EndEdit()
 evn.AppendAction opBth
 evn.AppendAction opShp
 evn.AppendAction opScl
 evn.AppendAction opPO
 evn.AppendAction opFrc
 evn.AppendAction opDlt
 evn.AppendAction opKP
 evn.AppendAction opDP
 pF.AppendInitialActionList evn
 -- Добавляем в качестве компонента составного объекта текущую систему частиц
 blb.BlobMeshOps.AddBlob pF
)
-- Задаем параметры воспроизведения
backgroundColor = gray
fNm = "g:/flm.avi"
-- Генерируем 70-й кадр результата
render frame:70 outputSize:[320, 240] outputFile:fNm
-- render fromframe:0 toframe:100 outputSize:[160, 120] outputFile:fNm

В некоторых случаях результат можно улучшить, если употребить с BlobMesh следующие модификаторы:

cp = cap_Holes()
addModifier blb cp
tb = turboSmooth iteration:1
addModifier blb tb
rlx = relax relax_value:0.5 iterations:1 keep_Boundary_Pts_Fixed:0
addModifier blb rlx

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

Имитация дыма

В примере частицы замещаются сферическими помощниками SphereGizmo, которые благодаря атмосферному эффекту Fire_Effect позволяют воспроизвести дым. Для смещения частиц и, следовательно, дыма в сцену введен ветер Wind, связанный с системой частиц посредством оператора Force.
Частицы, возраст которых более 4000 тиков, удаляются со сцены.

animationRange = interval 0f 100f
sliderTime = 0f
delete $*
while numAtmospherics > 0 do deleteAtmospheric 1
while numEffects > 0 do deleteEffect 1
spG = sphereGizmo radius:5 pos:[15, -40, 0]
fE = fire_Effect inner_Color:(color 100 100 100) outer_Color:(color 100 100 100)
appendGizmo fE spG
addAtmospheric fE
for k = 1 to 24 do maxOps.CloneNodes spG
-- Array of SphereGizmo objects
global arrSpG = $SphereGizmo*
qt = eulerToQuat (eulerAngles 0 90 0)
wnd = wind strength:0.05 turbulence:0.5 decay:0 pos:[30, 0, 0] frequency:15 rotation:qt
qt = eulerToQuat (eulerAngles 180 0 0)
pF = PF_Source enable_Particles:true \
 quantity_Viewport:100 rotation:qt pos:[0, 0, -40]
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:12000 amount:60
opPI = position_Icon distinct_Points_Only:on total_Distinct_Points:1
opSpd = speed speed:100
opRP = renderParticles()
opFrc = force force_Space_Warps:#(wnd)
opSO = script_Operator proceed_Script:"
on channelsUsed pCont do (
 pCont.UseTime = true
 pCont.UseTM = true
 pCont.UseAge = true
)
on proceed pCont do (
 nP = pCont.NumParticles()
 cnt = amin #(nP, arrSpG.Count)
 for k = 1 to cnt do (
  pCont.ParticleIndex = k
  pAg = pCont.ParticleAge as float
  pAg /= 160.0
  rSpG = 0.8 * pAg + 10
  arrSpG[k].Transform = pCont.ParticleTM
  arrSpG[k].Radius = rSpG
 )
)"
-- In ticks
tstAT = age_Test test_Value:4000 variation:0
evn = event()
particleFlow.EndEdit()
evn.AppendAction opBth
evn.AppendAction opPI
evn.AppendAction opSpd
evn.AppendAction opFrc
evn.AppendAction opSO
evn.AppendAction tstAT
pF.AppendAction opRP
pF.AppendInitialActionList evn
--
-- Event 2
opDlt = deleteParticles type:0
evn2 = event()
particleFlow.EndEdit()
evn2.AppendAction opDlt
tstAT.SetNextActionList evn2 tstAT
max tool zoomExtents
backgroundColor = black
renderWidth = 320; renderHeight = 240
render frameRange:(interval 0f 20f)
-- To restore the scene
-- arrSpG.Pos = [15, -40, 0]; arrSpG.Radius = 5

Один кадр анимации приведен на рис. 8.

24 SphereGizmo с Fire_Effect в  PF Source для создания дыма

Рис. 8. Помощники SphereGizmo в окне воспроизведения и видовом порте (t = 40)

Заключение

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

Источники

  1. Autodesk® 3ds Max® 2009 MAXScript Reference.
  2. Бартеньев О. В. Программирование модификаторов 3ds Max. Учебно-справочное пособие. – М.:Физматкнига, 2009. – 341 с.
  3. Он же. Программирование систем частиц. – Учебно-справочное пособие. – М.:МЭИ, 2009. – 235 с.

Список примеров

Рейтинг@Mail.ru