Из частиц создаются снеговик и другие фигуры. Для получения результата использованы система частиц PF Source (поток частиц) и язык программирования MAXScript.
Системы частиц позволяют моделировать явления реального мира и воплощать разного рода вымышленные образы.
MAXScript не только предоставляет дополнительные возможности при работе с системами частиц, но и укоряет процесс получения результата. Кроме того, код компактно описывает моделируемую сцену, что весьма часто позволяет оценить генерируемые им образы, не имея под рукой 3ds Max.
Эти и иные преимущества будут ощутимы при наличии определенных навыков и достаточно продолжительной тренировки.
В качестве иллюстрации тезиса о компактности представления модели приведем код, обеспечивающей решение следующей задачи:
Получение результата обеспечивает следующий код:
-- 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.
Рис. 1. Частицы на ребрах текста
В основной программе три блока.
Первый блок подготовительный. Он очищает сцену, создает Mesh в виде выдавленного текста t и цилиндра cl, форма которого будет затем использована для отображения частиц.
Второй блок отвечает за создание источника частиц. Источник содержит одно событие с 6-ю операторами (рис. 2).
Рис. 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 в следующем порядке:
Реализуется следующая сцена: четыре потока частиц в виде разноцветных снежинок пересекаются в одной точке и порождают сферический источник снежинок, медленно удаляющихся от его центра (рис. 3).
Рис. 3. Большая снежинка
Задача решается по следующей схеме:
Рис. 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).
Рис. 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]):
Значения углов тригонометрических функций этого уравнения определяются при помощи датчика случайных чисел.
Во втором блоке основной программы в результате обращений к функции 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).
Рис. 6. BlobMesh-пламя на базе четырех PF Source
Цветовые характеристики BlobMesh обеспечиваются материалом RaytraceMaterial с Fog-картой Falloff.
Описанный подход известен и достаточно широко представлен в виде интерактивной реализации в Интернете и иных источниках. Здесь же приводится MAXScript-решение описанной задачи.
Использованы следующие операторы:
Параметры операторов записаны в массивы.
Операторы ScaleParticles, Position_Object и DisplayParticles одинаковы в каждом источнике частиц.
Первый источник генерирует наибольшее число частиц и предназначен для получения основной массы пламени, второй – большое пламя, третий – малое пламя, а последний – куски пламени. Вклад каждого источника приведен на рис. 7.
Рис. 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.
Рис. 8. Помощники SphereGizmo в окне воспроизведения и видовом порте (t = 40)
Большое число частиц и связанные с ними вычисления нередко требуют существенных вычислительных ресурсов. В таких случаях отладку решений следует выполнять на малом числе частиц и небольших анимационных интервалах. Кроме того, желательно наметить несколько путей решения задачи и выбрать среди них наименее ресурсоемкий.