Рассматриваются механизмы управления частицами средствами 3ds Max и языка программирования MAXSript. Для освоения рассматриваемых механизмов не требуется навыков работы с 3ds Max и MAXSript. Желательно, однако, иметь некоторый опыт программирования на любом языке высокого уровня.
Управлять частицами – это в каждый момент времени соответствующим образом регулировать количество частиц в сцене, изменять их координаты, скорость, форму, цвет и прочие свойства частиц. Все средства, необходимые для решения указанных задач, имеются.
Дополнительно освещаются методы работы с материалами и картами цветов, силами и отражателями. Так же даются сведения о механизме анимации координат и параметров объектов и о некоторых модификаторах приложения.
Рассматриваемые системы частиц позволяют, в частности, воспроизводить различные природные явления, например огонь, дым, снег, дождь или песчаную бурю, анимировать различные процессы, например разрушение предмета на фрагменты или его сборку из уже имеющихся фрагментов (обратный разрушению процесс).
При работе с системами частиц будем придерживаться приведенных на рис. 1.1 единиц измерения.
Рис. 1.1. Установка единиц измерения
Приведенное на рис. 1.1 окно Units Setup открывается в меню Customize – Units Setup, а окно System Unit Setup – после нажатия на одноименную кнопку в окне Units Setup.
Установки единиц измерения обеспечиваются следующим кодом:
max unitsetup
units.DisplayType = #Generic
units.SystemType = #Inches
Конфигурация видовых портов определяется следующим кодом:
viewport.SetLayout #layout_4
viewport.ActiveViewport = 4
viewport.SetType #view_persp_user -- Or: max vpt persp user
Настройки воспроизведения анимации устанавливаются в соответствии с рис. 1.2.
Рис. 1.2. Окно Time Configuration
Окно Time Configuration открывается при нажатии на кнопку нижней панели инструментов.
Приведенные установки обеспечит следующий код:
Система Particle Flow (поток частиц) обладает наиболее широкими возможностями управления частицами по сравнению с иными имеющимися в 3ds Max системами, такими, как Snow (снег), Spray (распылитель), Blizzard (вьюга, пурга, буран) и др.
Управление частицами реализуется в виде последовательности связанных событий. Каждое событие содержит некоторый набор операторов и тестов, влияющих на поведение частиц и способ их отображения.
По умолчанию частицы генерируются в области расположении эмиттера объекта PF_Source, вводимого в сцену либо интерактивно после выбора на командной панели - Particle Flow, либо программно следующим конструктором:
pF = PF_Source()
В конструкторе или отдельными выражениями могут быть определены значения свойств объекта, например:
Для неуказанных свойств берутся заданные по умолчанию значения.
Эмиттер отображается, если свойство Show_Emitter объекта PF_Source имеет значение true. Наряду с эмиттером PF_Source содержит логотип системы Particle Flow, который будет виден (рис. 1.3), если значение true присвоено свойству Show_Logo:
pF.Show_Emitter = true
pF.Show_Logo = true
Рис. 1.3. Эмиттер (внешний прямоугольник) и логотип системы Particle Flow
Объект PF_Source может содержать операторы и тесты, действие которых распространяется на все события системы.
Число вводимых в сцену PF_Source-объектов произвольно.
Состав каждой системы частиц и связи между ее событиями отображаются в форме Particle View, открываемой после выбора системы либо интерактивно (– список Setup – кнопка Particle View), либо программно:
particleFlow.OpenParticleView()
Созданная интерактивно система частиц имеет название Standard Flow, содержит объект PF_Source и одно событие со стандартным набором операторов и представляется в форме Particle View приведенными на рис. 1.4 образами.
Рис. 1.4. Структурная схема создаваемой по умолчанию системы Particle Flow
Программно Standard Flow вводится в сцену следующим кодом:
Код набирается в MAXScript-редакторе и запускается после нажатия на Ctrl+E.
Первая строка кода (delete $*), очищает сцену, а его три последние строки позиционируют надлежащим образом компоненты структурной схемы системы Particle Flow и открывают форму Particle View. Локальная система координат объекта pF совпадает с мировой, а вектор скорости генерируемых частиц направлен по оси –Z.
Метод AppendAction устанавливает принадлежность оператора событию evn или объекту pF. В случае Standard Flow объекту pF принадлежит только один оператор opRP, отвечающий за воспроизведение (rendering) частиц.
Операторы и тесты вводятся соответствующими конструкторами. Так, оператор opBth, обеспечивающий генерацию частиц в созданной системе, введен конструктором Birth.
Время начала и завершения генерации частиц (соответственно свойства Emit_Start и Emit_Stop оператора opBth) задается в тиках. По умолчанию в одном кадре временной шкалы 160 тиков.
Замечание. 4800 тиков составляют 1 секунду, то есть по умолчанию за одну секунду проигрывается 30 кадров. Это значение отображается в поле FPS (frames per second) окна Time Configuration, открываемого при нажатии на кнопку нижней панели инструментов.
Кроме этих свойств, конструктор Birth задает значение свойства Amount, устанавливающее общее число генерируемых частиц.
После нажатия на кнопку Play Animation частицы наблюдаются в активном видовом порте (рис. 1.5).
Рис. 1.5. Частицы созданной по умолчанию системы Particle Flow при t = 10f
Программно анимация выполняется в видовом порте после обращения к следующему методу:
playAnimation()
Число имеющихся в системе частиц возвращается методом NumParticles. В частности для рассматриваемой системы на десятом кадре анимации метод вернет число 34:
pF.NumParticles() -- 34
Для позиционирования на 10-м кадре временной шкалы можно выполнить
sliderTime = 10f
В окне воспроизведения результат наблюдается после вызова метода Render, например:
render frameRange:(interval 0f 30f)
Последние 4 строки кода можно набрать в MAXScript Listener. Для выполнения кода курсор помещается на строку с кодом и нажимается Shift+Enter либо маленькая клавиша Enter (если таковая имеется).
Наблюдаемые в окне воспроизведения частицы будут иметь форму пирамиды (рис. 1.6.), поскольку свойство '3D_Type' оператора ShapeLibrary равно 18.
Рис. 1.6. Частицы Standard Flow в окне воспроизведения
По умолчанию свойство Quantity_Viewport = 50. То есть при просмотре анимации в видовом порте генерируется половина от числа частиц, которые окажутся в видовом порте в заданный момент времени, если Quantity_Viewport = 100. В рассматриваемом случае в точке t = 10f при Quantity_Viewport = 100 метод NumParticles вернет число 67.
Для сокращения кода, приводимого в пособии, следующая часть кода
evn = event()
evn.AppendAction opBth
evn.AppendAction opPI
evn.AppendAction opSpd
evn.AppendAction opRt
evn.AppendAction opShS
evn.AppendAction opDP
pF.AppendAction opRP
particleFlow.EndEdit()
pF.AppendInitialActionList evn
оформляется в виде функции
function apActFn pF arrOps hasRP = (
local evn
evn = event()
arrCnt = if hasRP then arrOps.Count - 1 else arrOps.Count
for k = 1 to arrCnt do evn.AppendAction arrOps[k]
if hasRP do pF.AppendAction arrOps[arrCnt + 1]
particleFlow.EndEdit()
pF.AppendInitialActionList evn
return evn
)
Функция принимает ссылку на объект PF_Source, массив операторов и тестов и логическую переменную hasRP, равную true, если имеется оператор opRP, или false – в противном случае. Состав массива arrOps может меняться. В рассматриваемом примере массив определяется следующим образом:
arrOps = #(opBth, opPI, opSpd, opRt, opShS, opDP, opRP)
Оператор opRP, если он определен в коде, всегда должен завершать массив arrOps.
Таким образом, вышеприведенный код создания системы Standard Flow после введенных изменения будет следующим:
function apActFn pF arrOps hasRP = (
local evn
evn = event()
arrCnt = if hasRP then arrOps.Count - 1 else arrOps.Count
for k = 1 to arrCnt do evn.AppendAction arrOps[k]
if hasRP do pF.AppendAction arrOps[arrCnt + 1]
particleFlow.EndEdit()
pF.AppendInitialActionList evn
return evn
)
--
delete $*
--
pF = PF_Source enable_Particles:true quantity_Viewport:100
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:4800 amount:200
opPI = position_Icon location:4
opSpd = speed speed:300 direction:0
opRt = rotation direction:0
opShS = shapeStandard shape:0
opDP = displayParticles color:pF.WireColor type:2
opRP = renderParticles type:2
--
apActFn pF #(opBth, opPI, opSpd, opRt, opShS, opDP, opRP) true
--
pF.SetPViewLocation 0 0
evn. SetPViewLocation 0 100
particleFlow.OpenParticleView()
В дальнейшем код функции apActFn будет опускаться. Однако при запуске примеров, имеющих обращения к этой функции, она должна быть доступна.
Осуществляется в скриптах Every Step Update и Final Step Update и операторами Birth_Script, Script_Operator и Script_Test.
В операторах код записывается как значение свойства Proceed_Script и имеет в общем случае следующие процедуры-обработчики событий: ChannelsUsed, Init, Proceed и Release.
Все процедуры принимают один параметр, по умолчанию имеющий имя pCont (Particle Container) и обеспечивающий доступ к свойствам и методам генерируемых частиц.
Оператор Birth_Script может быть добавлен только в первое событие системы, Причем недопустимо одновременное присутствие в событии операторов Birth и Birth_Script.
Пример. Обеспечить генерацию частиц, имеющих форму цилиндра или конуса, назначив цилиндрическую форму частицам с нечетными номерами, а коническую форму с четными.
delete $*
cl = cylinder radius:5 height:7
cn = cone radius1:5 radius2:0 height:7
hide #(cl, cn)
pF = PF_Source enable_Particles:true quantity_Viewport:100
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:4800 amount:50
opPI = position_Icon location:4
opSpd = speed speed:50 direction:3 -- 3D
opDP = displayParticles color:(color 75 75 75) type:6
opSO = script_Operator proceed_Script:"
on channelsUsed pCont do (
pCont.UseTime = true
pCont.UseShape = true
)
on proceed pCont do (
nP = pCont.NumParticles()
for k = 1 to nP do (
pCont.ParticleIndex = k
if pCont.ParticleNew then
pCont.ParticleShape = if mod k 2 == 1 then cl.Mesh else cn.Mesh
)
)"
apActFn pF #(opBth, opPI, opSpd, opDP, opSO) false
playAnimation()
Решение (рис. 1.7) обеспечивает введение оператора opSO, в proceed-обработчике которого каждой новой частице назначается соответствующая форма. В качестве источника формы берется TriMesh цилиндра и конуса, вводимых в сцену и скрываемых начальными строчками кода.
Рис. 1.7. Система, порождающая частицы двух форм: а - структурная схема; б - сцена при t = 30f
В нижеприводимом коде вычисляется число вызовов различных обработчиков оператора Script_Operator. Результат выводится после завершения анимации в интервале 0f - 100f. Так же печатается общее число частиц в системе в последней точке анимационного интервала.
delete $*
global kCh = kI = kP = kR = 0
pF = PF_Source enable_Particles:true quantity_Viewport:100
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_End:0 amount:100
opPI = position_Icon()
opSpd = speed()
opShS = shapeStandard()
opDP= displayParticles color:pF.WireColor
opSO = script_Operator proceed_Script:"
on channelsUsed pCont do (pCont.UseTime; kCh += 1)
on init pCont do kI += 1
on proceed pCont do kP += 1
on release pCont do kR += 1"
apActFn pF #(opBth, opPI, opSpd, opDP, opShS, opSO) false
animationRange = interval 0f 100f
timeConfiguration.PlaybackLoop = false
playAnimation()
kCh -- 1
kI -- 1
kP -- 500
kR -- 0
pF.NumParticles() -- 100
pF.NumParticlesGenerated() -- 100
sliderTime = 0f
Задание. Вывести аналогичные данные для скриптов Every Step Update и Final Step Update и обработчиков операторов Birth_Script и Script_Test.
Каналы управления частицами указываются в обработчике ChannelsUsed свойства Proceed_Script операторов Birth_Script, Script_Operator и Script_Test. По каналам, получившим статус true, можно получать и изменять значения соответствующих свойств частиц. Могут быть использованы следующие каналы:
on channelsUsed pCont do (
pCont.UseTime = true -- Активизируется канал Time
pCont.UseSpeed = true -- Активизируется канал Speed
pCont.UseInteger = true -- Активизируется канал Integer
)
Операторы, управляющие частицами, разделяются на следующие категории:
Генерация и удаление (Birth, BirthScript и DeleteParticles).
Преобразование координат (Position_Icon, Position_Object, Rotation, ScaleParticles и Spin).
Скорость (Keep_Apart, Speed, Speed_By_Surface и SpeedByIcon).
Форма (ShapeStandard, Shape_Facing, Shape_Instance и Shape_Mark).
Материал (Mapping, Material_Dynamic, Material_Frequency и Material_Static).
Иные (Force и Script_Operator).
Метаоператоры (Cache, Notes, Event, RenderParticles и DisplayParticles).
Кроме операторов, поведение частиц регулируется тестами, например Age_Test (тест возраста) или Collision (столкновение). Частицы, успешно завершившие тест, могут быть переданы следующему событию.
В форме Particle View оператор или тест можно выбрать на складе и перетащить мышкой в подходящую строку нужного события.
Объекты и тесты системы частиц принадлежат классу Helper, что устанавливается следующим кодом:
Оператор Position_Icon используется для управления начальным размещением частиц на эмиттере. Эмиттер может принимать прямоугольника, прямоугольного параллелепипеда, окружности или сферы (свойство Emitter_Type объекта PF_Source).
Частицы могут рождаться на поверхности эмиттера, в его объеме, на ребрах или вершинах эмиттера или в его центре (свойство Location оператора Position_Icon). Кроме того, можно точно указать число точек, в которых будут генерироваться частицы (свойство Total_Distinct_Points оператора Position_Icon). Такой режим будет использован, если установить в on свойство Distinct_Points_Only оператора Position_Icon.
В примере частицы генерируются из одной точки эмиттера, имеющего форму окружности, и направляются вертикально вниз. После соприкосновения с плоскостью отражателя POmniFlect они останавливаются.
Введенный в сцену отражатель указывается в свойстве Collision_Nodes теста Collision, присутствующего в первом событии системы частиц. Для остановки частиц, достигших отражателя, свойство Speed_Option в тесте Collision устанавливается равным 2 (Speed Stop).
После достижения отражателя частицы передаются второму событию, в котором изменяется их форма и цвет.
Выполняемая анимация координат объекта PF_Source обеспечивает расположение частиц на плоскости в виде буквы А.
Аналогичный результат можно получить, если в предыдущем примере вместо теста Collision применить оператор Shape_Mark, в котором задается контактный объект. Частицы, достигшие этого объекта, останавливаются и отображаются в виде маркеров, заданных оператором Shape_Mark. Этот способ будет употреблен, если в операторе DisplayParticles свойство Type = 6 (Geometry). В противном случае вид маркера будет определяться оператором DisplayParticles.
fn fEmSM h = (
animationRange = interval 0f 100f
messageBox("Emitter motion and Shape_Mark")
rAng = (eulerAngles 0 10 0) as quat
for k = 6 to 9 by 3 do (
delete $*
pln = plane length:90 width:80 pos:[0, 0, 0] wireColor:gray
pF = PF_Source enable_Particles:true emitter_Type:2 \
pos:[0, 0, 40] quantity_Viewport:100
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:16000 amount:80
opPI = position_Icon distinct_Points_Only:on total_Distinct_Points:1
opShS = shapeStandard()
opSpd = speed speed:50
opDP = displayParticles color:red type:k
opShM = shape_Mark contact_Object:pln align_To:0 \
size_Space:0 length:10 width:10
apActFn pF #(opBth, opPI, opShS, opSpd, opDP, opShM) false
animate on (
at time 0f pF.Pos = [-25, -30, 0]
at time 20f pF.Pos = [0, 30, 0]
at time 40f pF.Pos = [25, -30, 0]
at time 50f pF.Pos = [15, -7, 0]
at time 60f pF.Pos = [-15, -7, 0]
)
playAnimation()
)
)
fEmSM h 1
На рис. 1.10. приведен результат с различными значениями свойства Type оператора DisplayParticles.
Рис. 1.10. Представление частиц системы: а – type = 6 (Geometry); б – type = 9 (Asterisks)
По умолчанию эмиттером частиц является иконка системы частиц Particle Flow. Оператор Position_Object позволяет указать объект сцены в качестве иного эмиттера частиц. При этом частицы могут генерироваться любой точкой поверхности или объема объекта либо ребрами, либо вершинами, либо базовой точкой, либо выборкой подобъектов. Также генерация частиц может управляться примененным к объекту материалом.
В примере в качестве эмиттера частиц берется сфера, размеры которой в процессе анимации уменьшаются во времени, и вдобавок сфера перемещается по оси X. Рожденные частицы падают вниз и останавливаются на плоскости, указанной в тесте Collision. Частицы останавливаются, поскольку в тесте свойство Speed_Option = 2 (Speed Stop).
Для сокращения кода опущены операторы Rotation и RenderParticles. Оператор Position_Icon заменен оператором Position_Object.
Заданные свойства оператора обеспечивают генерацию частиц из 10 точек поверхности, а установленный флажок animated_Shape обеспечивает изменение координат этих точек при изменении размера сферы (эмиттера). Успокоившиеся на плоскости частицы системы показаны на рис. 1.11, а структурная схема системы - на рис. 1.12.
Рис. 1.11. Система при t = 90f
Рис. 1.12. Структурная схема системы с Position_Object
В случае задания оператором Position_Object нескольких эмиттеров частицы распределяются между эмиттерами пропорционально их объемам.
В следующем примере в качестве эмиттеров указываются две сферы разных радиусов. Как и ранее сгенерированные частицы падают на плоскость отражателя POmniFlect и останавливаются на этой плоскости.
Свойство Surface_Objects оператора Speed_By_Surface определяется значением массива объектов, перемещение которых взывает воздействие на вектор скорости частиц. При этом характер воздействия зависит как от траектории движения объектов, так и от положения их базовых точек.
В примере в качестве действующего на систему частиц объекта употребляется цилиндр, перемещающийся по эллипсу. Все частицы генерируются в одной точке эмиттера (свойство Distinct_Points_Only установлено в on, свойство Total_Distinct_Points = 1). Перемещение объекта обусловливает спиралевидную траекторию движения частиц, смещаемую вниз по оси Z (рис. 1.14, а), если уменьшена Z-координата базовой точки цилиндра (k = 2), или вверх по оси Z (рис. 1.14, б) – в противном случае (k = 3).
Рис. 1.14. Оператор Speed_By_Surface: а - базовая точка объекта смещена вниз; б - базовая точка объекта смещена вверх
Тест Find_Target отображается в сцене в виде иконки, которая используется, если не задано свойство Target_Objects, в качестве цели.
По умолчанию Find_Target направляет частицы к указанной цели или нескольким целям. Частицы, достигшие цели, можно передать другому событию.
Также Find_Target можно использовать для выделения частиц, находящихся от центра цели на некотором расстоянии, с последующей их переадресовкой другому событию. В этом случае свойство Speed_Type теста следует установить равным 2 (No Control). При таком варианте использования цель не притягивает частицы системы.
В первом примере в тесте Find_Target свойство Speed_Type = 0 (Control By Speed), а Use_Cruise_Speed = on, поэтому частицы после генерации приобретают скорость, величина которой определяется свойством Cruise_Speed теста. При этом оператор Speed, если он имеется, формирует начальные векторы скорости частиц, ориентируемые затем по направлению к цели, в качестве которой использована иконка помощника Find_Target.
Рис. 1.15. Для достижения цели используется скорость, заданная свойством Cruise_Speed теста Find_Target
В следующем примере в тесте Find_Target свойство Speed_Type = 1 (Control By Time), поэтому частицы после генерации приобретают скорость, величина и направление которой определяются оператором Speed (соответственно свойства Speed и Direction). Затем частицы направляются к цели, их скорость (величина и направление) меняется таким образом, что они достигают цель, когда время частицы в событии (Timing_Type = 2) равно значению свойства Time (в коде задается в тиках) теста Find_Target. При ненулевом значении свойства Time_Variation время достижения частицей цели берется случайным образом из диапазона Time ? Time_Variation (в коде эти величины задаются в тиках).
fn fFt2 h = (
animationRange = interval 0f 100f
msg = "Find_Target: Time "
for k = 1 to 4 do (
addMsg = \
case k of (
1 : ""
2 : "(Docking_Speed = 0)"
3 : "(two events: Speed = 0)"
4 : "(two events: Speed Random Horizontal)"
)
messageBox(msg + addMsg)
delete $*
pF = PF_Source enable_Particles:true quantity_Viewport:100 pos:[-40, 0, 20]
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:9600 amount:300
opPI = position_Icon()
opSpd = speed speed:100 direction:0
opDP = displayParticles color:black type:2
if k != 2 then
tstFT = find_Target speed_Type:1 test_Type:1 \
time:9600 time_Variation:800 pos:[40, 0, 0]
else
tstFT = find_Target speed_Type:1 test_Type:1 \
time:9600 time_Variation:800 pos:[40, 0, 0] \
use_Docking_Speed:on docking_Speed:0
apActFn pF #(opBth, opPI, opSpd, opDP, tstFT) false
if k > 2 then (
--
-- Event 2
opDP2 = displayParticles color:red type:2
if k == 3 then
opSpd2 = speed speed:0
else
opSpd2 = speed speed:200 direction:4 -- Random Horizontal
evn2 = apActFn pF #(opDP2, opSpd2) false
tstFT.SetNextActionList evn2 tstFT
)
playAnimation()
)
)
fFt2 1
Результат для k = 1 приведен на рис. 1.16.
Рис. 1.16. Частицы при t = 60f
После достижения цели частицы, если они остаются в текущем событии, продолжают движение, направление и скорость которого определяются вектором скорости частицы. Для изменения поведения частиц после достижения цели их можно передать следующему соответствующим образом оформленному событию.
В следующем примере частицы, достигшие цель, останавливаются. Это обеспечивается вторым событием, в котором имеется оператор Speed, содержащий нулевое значение скорости. Вдобавок частицы приобретают красный цвет. Разобранный пример реализуется вышеприведенным кодом при k = 3.
Один кадр анимации приведен на рис. 1.17.
Рис. 1.17. Система при t = 80f
Задание. Направить частицы после достижения цели в произвольном горизонтальном направлении. Решение (в коде реализуется при k = 4):
opSpd2 = speed speed:200 direction:4 -- Random Horizontal
Если нужно, чтобы частицы прибывали к цели с заданной скоростью, то свойство Use_Docking_Speed устанавливается on. Сама же скорость прибытия задается свойством Docking_Speed. При ненулевом свойстве Docking_Speed_Variation скорость достижения цели случайным образом выбирается из диапазона Docking_Speed ? Docking_Speed_Variation. При установленном флажке приложение находит такой путь частиц до цели, двигаясь по которому они имеют наименьшее ускорение.
Учитывая описанные возможности свойство, остановить частицы на поверхности цели можно, не передавая их второму событию, употребив взамен нулевую скорость прибытия. Такой вариант остановки частиц реализуется вышеприведенным кодом при k= 2.
Совместное употребление оператора SpeedByIcon и теста Find_Target позволяет направить частицы одного источника по разным путям.
В следующем примере создается сцена с тремя путями: основным и двумя ответвлениями. В начале каждого ответвления размещается иконка теста Find_Target (рис. 1.18).
Рис. 1.18. Система при t = 80f
Тесты Find_Target позволяют передать часть частиц связанным с ним событиям (рис. 1.19), каждое из которых обладает оператором SpeedByIcon.
Рис. 1.19. Структурная схема системы частиц, обеспечивающей ветвление частиц по трем путям
Движение иконок операторов SpeedByIcon вдоль созданных путей обеспечивается ограничениями Path_Constraint.
Изначально частицы перемещаются вдоль основного пути в цилиндре с радиусом в 9 единиц (у первого оператора SpeedByIcon свойства Steer_Towards_Trajectory = on, а Distance = 9).
В каждом тесте Find_Target свойство Speed_Type = 2. Поэтому в тесте оценивается расстояние частицы до центра иконки Find_Target, и тест завершается положительно, если это расстояние меньше значения свойства Test_Distance. В первом тесте это значение равно 5, а во втором – 7. Поэтому при достижении иконки теста Find_Target у первого ответвления второму событию будут переданы частицы основного потока, расположенные внутри цилиндра с радиусом в 5 единиц.
При достижении второго отводка часть оставшихся частиц, расположенная внутри цилиндра с радиусом в 7 единиц, передается третьему событию. Прочие частицы продолжат движение по основному пути вслед за иконкой оператора SpeedByIcon первого события.
Отметим, что значение свойства Distance оператора SpeedByIcon основного потока частиц должно превышать значение свойства Test_Distance каждого теста Find_Target. В противном случае в отводок, для которого это условие нарушено, уйдут все частицы основного потока.
Рис. 1.20. Система частиц в конце анимации: а - частицы выводятся в виде точек; б - частицы представлены как Mesh
Вторая анимация, воспроизводимая при k = 2, содержит BlobMesh-объект, формирующий на основе частиц Mesh (рис. 1.20, б).
При выводе в видовом порте свойство Type оператора DisplayParticles устанавливается равным нулю (частицы не отображаются). Аналогично, в случае воспроизведения оператор Render употребляется с Type = 0.
Похожий результат можно получить, заменив тесты Find_Target на тесты Script_Test, передавая событию, связанным с тестом, часть частиц, возраст которых лежит в некотором диапазоне. Так, в нижеприводимом коде частицы, находящиеся в области отводка вниз, имеют возраст около 3000 тиков. Поэтому возрастной диапазон для положительного завершения первого теста Script_Test можно установить равным 2800 – 3200 тиков. У верхнего ответвления возраст частиц в районе 6500 тиков.
Возраст частицы содержит свойство ParticleAge интерфейса MaxscriptParticleContainer. Тип данных свойства – time. В коде значение возраста переводится целое число, которое равно возрасту в тиках.
Использование Script_Test взамен Find_Target позволяет, во-первых, более точно управлять числом посылаемых в отводок частиц, а во-вторых, снимает ограничение на значение свойства Distance оператора SpeedByIcon основного потока частиц.
Тест позволяет указать объекты, к которым устремятся генерируемые частицы. Если свойство теста Lock_On_Target установлено в on, то частицы позиционируются на достигнутой цели. Распределение частиц по целям регулируется свойством ParticleInteger. В примере частицы распределяются по целям равномерно. В примере две цели: цилиндр, имитирующий монету, и плоскость. При этом если ParticleInteger = 0, то частица устремится к цилиндру и окажется на плоскости, если ParticleInteger = 1.
При анимации частицы, имитирующие песок, заносят монету, и плоскость, не которой монета лежит. Для сдувания песка с монеты можно либо проиграть сцену в обратную сторону (переключатель Direction окна Time Configuration устанавливается в положение Reverse), либо инвертировать последовательность ключей анимации.
По умолчанию тест Age_Test проверяет возраст частицы (Particle Age) на предмет превышения заданного свойством Test_Value значения. Если такое превышение наблюдается, то тест завершается со значением true и частица передается следующему событию.
Условие тестирования можно изменить на противоположное, то есть завершать тест положительно, если возраст частицы меньше Test_Value. Кроме того, взамен возраста частицы можно тестировать ее абсолютное время или время ее присутствия в текущем событии.
В примере цвет частицы меняется с белого на серый (рис. 1.22), если ее возраст превышает заданное значение.
Рис. 1.22. Частицы, прошедшие Age Test, имеют серый цвет
Генерируемые частицы вылетают из эмиттера по случайно выбранным направлениям (opSpd.Direction = 3). Цвет частиц регулируется свойством Color оператора DisplayParticles. В качестве начального цвета частиц выбран белый (opDP.Color = white).
Тест Age_Test передает второму событию Частицы, возраст которых больше 10f, что равно 1600 тиков (в коде теста время указывается в тиках). Если свойство Variation (отклонение) теста Age_Test задать отличным от нуля (значение задается в тиках), то тестируемое время будет отклоняться случайным образом от значения, заданного свойством Test_Value, на Variation. Вероятность передачи следующему событию «неподходящих» частиц будет ниже, если установить в true свойство Subframe_Sampling. Это обеспечит более частое выполнение теста.
Второе событие снабжено лишь одним оператором opDP2, обеспечивающим замену текущего цвета частицы на серый.
Структурная схема созданной системы приведена на рис. 1.23.
В примере после прохождения теста Sript_Test сферические, цилиндрические и конические частицы направляются соответственно по осям -X, -Y и X. Кроме того, во втором событии меняется цвет частиц и вводится посредством оператора Keep_Apart сила взаимного отталкивания (рис. 1.24).
Структурная схема рассмотренной системы частиц приведена на рис. 1.25.
Рис. 1.25. Пройден Sript_Test
В следующем примере генерируются частицы, имеющие форму сферы, цилиндра, конуса или чайника. Форма случайным образом назначается частице в proceed-обработчике теста Sript_Test. Частицы, возраст которых превышает заданную величину (переменная aRngEnd), если их форма отлична от формы чайника, не проходят Sript_Test и останавливаются. Частицы в форме чайники тест проходят и поэтому передаются следующему событию, в котором меняется их цвет. Кроме того, они разлетаются в разные стороны (рис. 1.26) благодаря имеющемуся во втором событии оператору Keep_Apart.
Рис. 1.26. Движение продолжают частицы в форме чайника
Решение обеспечивается следующим кодом:
fn fPST2 h = (
animationRange = interval 0f 100f
delete $*
messageBox("Sript_Test and Keep_Apart")
global aRngEnd = animationRange.End / 3
global arrMsh = #(1, 2, 3, 4)
sp = sphere radius:4 segs:8
tp = teapot radius:5
cl = cylinder radius:4 height:8
cn = cone radius1:4 radius2:0 height:8
hide #(sp, tp, cl, cn)
arrMsh[1] = sp.Mesh
arrMsh[2] = tp.Mesh
arrMsh[3] = cl.Mesh
arrMsh[4] = cn.Mesh
global nVertsTp = tp.Mesh.NumVerts
pF = PF_Source enable_Particles:true quantity_Viewport:100 \
pos:[0, 0, 50]
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:1600 amount:50
opPI = position_Icon()
opSpd = speed speed:50 direction:0 divergence:40
opRt = rotation()
opDP = displayParticles color:red type:6 -- Geometry
tstST = script_Test proceed_Script:"
on channelsUsed pCont do (
pCont.UseShape = true
pCont.UseAge = true
pCont.UseTime = true
pCont.UseSpeed = true
)
on proceed pCont do (
cnt = pCont.NumParticles()
for i in 1 to cnt do (
pCont.ParticleIndex = i
if pCont.ParticleNew then pCont.ParticleShape = arrMsh[random 1 4]
if pCont.ParticleAge > aRngEnd then
if pCont.ParticleShape.NumVerts == nVertsTp then (
pCont.ParticleTestStatus = true
pCont.ParticleTestTime = pCont.ParticleTime
)
else
pCont.ParticleSpeed = [0, 0, 0]
)
)"
apActFn pF #(opBth, opPI, opSpd, opRt, opDP, tstST) false
--
-- Event 2
particleFlow.BeginEdit()
opSpd2 = speed speed:1 direction:4 divergence:0 -- Random Horizontal
opDP2 = displayParticles color:green type:6
opKA2 = keep_Apart force:1.0
evn2 = apActFn pF #(opSpd2, opDP2, opKA2) false
tstST.SetNextActionList evn2 tstST
playAnimation()
)
fPST2 1
Структурная схема рассмотренной системы частиц приведена на рис. 1.27.
В примере генерируется одна частица, которая благодаря оператору Material_Dynamic и примененному с ним стандартным материалом с диффузионной картой Particle_Age изменяет по мере старения свой цвет.
При k = 1, частица имеет стандартную сферическую форму, при k = 2 и 3 – плоскую форму (употреблен оператор Shape_Facing). При этом при k = 3, поскольку стандартный материал вдобавок снабжен картой OpacityMap на основе маски Gradient, частица имеет форму круга (рис. 1.28).
Рис. 1.28. Частица с Particle_Age: а - стандартная форма; б - употреблен Shape_Facing; в - употреблены Shape_Facing и OpacityMap
Результат обеспечивается следующим кодом:
fn fPAg h = (
animationRange = interval 0f 100f
msg = "Particle Age"
pAMp = particle_Age color1:red age1:0 color2:green age2:40 color3:blue age3:80
sMPF = standard diffuseMap:pAMp showInViewport:true diffuseMapEnable:true
for k = 1 to 3 do (
delete $*
case k of (
2 : msg += " Shape_Facing"
3 : (
grd = gradient gradientType:1
opcMp = mask map:grd
sMPF.OpacityMap = opcMp
sMPF.Opacity = 100
sMPF.UseSelfIllumColor = on
sMPF.SelfillumMap = pAMp
msg += " Shape_Facing with Opacity Gradient Map"
)
)
messageBox(msg)
meditMaterials[1] = sMPF
pF = PF_Source enable_Particles:true quantity_Viewport:100
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:0 amount:1
opPI = position_Icon()
opShS = shapeStandard shape:2 size:(k * 20) -- Sphere with R = 20
opRP = renderParticles()
opDP = displayParticles type:6
opDlt = deleteParticles type:2 life_Span:8000 variation:0
opMD = material_Dynamic assigned_Material:sMPF
-- In Local Space (Size_Space = 1)
if k > 1 then (
opShFc = shape_Facing size_Space:1 -- Flat particles
apActFn pF #(opBth, opPI, opShS, opDP, opDlt, opMD, opShFc, opRP) true
)
else
apActFn pF #(opBth, opPI, opShS, opDP, opDlt, opMD, opRP) true
backgroundColor = white
renderWidth = 320; renderHeight = 240 -- 640 * 480
render frameRange:(interval 0f 50f)
)
)
fPAg 1
В примере генерируется одна частица, которая благодаря оператору Material_Dynamic и примененному с ним стандартным материалом с диффузионной картой Particle_Age изменяет по мере старения свой цвет.
Частица имеет стандартную сферическую форму, однако при воспроизведении используется плоская форма, что обусловлено употреблением оператора Shape_Facing.
fn fMSt h = (
animationRange = interval 0f 75f
sliderTime = 0f
delete $*
messageBox("Material Static for ShapeStandard")
gR = gradient_Ramp gradient_Type:4 \
noise_Type:1 amount:0.5 size:2.5 phase:2 levels:4 \
low_Threshold:0 high_Threshold:1 source_Map_On:0
gR.Coordinates.MappingType = 0 -- Texture
gR.Coordinates.Mapping = 0 -- Explicit Map Channel
gR.Gradient_ramp.Flag__1.Color = red
gR.Gradient_ramp.Flag__2.Color = green
gR.Gradient_ramp.Flag__3.Color = blue -- Central flag
sM = standard diffuseMap:gR showInViewport:true diffuseMapEnable:true
meditMaterials[1] = sM
animate on (
at time 0 (
gR.Gradient_ramp.Flag__3.Color = yellow
gR.Gradient_ramp.Flag__3.Position = 20
)
at time 100 (
gR.Gradient_ramp.Flag__3.Color = blue
gR.Gradient_ramp.Flag__3.Position = 80
)
)
pF = PF_Source enable_Particles:true quantity_Viewport:100
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:0 amount:1
opPI = position_Icon()
opShS = shapeStandard shape:2 size:40 -- Sphere with R = 40
opRP = renderParticles()
opDP = displayParticles type:6
opMp = mapping U_Map:1 V_Map:1 W_Map:1 \
sync_Type:0 channel_Type:1 show_In_Viewport:on
opMtS = material_Static assigned_Material:sM
opShFc = shape_Facing size_Space:1 -- Flat particles
apActFn pF #(opBth, opPI, opShS, opDP, opMp, opMtS, opShFc, opRP) true
playAnimation()
)
fMSt 1
Один кадр анимации приведен на рис. 1.29.
Рис. 1.29. В начале анимации заметен желтый цвет; затем он замещается синим цветом
В следующем примере оператор Material_Static используется с частицей цилиндрической формы, устанавливаемой как значение свойства ParticleShape частицы. При k = 3 вдобавок используется оператор Shape_Facing (рис. 1.30).
Рис. 1.30. Частица картой Gradient_Ramp: а - используется цилиндрическая форма; б - употреблен оператор Shape_Facing
Результат получается после исполнения следующего кода:
fn fMSt2 h = (
animationRange = interval 0f 75f
sliderTime = 0f
msg = "Material Static with UseShape"
pObj = cylinder height:50 radius:25 mapCoords:on
global pObjMesh = pObj.Mesh, pF
hide pObj
gR = gradient_Ramp gradient_Type:4 \
noise_Type:1 amount:0.5 size:2.5 phase:2 levels:4 \
low_Threshold:0 high_Threshold:1 source_Map_On:0
gR.Coordinates.MappingType = 0 -- Texture
gR.Coordinates.Mapping = 0 -- Explicit Map Channel
gR.Coordinates.W_Angle = -90 -- Gradient from bottom to top
gR.Gradient_ramp.Flag__1.Color = red
gR.Gradient_ramp.Flag__2.Color = green
gR.Gradient_ramp.Flag__3.Color = blue -- Central flag
sM = standard diffuseMap:gR showInViewport:true diffuseMapEnable:true
meditMaterials[1] = sM
animate on (
at time 0 (
gR.Coordinates.W_Angle = -90
gR.Gradient_ramp.Flag__3.Color = yellow
gR.Gradient_ramp.Flag__3.Position = 20
)
at time 100 (
gR.Coordinates.W_Angle = -90
gR.Gradient_ramp.Flag__3.Color = blue
gR.Gradient_ramp.Flag__3.Position = 80
)
)
for k = 1 to 3 do (
delete $*
if k == 3 do msg += " and Shape_Facing"
messageBox(msg)
pF = PF_Source enable_Particles:true quantity_Viewport:100 pos:[0, 0, 10]
particleFlow.BeginEdit()
opBthS = birth_Script proceed_Script:"
on ChannelsUsed pCont do (
pCont.UseTime = true
pCont.UseAge = true
pCont.UsePosition = true
pCont.UseSpeed = true
pCont.UseShape = true
)
on Proceed pCont do (
nP = pCont.NumParticles()
if nP == 0 then (
pCont.AddParticle()
pCont.ParticleIndex = 1
pCont.ParticleTime = 0
pCont.ParticleAge = 0
pCont.ParticlePosition = pF.Pos
pCont.ParticleSpeed = [0, 0, -0.005]
pCont.ParticleShape = pObjMesh
)
)"
opPI = position_Icon()
opRP = renderParticles()
opDP = displayParticles type:6
opMtS = material_Static assigned_Material:sM
if k < 3 then
apActFn pF #(opBthS, opPI, opShS, opDP, opMtS, opRP) true
else (
-- Size_Space = 0 = flat particles in World Space
opShFc = shape_Facing size_Space:0 units:50
-- Or
-- opShFc = shape_Facing size_Space:1 inherited_Scale:100
apActFn pF #(opBthS, opPI, opShS, opDP, opMtS, opShFc, opRP) true
)
if k == 1 or k == 3 then
playAnimation()
else (
backgroundColor = white
renderWidth = 320; renderHeight = 240 -- 640 * 480
render frameRange:(interval 0f 75f)
)
)
)
fMSt2 1
В примере создаются 10 сферических частиц, равномерно размещенных на окружности. К частицам посредством оператора Material_Frequency применяется материал Multimaterial с тремя различными диффузионными картами: Checker, Marble и Bricks. Каждой частице назначается одна из трех карт. Частота назначения карты k определяется значением свойства mtl_ID_k. В примере для карт Checker, Marble и Bricks использованы соответственно значения 40, 40 и 20.
fn fMFrq h = (
animationRange = interval 0f 75f
sliderTime = 0f
delete $*
messageBox("Material_Frequency")
sph = sphere radius:8 mapCoords:on pos:[40, -40, 0] segs:16
global sphMesh = sph.Mesh, pF
hide sph
chk = checker()
chk.Coordinates.U_Tiling = 3
chk.Coordinates.V_Tiling = 3
mbl = marble()
brk = bricks()
brk.Coords.U_Tiling = 2
brk.Coords.V_Tiling = 2
mlMt = multimaterial()
mlMt.MaterialList[1].DiffuseMap = chk
mlMt.MaterialList[2].DiffuseMap = mbl
mlMt.MaterialList[3].DiffuseMap = brk
meditMaterials[1] = mlMt
pF = PF_Source enable_Particles:true quantity_Viewport:100
particleFlow.BeginEdit()
opBthS = birth_Script proceed_Script:"
on proceed pCont do (
nP = pCont.NumParticles()
if nP == 0 do pCont.AddParticles 10
)"
opSO = script_Operator proceed_Script:"
on channelsUsed pCont do (
pCont.UseTime = true
pCont.UseAge = true
pCont.UsePosition = true
pCont.UseSpeed = true
pCont.UseShape = true
pCont.UseOrientation = true
pCont.UseSpin = true
)
on proceed pCont do (
nP = pCont.NumParticles()
r = 50
for k = 1 to 10 do
if pCont.ParticleNew then (
pCont.ParticleIndex = k
pCont.ParticleTime = 0
pCont.ParticleAge = 0
x = r * sin (36 * k)
y = 0.5 * r * cos (36 * k)
pCont.ParticlePosition = pF.Pos + [x, y, y + 20]
pCont.ParticleSpeed = [0, 0, -0.01]
pCont.ParticleSpin = (angleAxis 0.01 [1, 0, 1])
pCont.ParticleShape = sphMesh
)
)"
opPI = position_Icon()
opRP = renderParticles()
opDP = displayParticles type:6
opMFrq = material_Frequency assigned_Material:mlMt \
mtl_ID_1:40 mtl_ID_2:40 mtl_ID_3:20
apActFn pF #(opBthS, opPI, opSO, opDP, opMFrq, opRP) true
backgroundColor = gray
renderWidth = 320; renderHeight = 240
render frameRange:(interval 0f 20f)
)
fMFrq 1
Один кадр анимации приведен на рис. 1.31.
Рис. 1.31. Частицы с оператором Material_Frequency
В примере эмиттером частиц является сфера, указанная в свойстве Emitter_Objects оператора Position_Object. Изначально все частицы рождаются в первом событии, когда t = 0. частицы. Далее они посредством теста Spawn (тиражирование частиц) передаются второму событию и подвергаются воздействию ветра, что вызывает их смещение. Частицы, вышедшие из заданного пространства, передаются третьему событию и уничтожаются.
Цвет хвоста кометы обусловлен картой Particle_Age, примененной к частицам посредством оператора Material_Dynamic и стандартного материала, диффузионная карта которого определена ссылкой на карту Particle_Age. Эта карта имеет три цвета: желтый, красный и черный. Первый используется для вновь появившихся частиц, затем их цвет последовательно меняется на красный и черный.
Структурная схема системы частиц приведена на рис. 1.32.
В примере одномоментно создаются 30 частиц, к которым посредством оператора Material_Dynamic применяются карты Particle Age и Gradient_Ramp. Перемещение частиц обеспечивается приложенными силами ветра, торможения и гравитации (соответственно Wind, Drag и Gravity). Наличие оператора Shape_Facing обеспечивает плоскую форму частиц, а также их ориентацию относительно введенной в сцену камеры.
Для повышения наглядности в карту Gradient_Ramp следует интерактивно добавить шум с параметром type = 2 (Turbulence), а также флаги 4 и 5 и флаг 4 соответственно в карты gR и gR2.
Частица – это элемент подмножества частиц, называемого контейнером частиц. В каждый момент времени частица принадлежит одному событию.
Геометрия частицы описывается в ее локальной системы координат. Частицы обладают массой, величина которой пропорциональна объему частицы.
При программировании операторов Birth_Script, Script_Operator и Script_Test управление частицами обеспечивает интерфейс MaxscriptParticleContainer. В прочих скриптах доступ к частицам осуществляется средствами интерфейса ParticleObjectExt.
Интерфейс MaxscriptParticleContainer позволяет получать и изменять значения следующих свойств частицы (в скобках указывается тип свойства):
ParticleOrientation (point3) – ориентация системы координат частицы в мировой системе координат;
ParticleSpin (angleAxis) – угол в градусах, на который поворачивается частица в каждый момент (тик) времени вокруг указанного вектора; формируется конструктором AngleAxis, например (angleAxis 0.2 [1, 0, 0]
ParticleScale (float) – размер частицы (среднее арифметическое коэффициентов масштабирования частицы по осям X, Y и Z;
ParticleSelected (bool) – свойство равно true, если частица выбрана, и false – в противном случае;
ParticleShape (mesh) – TriMesh-форма частицы;
ParticleInteger (integer) – номер цели, которую ищет частица; применяется с тестом Find_Target, свойство которого Assignment_Type = 4 (By Script Integer);
ParticleTestStatus (bool) – свойство равно true, если частица прошла тест, и false – в противном случае;
ParticleTestTime (time) – время частицы в тесте Script_Test
ParticleFloat (float) – позволяет регулировать влияние сил операторов Force и Keep_Apart на частицу: влияние силы увеличивается по мере увеличения значения ParticleFloat;
ParticleVector (point3) – используется для указания позиции цели при работе с тестом Find_Target;
ParticleMatrix (matrix3) – позволяет сохранить или прочитать матрицу данных.
Интерфейс ParticleObjectExt оперирует свойствами ParticleIndex, ParticleID, ParticleAge, ParticlePosition, ParticleSpeed, ParticleOrientation, ParticleSpin, ParticleScale, ParticleScaleXYZ, ParticleTM, ParticleSelected и ParticleShape, а также свойством ParticleGroupTime (time), содержащим положение частицы на временной оси.
В обоих интерфейсах число доступных частиц возвращается методом NumParticles. При этом в обработчиках оператор Birth_Script, Script_Operator и Script_Test доступны частицы, принадлежащие только событию, которому операторы принадлежат.
Генерацию частиц обеспечивает оператор Birth, а также методы AddParticle и AddParticles интерфейса MaxscriptParticleContainer. Кроме того, генерацию частиц из существующих обеспечивают тесты Spawn и Collision_Spawn: каждая прошедшая тест частица порождает другие частицы. Число вновь рожденных частиц регулируется свойствами Number_of_Offsprings и Offsprings_Variation соответствующего теста.
В примере в тесте Birth_Script в точках 0, 20f, …, 100f генерируются и посылаются по случайно выбранным направлениям в плоскости Z = 0 по 100 частиц.
fn fBS h = (
animationRange = interval 0f 100f
delete $*
messageBox("Birth Script. AddParticle")
delete $*
pF = PF_Source show_Logo:off show_Emitter:off \
emitter_Length:0 emitter_Width:0 quantity_Viewport:100
particleFlow.BeginEdit()
opBth = birth_Script proceed_Script:"
on channelsUsed pCont do pCont.UseTime = true
on proceed pCont do (
nP = pCont.NumParticles()
pTMax = 0
for k in 1 to nP do (
pCont.ParticleIndex = k
pT = pCont.ParticleTime as integer
if pT > pTMax then pTMax = pT
)
if nP == 0 or (pTMax > 0 and mod pTMax 3200 == 0) then
for k = 1 to 100 do pCont.AddParticle()
)"
opPI = position_Icon()
opSpd = speed speed:20 direction:4
opShS = shapeStandard()
opDP = displayParticles color:red type:2
apActFn pF #(opBth, opPI, opSpd, opShS, opDP) false
playAnimation()
)
fBS 1
Взамен цикла
for k = 1 to 100 do pCont.AddParticle()
можно употребить метод
pCont.AddParticles 100
При анимации, если размеры эмиттера равны нулю, частицы располагаются на окружностях (рис. 1.35).
Рис. 1.35. Система частиц при t = 61f
Тот же эффект достигается и при ненулевых размерах эмиттера, а в результате употребления оператора position_Icon со следующими свойствами:
opPI = position_Icon distinct_Points_Only:on total_Distinct_Points:1
В следующем примере используется тест Collision_Spawn. Каждая частица после столкновения препятствием (объект POmniFlect) порождает три новые частицы. При этом родительская частица удаляется из системы частиц. Форма исходных частиц - сфера, а возникших после столкновения - куб.
Удаление частиц выполняют оператор DeleteParticles и методы DeleteParticle и DeleteParticles интерфейса MaxscriptParticleContainer.
В примере генерируемые частицы перемещаются в случайно выбранных направлениях, однако после превышения возраста 30f частица удаляется, если ее Х-координата меньше -10 или Z-координата превышает +10. Оставшиеся частицы после каждого удаления меняют форму на сферическую или цилиндрическую.
fn fDP h = (
animationRange = interval 0f 100f
delete $*
messageBox("Delete Particle")
global sph, cl, cn, mDlt = 1
sph = sphere radius:5
cl = cylinder radius:4 height:20
cn = cone radius1:5 radius2:0 height:15
hide #(sph, cl, cn)
pF = PF_Source enable_Particles:on \
show_Logo:on show_Emitter:on quantity_Viewport:100
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:0 amount:11
opPI = position_Icon()
opSpd = speed speed:14 variation:5 direction:3
opDP = displayParticles color:red type:6
opSO = script_Operator proceed_Script:"
on channelsUsed pCont do (
pCont.UseTime = true
pCont.UseAge = true
pCont.UsePosition = true
pCont.UseShape = true
)
on proceed pCont do (
nP = pCont.NumParticles()
isDlt = false
for k in 1 to nP do (
pCont.ParticleIndex = k
if pCont.ParticleNew then pCont.ParticleShape = cn.Mesh
pAg = pCont.ParticleAge
pPos = pCont.ParticlePosition
if pAg > 30f and (pPos[1] < -10 or pPos[3] > 10) then (
pCont.DeleteParticle k
isDlt = true
)
)
if isDlt do (
nP = pCont.NumParticles()
for k in 1 to nP do (
pCont.ParticleIndex = k
pCont.ParticleShape = if mod mDlt 2 == 1 then sph.Mesh else cl.Mesh
)
mDlt += 1
)
)"
apActFn pF #(opBth, opPI, opSpd, opDP, opSO) false
playAnimation()
)
fDP 1
Два кадра анимации приведены на рис. 1.37.
Рис. 1.37. Частицы при t = 30f и t = 70f
Аналогичный результат можно получить, используя Script_Test, присутствующий в следующем коде:
delete $*
pF = PF_Source enable_Particles:on \
show_Logo:on show_Emitter:on quantity_Viewport:100
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:0 amount:10
opPI = position_Icon()
opSpd = speed speed:20 direction:3
opShS = shapeStandard shape:2 size:5 -- Sphere with R = 5
opDP = displayParticles color:red type:6
tstST = script_Test proceed_Script:"
on channelsUsed pCont do (
pCont.UseTime = true
pCont.UseAge = true
pCont.UsePosition = true
)
on proceed pCont do (
nP = pCont.NumParticles()
for k in 1 to nP do (
pCont.ParticleIndex = k
pAg = pCont.ParticleAge
pPos = pCont.ParticlePosition
if pAg > 30 and (pPos[1] < 0 or pPos[3] > 0) then (
pCont.ParticleTestStatus = true
pCont.ParticleTestTime = pCont.ParticleTime
)
)
)"
apActFn pF #(opBth, opPI, opSpd, opShS, opDP, tstST) false
particleFlow.BeginEdit()
opD2 = deleteParticles type:0 -- Delete All
evn2 = apActFn pF #(opD2) false
tstST.SetNextActionList evn2 tstST
playAnimation()
Приведенный Script_Test передает следующему событию с оператором Delete частицы, удовлетворяющие условиям удаления.
Структурная схема последней системы приведена на рис. 1.38.
Рис. 1.38. Система с тестом Script_Test
В следующем примере генерируемые частицы направляются от центра иконки эмиттера (в операторе Speed свойство Direction = 1, или Icon Center Out). При этом частицы не выходят за пределы некоторого круга, поскольку при достижении заданного возраста они удаляются из системы.
В примере направление движения частицы изменяется на противоположное, если ее возраст кратен 20f.
fn fPSpd h = (
animationRange = interval 0f 100f
delete $*
messageBox("Particle Speed")
pF = PF_Source enable_Particles:true \
quantity_Viewport:100 emitter_Type:2 emitter_Length:30
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:0 amount:3
opPI = position_Icon location:4
opSpd = speed speed:50 direction:1
opShS = shapeStandard shape:0 size:10
opDP = displayParticles color:(color 30 30 30) type:6
opSO = script_Operator proceed_Script:"
on channelsUsed pCont do (
pCont.UseTime = true
pCont.UseSpeed = true
)
on proceed pCont do (
nP = pCont.NumParticles()
for k = 1 to nP do (
pCont.ParticleIndex = k
pAg = pCont.ParticleAge as integer
if mod pAg 3200 == 0 then pCont.ParticleSpeed = -pCont.ParticleSpeed
)
)"
apActFn pF #(opBth, opPI, opSpd, opShS, opDP, opSO) false
playAnimation()
)
fPSpd 1
Два крайних положения частиц системы приведены на рис. 1.40.
Рис. 1.40. Система при t = 0f и t = 20f
На отрезке 21f – 40f частицы устремятся к эмиттеру.
Разброс позиций частиц в точке t = 0f (то есть сразу после рождения) обусловлен свойствами эмиттера: его иконка представляется в виде окружности (Emitter_Type = 2) с диаметром в 30 единиц (Emitter_Length = 30)
Proceed_Script оператора Script_Operator содержит два обработчика: ChannelsUsed и Proceed. Первый вызывается единожды при создании системы частиц, и в нем фиксируется каналы связи с частицами. По каналам, получившим статус true, можно получать и изменять значения соответствующих свойств частиц. Так, в примере обеспечивается возможность программного изменения скорости частиц (канал UseSpeed). Канал UseTime необходим для фиксации изменений свойств частицы на временной оси.
Обработчик Proceed вызывается на каждом шаге временной оси. Для доступа к свойствам частицы необходимо сделать частицу текущей, указав ее по ее номеру
pCont.ParticleIndex = k
Все последующие изменения свойств будут затрагивать только текущую частицу.
Поскольку возраст частицы указан в свойстве ParticleAge в тиках и имеет тип time, то его необходимо преобразовать в тип integer, и устанавливать кратность возраста не 20 кадрам (как в условии), а 160 * 20 = 3200 тикам.
В следующем примере первоначально частицы направляются вниз, а затем частица, после достижения заданного возраста направляется по оси –X, если индекс частицы четен, или – в противоположном направлении, если индекс частицы нечетен.
fn fPSpd2 h = (
animationRange = interval 0f 100f
delete $*
messageBox("Particle Speed 2")
global pAgM = 30f
pF = PF_Source pos:[0, 0, 40] enable_Particles:true \
emitter_Type:0 quantity_Viewport:100
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:16000 amount:30
opPI = position_Icon distinct_Points_Only:on total_Distinct_Points:1
opShS = shapeStandard shape:2 size:4
opDP = displayParticles type:6 color:(color 135 110 8)
opSO = script_Operator proceed_Script:"
on channelsUsed pCont do (
pCont.UseAge = true
pCont.UseSpeed = true
)
on proceed pCont do (
nP = pCont.NumParticles()
for k = 1 to nP do (
pCont.ParticleIndex = k
if pCont.ParticleNew then
pCont.ParticleSpeed = [0, 0, -0.01]
else
if pCont.ParticleAge > pAgM then
pCont.ParticleSpeed = if mod k 2 == 0 then [-0.01, 0, 0] else [0.01, 0, 0]
)
)"
apActFn pF #(opBth, opPI, opShS, opDP, opSO) false
playAnimation()
)
fPSpd2 1
Один кадр анимации и структурная схема системы приведены на рис. 1.41.
Рис. 1.41. Система при t = 80f и структурная схема системы
После генерации все частицы перемещаются вниз по одной линии, что обеспечивается соответствующими свойствами оператора Position_Icon.
В примере частица после генерации имеет форму сферы, при достижении возраста 25f она приобретает форму цилиндра, в возрасте 50f – форму конуса, а в возрасте 75f – форму куба.
Оператора задания форма частицы в вышеприведенном коде опущен, поскольку форма частицы в каждый момент времени известна и задается как значение свойства ParticleShape.
Структурная схема рассмотренной системы проста (рис. 1.44).
Рис. 1.44. Структурная схема системы; форма частиц меняется Script_Operator
Тот же результат можно получить, употребляя вместо pAg глобальную переменную sliderTime. В этом случае proceed-обработчик записывается следующим образом:
on proceed pCont do (
nP = pCont.NumParticles()
for k = 1 to nP do (
pCont.ParticleIndex = k
case sliderTime of (
0f : pCont.SetParticleShape k sphM
21f : pCont.SetParticleShape k clM
41f : pCont.SetParticleShape k cnM
61f : pCont.SetParticleShape k cbM
)
)
)
Кроме того, вместо метода
playAnimation()
следует при каждом воспроизведении употреблять цикл
for k = 0 to 100 do sliderTime = k
Так же в качестве альтернативы свойству ParticleShape использован метод SetParticleShape.
Аналогичный результат можно получить, не прибегая к Script_Operator, использовав взамен трижды тест Age_Test и оператор Shape_Instance, определяя его свойство Shape_Object ссылкой на соответствующий объект.
В примере задается произвольный текст, из букв которого формируется массив. Далее в proceed-обработчике оператора Script_Operator новой частице назначается форма соответствующей буквы. Если число частиц превышает число букв в тексте, то форма избыточных частиц определяется как пустая форма.
fn fPShp3 h = (
animationRange = interval 0f 100f
delete $*
messageBox("ParticleShape: Letters")
global letterArray, txtCount, emptyMesh
txtVal = "Some text line"
textSource = text text:txtVal size:30 pos:[0, 15, 30] wireColor:gray
addModifier textSource (extrude amount:5)
letterArray = #()
txtCount = txtVal.Count
for k = 1 to txtCount do (
letter = copy textSource -- Copy with modifier
letter.Text = (substring txtVal k 1)
letter.Font = textSource.Font
append letterArray letter
hide letter
)
emptyMesh = triMesh()
pF = PF_Source enable_Particles:true quantity_Viewport:100
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:7000 amount:txtCount
opPI = position_Icon()
opSpd = speed speed:25 direction:2 divergence:10 reverse:on
opDP = displayParticles color:red type:6
opSO = script_Operator proceed_Script:"
on channelsUsed pCont do pCont.UseShape = true
on proceed pCont do (
nP = pCont.NumParticles()
for k = 1 to nP do (
pCont.ParticleIndex = k
if k <= txtCount then
pCont.ParticleShape = letterArray[k].Mesh
else
pCont.ParticleShape = emptyMesh
)
)"
apActFn pF #(opBth, opPI, opSO, opSpd, opDP) false
playAnimation()
)
fPShp3 1
Один кадр анимации приведен на рис. 1.47.
Рис. 1.47. Форма частиц определяется как Mesh одной из букв текста
В примере частицы случайным образом распределяются между тремя сферами. Это обеспечивается, во-первых, за счет задания Assignment_Type = 4 (By Script Integer) в тесте Find_Target, и, во-вторых, за счет определения значения свойства ParticleInteger случайным числом из диапазона 0 – 2. Частицы, у которых ParticleInteger = nI, будут устремляться к цели, индекс которой в массиве целей равен nI + 1. Массив целей содержит свойство Target_Objects теста Find_Target.
Значение свойства ParticleInteger выбирается случайным образом из чисел 0, 1 и 2 и устанавливается для новой частицы (ее свойство ParticleNew = true). Такой подход позволяет равномерно распределить частицы между целями (рис. 1.48).
Рис. 1.48. Система частиц с тремя целями при t = 28f
Если в тесте Find_Target свойство Lock_On_Target = on, то тест Find_Target должен находиться в структурной схеме после оператора Script_Operator, в котором частицам назначаются цели. Это связано с тем, что при такой установке частицы распределяются между целями единожды в начале анимации. Если же Lock_On_Target = off, то приложение может решать эту задачу и в процессе анимации.
При наличии сил, действующих на частицы, можно интерпретировать массу частицы как функцию от ее объема. Для этого употребляется свойство ParticleFloat, регулирующее меру воздействия силы на частицы. Значение свойства определяется как величина, обратно пропорциональная кубу коэффициента масштабирования частицы, хранимого свойством ParticleScale. Таким образом, при уменьшении объема частицы значение свойства ParticleFloat будет резко увеличиваться и, следовательно, будет возрастать степень влияния силы (в нижеприводимом примере это ветер) на частицу.
В интерфейсе оператора Force следует выбрать переключатель Influence группы Use Script Float As списка Script Wiring интерфейса силы, действующей на частицы (рис. 1.49).
Рис. 1.49. Значение ParticleFloat будет влиять на величину силы
Для выбора переключателя открывается Particle View, в контейнере события выбирается оператор Force – правая кнопка мыши – Use Script Wiring – раскрыть список Script Wiring – Influence. Программно этот результат достигается после установки следующих свойств силы, в качестве которой использован ранее введенный в сцену ветер wnd:
В примере одномоментно генерируется 100 частиц сферической формы. Изменение объема частиц обеспечивается анимацией свойств X_Scale_Factor, Y_Scale_Factor и Z_Scale_Factor оператора ScaleParticles: в начале временной оси коэффициенты масштабирования равны 100%, а в конце – 20%. Изменение ParticleFloat выполняется в proceed-обработчике оператора Script_Operator.
fn fPFlt h = (
animationRange = interval 0f 100f
delete $*
messageBox("Particle Float")
qt = eulerToQuat (eulerAngles 0 90 0)
wnd = wind strength:0.05 decay:0 turbulence:1 \
frequency:15 rotation:qt pos:[100, 0, 0]
pF = PF_Source enable_Particles:on pos:[90, 0, 0] \
quantity_Viewport:100 show_Logo:on show_Emitter:on
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:0 amount:100
opPI = position_Icon()
--opShS = shapeStandard shape:2 -- Sphere
opShS = ShapeLibrary()
opShS.'3D_Type' = 14 -- Sphere
opSP = scaleParticles type:2 constrain_Scale:false
opSO = script_Operator proceed_Script:"
on channelsUsed pCont do (
pCont.UseTime = true
pCont.UseScale = true
pCont.UseFloat = true
)
on proceed pCont do (
nP = pCont.NumParticles()
for k in 1 to nP do (
pCont.ParticleIndex = k
pCont.ParticleFloat = 1.0 / pCont.ParticleScale^3
)
)"
opFc = force force_Space_Warps:#(wnd) useScriptWiring:1 use_Script_Float:1
opDP = displayParticles type:6 color:[50, 50, 50]
apActFn pF #(opBth, opPI, opShS, opSP, opSO, opFc, opDP) false
max tool animmode
set animate on
with redraw off (
sliderTime = 0f
opSP.X_Scale_Factor = opSP.Y_Scale_Factor = opSP.Z_Scale_Factor = 100
sliderTime = 100f
opSP.X_Scale_Factor = opSP.Y_Scale_Factor = opSP.Z_Scale_Factor = 20
sliderTime = 0f
)
set animate off
max tool animmode
playAnimation()
)
fPFlt 1
Два кадра анимации приведены на рис. 1.50.
Рис. 1.50. Сцена при t = 30f и t = 70f
В примере старые частицы держатся кучкой, а молодые разлетаются (рис. 1.51).
Рис. 1.51. Влияние ParticleFloat на поведение частиц
Это обеспечивается оператором Keep_Apart и свойством ParticleFloat, значение которого тем выше, чем моложе частица (возраст частицы тем меньше, чем больше значение ее свойства ParticleID).
fn fPFlt2 h = (
animationRange = interval 0f 100f
delete $*
pF = PF_Source enable_Particles:true \
quantity_Viewport:100 show_Logo:on show_Emitter:on
particleFlow.BeginEdit()
opBth = birth amount:20
opPI = position_Icon()
opSpd = speed speed:10
opShS = shapeStandard()
opDP = displayParticles type:6 color:pF.WireColor
opKA = keep_Apart useScriptWiring:1 use_Script_Float:1
opSO = script_Operator proceed_Script:"
on channelsUsed pCont do pCont.UseFloat = true
on proceed pCont do (
cnt = pCont.NumParticles()
for i in 1 to cnt do (
pCont.ParticleIndex = i
-- The younger the particle, the higher the ParticleFloat value
pCont.ParticleFloat = pCont.ParticleID / 10
)
)"
--
apActFn pF #(opBth, opPI, opSpd, opShS, opDP, opKA, opSO) false
playAnimation()
)
fPFlt2 1
В примере частицы в результате выполнения теста Find_Target с Aim_Point_Type = 2 скапливаются в области, определяемой в proceed-обработчике оператора Script_Operator посредством задания значения свойства ParticleVector каждой частицы из специально подобранного диапазона [20, -20, 0] – [60, -60, 0].
При этом скопление частиц (рис. 1.52) будет наблюдаться, если изменять значение свойства ParticleVector при каждом вызове proceed-обработчика (частицы, словно, не знают, в каком направлении им двигаться).
Рис. 1.52. Положение частиц при t = 80f
Если же значение свойства ParticleVector определять только для новых частиц, то после нахождения цели частицы будут от нее удаляться.
fn fPVct h = (
animationRange = interval 0f 200f
delete $*
messageBox("Particle Vector")
pF = PF_Source enable_Particles:on pos:[-30, 30, 0] \
quantity_Viewport:100 show_Logo:on show_Emitter:on
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:0 amount:30
opPI = position_Icon()
opShS = shapeStandard shape:2 size:4 -- Sphere with R = 5
opDP = displayParticles color:red type:6
-- Control By Speed, Icon, By Script Vector
tstFT = find_Target speed_Type:0 target_Type:0 aim_Point_Type:2 \
use_Cruise_Speed:on cruise_Speed:100 \
acceleration_Limit:300
opSO = script_Operator proceed_Script:"
on channelsUsed pCont do (
pCont.UseTime = true
pCont.UseVector = true
)
on proceed pCont do (
nP = pCont.NumParticles()
for k in 1 to nP do (
pCont.ParticleIndex = k
vPos = random [10, -10, -40] [50, -50, 40]
pCont.ParticleVector = vPos
-- if pCont.ParticleNew then pCont.ParticleVector = vPos
)
)"
apActFn pF #(opBth, opPI, opShS, opSO, opDP, tstFT) false
hide tstFT
playAnimation()
)
fPVct 1
В следующем примере имитируется эффект обмена частиц между тором и цилиндром. Для этой цели создаются две системы частиц с двумя эмиттерами и тестом Find_Target каждая. Цель каждой частицы определяется значением ее свойства ParticleVector.
fn fPVct2 h = (
animationRange = interval 0f 150f
delete $*
messageBox("Particle Vector 2")
cl = cylinder radius:40 height:40 pos:[0, -100, 0] wireColor:gray
tr = torus radius1:50 radius2:20 pos:[0, 100, 0] wireColor:gray
vr = vortex timeOff:30 axialStrength:15 rotationStrength:25 \
iconSise:40 rotation:(quat -1 0 0 0) pos:[40, 40, 0]
sph = sphere radius:1 pos:[300, 20, 0]
pF = PF_Source enable_Particles:on \
quantity_Viewport:100 show_Logo:off show_Emitter:off
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:0 amount:100
opPO = position_Object Location:4 emitter_Objects:#(tr)
opPO2 = position_Object Location:4 emitter_Objects:#(cl)
opSpd = speed direction:3
opShS = shapeStandard shape:1 size:15
opDP = displayParticles type:6 color:red
-- The operator defines destination points inside the target volume
opSO = script_Operator proceed_Script:"
on channelsUsed pCont do (
pCont.UseTime = true
pCont.UsePosition = true
pCont.UseVector = true
)
on proceed pCont do (
nP = pCont.NumParticles()
for k in 1 to nP do (
pCont.ParticleIndex = k
if pCont.ParticleNew then pCont.ParticleVector = pCont.ParticlePosition
)
)"
opSp = spin spinRate:200 variation:50
opFc = force influence:100 force_Space_Warps:#(vr)
tstFT = find_Target cruise_Speed_Variation:1000 \
acceleration_Limit:3000 aim_Point_Type:2 icon_Size:0
evn = apActFn pF #(opBth, opPO, opSO, opPO2, opShS, opSpd, \
opDP, opSp, opFc, tstFT) false
-- Event 2
particleFlow.BeginEdit()
opSpd2 = speed speed:0
opDP2 = displayParticles type:6 color:red
evn2 = apActFn pF #(opSpd2, opDP2) false
-- Connect the second Event to the Find_Target
tstFT.SetNextActionList evn2 tstFT
maxOps.CloneNodes #(pF, evn, evn2) newNodes:&dc
evnC = dc[2]
evn2C = dc[3]
opDPC = evnC.GetAction 7; opDPC.Color = blue
opDP2C = evn2C.GetAction 2; opDP2C.Color = blue
opPOC = evnC.GetAction 2
opPO2C = evnC.GetAction 4
tstFTC = evnC.GetAction 10
-- Define emitter objects
opPOC.Emitter_Objects = #(cl)
opPO2C.Emitter_Objects = #(tr)
-- Bind the Find_Target Test output to the clone of the Second Event
tstFTC.SetNextActionList evn2C tstFTC
max tool zoomExtents
playAnimation()
)
fPVct2 1
При создании события evn важно, чтобы оператор opSO следовал за оператором opPO, а оператор opPO2 – за оператором opSO. При такой последовательности операторов первоначально частицы генерируются оператором opPI, то есть располагаются на поверхности тора. Далее выполняется proceed-обработчик оператора opSO, в котором в свойство ParticleVector заносятся координаты новой частицы, хранимые свойством ParticlePosition, (впоследствии это значение будет использовано для позиционирования частицы тестом tstFT). Затем выполняется оператор opPO2 и все частицы сосредотачиваются на поверхности второго эмиттера (цилиндра). При анимации частицы покидают цилиндр, закручиваются вихрем и находят благодаря оператору tstFT цель. Его свойство Aim_Point_Type = 2 (By Sript Vector). После достижения цели частицы передаются второму событию, в котором их скорость обнуляется.
Для наглядности создана вторая система частиц (путем копирования первой системы), в которой порядок следования объектов-эмиттеров иной: прежде идет цилиндр, а затем тор. Поэтому частицы этой системы покидают тор и приземляются на цилиндре, что создает видимость обмена частицами между двумя поверхностями (рис. 1.53).
Рис. 1.53. Обмен частицами: а – частицы покидают объекты; б – цели найдены
Структурная схема систем частиц рассматриваемого примера приведена на рис. 1.54.
Рис. 1.54. Системы частиц с двумя операторами Position_Object
Замечание.. Как и ранее, для сокращения кода опущены операторы RenderParticles и Rotation.
В примере частицы замещаются сферическими помощниками SphereGizmo, которые благодаря атмосферному эффекту fire_Effect позволяют воспроизвести дым. Для смещения частиц и, следовательно, дыма в сцену введен ветер Wind, связанный с системой частиц посредством оператора Force.
fn fPTM h = (
animationRange = interval 0f 100f
sliderTime = 0f
delete $*
messageBox("Particle TM")
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
global arrSpG = $SphereGizmo* -- Array of SphereGizmo objects
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
opRt = rotation()
opRP = renderParticles()
opFc = 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
)
)"
tstAT = age_Test test_Value:4000 variation:0 -- In ticks
evn = apActFn pF #(opBth, opPI, opSpd, opRt, opFc, opSO, tstAT, opRP) true
--
-- Event 2
opDlt = deleteParticles type:0
evn2 = apActFn pF #(opDlt) false
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
)
fPTM 1
Результат приведен на рис. 1.55.
Рис. 1.55. Помощники SphereGizmo в окне воспроизведения и видовом порте при t = 40f
Структурная схема системы частиц приведена на рис. 1.56.
Рис. 1.56. Структурная схема системы имитации дыма
В примере генерируемые частицы располагаются по периметру квадрата, и им оператором Spin придается вращательное движение со случайно выбранной скоростью. Частицы имеют форму куба и имеют красный цвет. С заданного момента времени угловые частицы приобретают форму сферы, изменяют цвет на синий и начинают генерировать новые сферические частицы, имеющие зеленый цвет, направляя их вверх. Время жизни новых частиц ограничено.
fn fPPstn h = (
animationRange = interval 0f 100f
delete $*
messageBox("Particle Position")
max tool zoomExtents
global nPAdd = 5
sL = 40
dX = sL / nPAdd
global psnX = for k = 1 to nPAdd collect dX * (k - 1)
global psnY = for k = 1 to nPAdd collect sL - dX * (k - 1)
global ZPF = -50
global pAgSpn = 20f
global arrForSpn = for k = 1 to 4 collect nPAdd * (k - 1) + 1
global sph
ps = [0, 0, ZPF]
sph = sphere radius:5 pos:ps wireColor:(color 225 90 200) -- hide sph
pF = PF_Source enable_Particles:true \
quantity_Viewport:100 show_Logo:on show_Emitter:on pos:ps
particleFlow.BeginEdit()
opBth = birth_Script proceed_Script:"
on channelsUsed pCont do pCont.UseTime = true
on proceed pCont do (
nP = pCont.NumParticles()
if nP == 0 then pCont.AddParticles (4 * nPAdd)
)"
opPI = position_Icon location:4
opShS = shapeStandard shape:1 size:5
opDP = displayParticles type:6 color:[30, 30, 30]
opSp = spin spinRate:360 variation:50
opSO = script_Operator proceed_Script:"
on channelsUsed pCont do (
pCont.UseTime = true
pCont.UsePosition = true
)
on proceed pCont do (
n = 0
for k = 1 to 4 do
for m = 1 to nPAdd do (
n += 1
pCont.ParticleIndex = n
if pCont.ParticleNew then (
case k of (
1 : pPsn = [psnX[m], psnY[m], ZPF]
2 : pPsn = [psnY[m], -psnX[m], ZPF]
3 : pPsn = [-psnX[m], -psnY[m], ZPF]
4 : pPsn = [-psnY[m], psnX[m], ZPF]
)
pCont.ParticlePosition = pPsn
)
)
)"
tstST = script_Test proceed_Script:"
on channelsUsed pCont do (
pCont.UseTime = true
pCont.UseAge = true
pCont.UseShape = true
)
on proceed pCont do (
nP = pCont.NumParticles()
for k in 1 to nP do (
pCont.ParticleIndex = k
if pCont.ParticleTime == pAgSpn then (
j = findItem arrForSpn k
if j > 0 then (
pCont.ParticleTestStatus = true
pCont.ParticleTestTime = pCont.ParticleTime
pCont.ParticleSpeed = [0, 0, 10]
pCont.ParticleShape = sph.Mesh
)
)
)
)"
evn = apActFn pF #(opBth, opPI, opShS, opDP, opSp, opSO, tstST) false
--
-- Event 2
particleFlow.BeginEdit()
opDP2 = displayParticles type:6 color:blue
opSpd2 = speed speed:1 reverse:on
tstSpwn2 = spawn number_of_Offsprings:1 spawn_Type:1 \
spawn_Rate:10 speed_Type:0 divergence:5 scale_Factor:50
evn2 = apActFn pF #(opDP2, opSpd2, tstSpwn2) false
-- Connect the second Event to the Script_Test
tstST.SetNextActionList evn2 tstST
--
-- Event 3
particleFlow.BeginEdit()
opDP3 = displayParticles type:6 color:green
opDlt3 = deleteParticles type:2 Life_Span:4800 variation:0
evn3 = apActFn pF #(opDP3, opDlt3) false
-- Connect the second Event to the Script_Test
tstSpwn2.SetNextActionList evn3 tstSpwn2
playAnimation()
)
fPPstn 1
Два кадра анимации системы частиц приведены на рис. 1.57, а структурная схема системы – на рис. 1.58.
Рис. 1.57. Система частиц при t = 0f и t = 40f
Рис. 1.58. Система частиц, генерирующая частицы в углах квадрата
Частицы могут совершать поступательное и движение и вращаться вокруг указанного вектора локальной системы координат частицы.
Первое движение определяется вектором скорости частицы, второе – ориентацией ее локальной системы координат в мировой систем координат и спином частицы.
Вектор скорости частицы хранит ее свойство ParticleSpeed, а ориентацию и спин – соответственно свойства ParticleOrientation и ParticleSpin.
Управлять движением частицы – это значит надлежащим образом изменять ее вектор скорости, ориентацию и спин.
Оператор Position_Icon используется для управления начальным размещением частиц на эмиттере. Иконка эмиттера может принимать вид прямоугольника, прямоугольного параллелепипеда, окружности или сферы (рис. 1.61).
Рис. 1.61. Формы эмиттера частиц объекта PF_Source
Форма частиц в видовом порте регулируется свойством Type оператора DisplayParticles:
fn fDTp h = (
animationRange = interval 0f 50f
messageBox("Display Particles Types")
for k = 0 to 10 do (
delete $*
esZ = 40
pF = PF_Source enable_Particles:true emitter_Type:1 \
pos:[0, 0, 0] quantity_Viewport:100 \
emitter_Length:eSz emitter_Width:eSz emitter_Height:eSz
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:4800 amount:50
opPI = position_Icon Location:4
opDP = displayParticles color:green type:k -- Asterisks (6 - Geometry)
if k == 5 or k == 6 then (
opShS = shapeStandard shape:2 size:10
apActFn pF #(opBth, opPI, opDP, opShS) false
)
else
apActFn pF #(opBth, opPI, opDP) false
playAnimation()
)
)
Вид эмиттера управляется свойством Emitter_Type объекта PF_Source.
Частицы могут рождаться в центре эмиттера, его вершинах, на его ребрах, поверхности или в его объеме (рис. 1.62).
Рис. 1.62. Различные варианты размещения новых частиц
Выбор способа размещение новых частиц управляется свойством Location оператора Position_Icon.
Кроме того, можно точно указать число точек, в которых будут генерироваться частицы (свойство Total_Distinct_Points оператора Position_Icon). Такой режим будет использован, если установить в on свойство Distinct_Points_Only оператора Position_Icon. Интерактивно эти свойства изменяются в группе Location интерфейса оператора Position_Icon (рис. 1.63, а).
Рис. 1.63. Режим рождения частиц в отдельных точках:
а - изменение свойств Distinct_Points_Only и Total_Distinct_Points;
б - точки генерации частиц
fn fPIL h = (
animationRange = interval 0f 50f
messageBox("Position_Icon Location")
for k = 0 to 5 do (
delete $*
esZ = 40
pF = PF_Source enable_Particles:true emitter_Type:1 pos:[0, 0, 0] \
rotation:(eulerToQuat (eulerAngles 0 0 -15)) quantity_Viewport:100 \
emitter_Length:eSz emitter_Width:eSz emitter_Height:eSz
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:4800 amount:50
if k == 5 then
opPI = position_Icon Location:(k - 1) \
distinct_Points_Only:on total_Distinct_Points:3
else
opPI = position_Icon Location:k
opDP = displayParticles color:green type:9 -- Asterisks (6 - Geometry)
apActFn pF #(opBth, opPI, opDP) false
playAnimation()
)
)
Число частиц, имеющихся в сцене, возвращает метод NumParticles интерфейсов MaxscriptParticleContainer и ParticleObjectExt. В частности, в рассматриваемой системе частиц при установке бегунка временной шкалы в точке 15f и после выбора иконки системы частиц выражение
$.NumParticles()
вернет число 100.
Это число вычисляется по следующей формуле
Также это число отображается в списке Selection интерфейса объекта PF_Source после перехода на вкладке Modify на подобъект Particle или Event объекта PF_Source и выбора всех частиц или события (рис. 1.64).
Рис. 1.64. Выбор частиц: а - подобъекты системы частиц; б - в выбранном событии присутствуют 100 частиц
При рождении частицы устанавливаются значения ее свойств ParticleIndex и ParticleID (индекс и идентификатор частицы). Тип свойства – Integer. Изначально значения этих свойств совпадают.
В общем случае индексы выбранных частиц содержит свойство – массив Selected_Particles объекта PF_Source. Пример. Выбрать интерактивно часть частиц и распечатать их индексы.
if classOf $ == PF_Source then
if $.Selected_Particles.Count == 0 then
messageBox("No particles selected")
else
for k in $.Selected_Particles do print k
Приведенный код вводится в верхней половине окна MAXSript Listener, выделяется и нажимается Shift + Enter.
Интерактивно, средствами группы Select by Particle ID интерфейса объекта PF_Source частицы можно выбирать по значению их свойства ParticleID. Задача. Выбрать частицы с индексами 2, 7 и 10 и распечатать состав выборки, употребив вышеприведенный код.
После нажатия на кнопку Add выборка очищается, если установлен флажок Clear Selection. Также указанная в поле ID частица будет удалена из выборки после нажатия на кнопку Remove. Задача. Удалить из выборки, выполненной в предшествующей задаче, частицу с индексом 7 и распечатать состав выборки, употребив вышеприведенный код. Задача. Применить для отображения частиц звездочки, а выбранных частиц – окружности. Решение приведено на рис. 1.65.
Рис. 1.65. Представление частиц системы
Это решение можно поддержать следующим кодом:
fn fDPS h = (
animationRange = interval 0f 50f
messageBox("Display Selected Particles Types")
for k = 1 to 3 do (
delete $*
esZ = 40
pF = PF_Source enable_Particles:true emitter_Type:1 pos:[0, 0, 0] \
quantity_Viewport:100 clear_Selection:off \
emitter_Length:eSz emitter_Width:eSz emitter_Height:eSz
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:0 amount:50
opPI = position_Icon Location:4
opDP = displayParticles color:green type:9 selected_Type:k
apActFn pF #(opBth, opPI, opDP) false
sliderTime = 10f
select pF
max modify mode
subObjectLevel = 1
pF.Selection_Level = 1
nP = pF.NumParticles()
for k2 = 1 to nP by 3 do pF.SetParticleSelected k2 true
playAnimation()
)
)
Положением генерируемых частиц можно также управлять, меняя размеры иконки эмиттера, устанавливая их в соответствующих полях группы Emitter Icon списка Emission интерфейса объекта PF_Source.
Анимация эмиттера вызовет перемещение сгенерированных им частиц. Характер движения зависит как вида преобразований координат эмиттера, так и от значений его свойств.
В примере в сцену вводится дуга, используемая затем в качестве пути для движения эмиттера (используется ограничение Path_Constraint).
fn fEm h = (
animationRange = interval 0f 100f
arrPI = #(#(off, off), #(off, on), #(on, off))
messageBox("Position_Icon: Lock_On_Emitter and Inherit_Emitter_Movement")
for k = 1 to 3 do (
delete $*
loe = arrPI[k][1]
iem = arrPI[k][2]
pth = arc radius:70 from:30 to:180 pie:off reverse:on \
pos:[25, 25, 0] wireColor:black
pF = PF_Source emitter_Type:1 quantity_Viewport:100
pc = path_Constraint follow:on axis:2 path:pth
pF.Pos.Controller = pc
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:4800 amount:200
opPI = position_Icon Location:4 \
Lock_On_Emitter:loe inherit_Emitter_Movement:iem
opDP = displayParticles type:1 color:(color 80 80 80)
apActFn pF #(opBth, opPI, opDP) false
playAnimation()
)
)
На рис. 1.66 приведен один кадр анимации при различных установках свойств оператора Position_Icon.
Рис. 1.66. Система частиц с перемещающимся эмиттером:
а – Lock_On_Emitter = Inherit_Emitter_Movement = off;
б – Inherit_Emitter_Movement = on; в – Lock_On_Emitter = on
Оператор Birth можно заменить на следующий birth Script-оператор:
opBS = birth_Script proceed_Script:"
on channelsUsed pCont do (
pCont.UseTime = true
pCont.UseAge = true
)
on proceed pCont do (
tS = pCont.GetTimeStart() as float
tE = pCont.GetTimeEnd() as float
if tE < 4800.0 then
for k = 1 to 10 do (
pCont.AddParticle()
pCont.ParticleIndex = pCont.NumParticles()
pCont.ParticleTime = tS
pCont.ParticleAge = 0
)
)"
В этом случае формирование события обеспечивается следующим вызовом:
apActFn pF #(opBS, opPI, opDP) false
Задача. Задать в качестве пути окружность и добавить анимацию масштабирования эмиттера, уменьшив в середине пути размеры его иконки в 5 раз и восстановив их в конце пути. Обеспечить рождение частиц на всей временной оси, а число частиц задать равным 1000. Просмотреть анимацию при различных значениях свойств Lock_On_Emitter и Inherit_Emitter_Movement оператора Position_Icon.
Решение.
fn fEm2 h = (
animationRange = interval 0f 100f
messageBox("Emitter motion")
arrPF = #(#(on, on), #(on, on), #(off, off))
for k = 1 to 3 do (
delete $*
sl = arrPF[k][1]
se = arrPF[k][2]
pth = circle radius:70 pos:[0, 0, 0] wireColor:black
if k == 3 do hide pth
pF = PF_Source emitter_Type:1 quantity_Viewport:100 \
show_Logo:sl show_Emitter:se
pc = path_Constraint follow:on axis:2 path:pth
pF.Pos.Controller = pc
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:16000 amount:1000
opPI = position_Icon Location:4 Lock_On_Emitter:off \
inherit_Emitter_Movement:off
opDP = displayParticles type:1 color:(color 80 80 80)
apActFn pF #(opBth, opPI, opDP) false
animate on (
if k == 1 then (
at time 0f pF.Scale = [1, 1, 1]
at time 50f pF.Scale = [0.2, 0.2, 0.2]
at time 100f pF.Scale = [1, 1, 1]
)
else (
at time 0f (pF.Scale = [1, 1, 1]; pth.Radius = 70)
at time 50f (pF.Scale = [0.2, 0.2, 0.2] ; pth.Radius = 35)
at time 100f (pF.Scale = [1, 1, 1]; pth.Radius = 70)
)
)
playAnimation()
)
)
Задача. Добавить в решение предшествующей задачи анимацию радиуса окружности, изменяя его по той же схеме, что коэффициенты масштабирования объекта PF_Source.
Решение.
animate on (
at time 0f (pF.Scale = [1, 1, 1]; pth.Radius = 70)
at time 50f (pF.Scale = [0.2, 0.2, 0.2] ; pth.Radius = 35)
at time 100f (pF.Scale = [1, 1, 1]; pth.Radius = 70)
)
Задача. Выполнить анимацию при скрытом пути, не показывая иконок эмиттера и его логотипа.
Задача. Удалить ключи анимации радиуса окружности и масштабирования объекта PF_Source. Преобразовать окружность в фигуру, напоминающую цифру 8, и повторить анимацию.
Решение.
Восстановить видимость пути, эмиттера и его логотипа. Удаления ключа выполняется в следующем порядке: выбрать объект – выбрать маркер ключа на временной шкале – правая кнопка мыши – Delete Key – выбрать, например Circle01: Radius, и удалить ключ – повторить операцию для всех имеющихся на временной шкале ключей.
Выбрать и скрыть объект PF_Source (правая кнопка мыши – Hide Selection).
Выбрать окружность и снабдить ее модификаторами Normalize Spline (например, с длиной сегмента в 20 единиц) и Edit_Spline.
Ввести в видовом порте Top цифру 8 как примитив (сплайн) Text, уровнять ее высоту с диаметром окружности и расположить внутри окружности (рис. 1.67).
Рис. 1.67. Окружность с модификатором Normalize Spline
Выбрать в стеке модификаторов Edit Spline, перейти на уровень подобъекта Vertex и отредактировать надлежащим образом сплайн (окружность), выбирая и перемещая его вершины, добавляя при необходимости новые вершины посредством инструментов Refine и Insert, выполняя иные преобразования.
Восстановить видимость иконки эмиттера и его логотипа, а затем удалить или скрыть примитив Text.
Воспроизвести анимацию (рис. 1.68).
Рис. 1.68. Частицы в виде цифры 8 (t = 100f)
Заметим, что проще подготовить путь в виде цифры 8 с одним сплайном, применив модификатор Edit Spline к тексту и удалив, находясь на уровне его подобъекта Spline внутренние сплайны цифры (рис. 1.69).
Рис. 1.69. Цифра 8 после употребления модификатора Edit Spline и удаления верхнего внутреннего сплайна
Кроме того, в виде пути может быть использована и исходная цифра, содержащая три сплайна: один внешний и два внутренних.
В следующем примере путь в виде цифры 8 используется при различных типах эмиттера (Rectangle, Box, Circle, Sphere):
fn fEm3 h = (
messageBox("Emitter_Type and Motion")
animationRange = interval 0f 100f
for k = 0 to 3 do (
delete $*
tS = 250
tPs = 50
eSz = 10
pth = text text:"8" size: tS pos:[0, -tPs, 0] wireColor:black
pF = PF_Source emitter_Type:k quantity_Viewport:100 \
show_Logo:on show_Emitter:on \
emitter_Length:eSz emitter_Width:eSz emitter_Height:eSz
pc = path_Constraint follow:on axis:2 path:pth
pF.Pos.Controller = pc
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:16000 amount:5000
opPI = position_Icon Location:4 Lock_On_Emitter:off \
inherit_Emitter_Movement:off
opDP = displayParticles type:1 color:(color 80 80 80)
apActFn pF #(opBth, opPI, opDP) false
playAnimation()
)
)
Домашнее задание. Ввести в сцену второй источник частиц и нарисовать из частиц двузначную цифру, например 45.
Рожденные частицы начнут двигаться, если им придать некоторую скорость или воздействовать на них одной или несколькими силами.
В примере в сцену вводится слабый незатухающий без порывов сферический ветер, в структурную схему системы частиц добавляется оператор Force, в список которого Force Space Warps добавляется ветер.
Программно эти действия поддерживаются следующим кодом:
fn fFc h = (
animationRange = interval 0f 100f
for k = 1 to 2 do (
delete $*
messageBox(if k == 1 then "Wind" else "Wind and Gravity")
wnd = wind windType:1 strength:0.01 decay:0 turbulence:0 pos:[60, 0, 0]
arrFc = #(wnd)
if k == 2 do (
grv = gravity strength:-0.01 gravityType:0 pos:[30, -30, 0]
append arrFc grv
)
pF = PF_Source enable_Particles:true emitter_Type:1 \
pos:[0, 0, 0] quantity_Viewport:100
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:4800 amount:200
opPI = position_Icon Location:4
opDP = displayParticles color:green type:9 -- Asterisks (6 - Geometry)
opFc = force force_Space_Warps:arrFc
apActFn pF #(opBth, opPI, opDP, opFc) false
playAnimation()
)
)
Задача. Добавить в сцену и систему частиц незначительную направленную вниз гравитацию. Пронаблюдать одновременное действие ветра и гравитации на частицы. Решение. В начало вышеприведенного кода добавляется следующее выражение:
Прочие строки кода остаются без изменений.
Структурная схема рассматриваемой системы частиц приведена на рис. 1.70.
Рис. 1.70. Структурная схема системы частиц с двумя силами
Силы действуют на все частицы текущего события. По умолчанию силы одинаково действуют на все частицы. Однако степень действия силы на частицу можно регулировать, изменяя, например в proceed-обработчике Sript_Operator, значение свойства ParticleFloat интерфейса MaxscriptParticleContainer.
В следующем примере сила ветра, действующая на частицу, тем выше, чем меньше ее идентификатор.
fn fPFlt h = (
animationRange = interval 0f 50f
delete $*
messageBox("Particle Float")
wnd = wind windType:1 strength:0.01 decay:0 turbulence:0 pos:[60, 0, 0]
pF = PF_Source enable_Particles:true emitter_Type:0 \
pos:[30, 0, 0] quantity_Viewport:100
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:0 amount:10
opPI = position_Icon distinct_Points_Only:on total_Distinct_Points:1
opDP = displayParticles color:black type:2 show_Numbering:on
opFc = force force_Space_Warps:#(wnd) useScriptWiring:1 use_Script_Float:1
opSO = script_Operator proceed_Script:"
on channelsUsed pCont do pCont.UseTime = pCont.UseFloat = true
on proceed pCont do (
nP = pCont.NumParticles()
for k in 1 to nP do (
pCont.ParticleIndex = k
-- The younger the particle, the higher the ParticleFloat value
if pcont.ParticleNew then pCont.ParticleFloat = 5.0 * pCont.ParticleID
)
)"
apActFn pF #(opBth, opPI, opDP, opFc, opSO) false
playAnimation()
)
Для наглядности число частиц уменьшено до десяти, и все они появляются в системе одновременно (opBth.Emit_Start = opBth.Emit_Stop = 0), причем на поверхности иконки эмиттера (opPI.Location = 3). Кроме того, рядом с частицами выводятся их идентификаторы (opDP.Show_Numbering = on), что позволяет наблюдать, как более свежие частицы обгоняют старые. (Если начальное и конечное время генерации частиц различны, то справедливо следующее: чем моложе частица, тем выше ее идентификатор.)
Действие свойства ParticleFloat обусловлено не только его обработкой оператором Script_Operator, но и заданием в операторе Force свойств UseScriptWiring = Use_Script_Float = 1.
Интерактивно задание этих свойств выполняется по следующей схеме: в контейнере события выбирать оператор Force – правая кнопка мыши – Use Script Wiring – раскрыть список Script Wiring интерфейса оператора Force – Influence.
Два кадра анимации приведены на рис. 1.71.
Рис. 1.71. Действие ветра и ParticleFloat на частицы в точках t = 22f и t = 30f
Задача. Изменить порядок действия свойства ParticleFloat, разгоняя старые частицы и замедляя более свежие. Решение.
if pcont.ParticleNew then pCont.ParticleFloat = 110.0 - 10.0 * pCont.ParticleID
В системе частиц PF Source имеются следующие три Speed-оператора:
Speed;
Speed By Icon;
Speed By Surface.
Оператор Speed задает величину скорости и ее направление для всех частиц, которые они сохраняют до тех пор, пока на частицы не оказанного иного воздействия, например в виде силы ветра.
Частицы, рождаемые в одной точке в центре эмиттера, при различных вариантах задания скорости отображены на рис. 1.72 (использован видовой порт Front).
Рис. 1.72. Частицы на фронтальной плоскости проекций
при различных вариантах задания скорости: а – Along Icon Arrow;
б – Icon Center Out и Icon Arrow Out; в – Random 3D; г – Random Horizontal
Для наглядности частицы рождаются на всем анимационном интервале, а оператор Position_Icon введен следующим выражением:
opPI = position_Icon Location:4 distinct_Points_Only:on total_Distinct_Points:1
Сам же оператор Speed вводится в код одноименным конструктором:
Свойство Divergence определяет предельно возможное отклонение частиц (в градусах) от заданного свойством Direction направления. Не используется, когда Direction = 3 (Random 3D). Пример действия свойства показан на рис. 1.73.
Рис. 1.73. Направление Icon Center Out: а – Divergence = 0; б – Divergence = 30
Следующий пример иллюстрирует совместное действие на частицы оператора Speed и анимации эмиттера. В примере частицы генерируются из одной точки эмиттера, имеющего форму окружности, и направляются вертикально вниз. После соприкосновения с плоскостью отражателя POmniFlect они останавливаются.
Введенный в сцену отражатель указывается в свойстве Collision_Nodes теста Collision, присутствующего в первом событии системы частиц. Для остановки частиц, достигших отражателя, свойство Speed_Option в тесте Collision устанавливается равным 2 (Speed Stop).
После достижения отражателя частицы передаются второму событию, в котором изменяется их форма и цвет.
Выполняемая анимация координат объекта PF_Source обеспечивает расположение частиц на плоскости в виде буквы А.
fn fEmSpdShSEvn h = (
animationRange = interval 0f 100f
msg = "Emitter motion, Speed and ShapeStandard "
sliderTime = 0f
rAng = (eulerAngles 0 10 0) as quat
pZ = 20
sl = se = on
for k = 1 to 3 do (
delete $*
if k == 3 then (
messageBox("Emitter motion and Position_Object (two Events)")
cnH = 30
cn = cone radius1:2 radius2:1 height:cnH pos:[0, 0, pZ] wireColor:green
cn2 = copy cn
cn2.Radius1 = 1; cn2.Height = 2
cn.Pivot = [0, 0, pZ + cnH]
rotate cn 190 [1, 0, 0]
sl = se = off
)
else
messageBox(msg + (if k == 1 then "(one Event)" else "(two Events)"))
pOF = POmniFlect pos:[-5, -5, -30] rotation:rAng width:80 height:80
hide pOF
pF = PF_Source enable_Particles:true emitter_Type:2 \
pos:[0, 0, 40] quantity_Viewport:100 show_Logo:sl show_Emitter:se
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:(62 *160) amount:50
if k == 3 then
opPO = position_Object Location:3 emitter_Objects:#(cn2) \
animated_Shape:on distinct_Points_Only:on total_Distinct_Points:1
else
opPI = position_Icon distinct_Points_Only:on total_Distinct_Points:1
opShS = shapeStandard shape:2 size:4
opSpd = speed speed:100
opDP = displayParticles color:red type:6 -- Geometry
tstCS = collision collision_Nodes:#(pOF) test_Type:0 speed_Option:2
if k == 3 then
apActFn pF #(opBth, opPO, opShS, opSpd, opDP, tstCS) false
else
apActFn pF #(opBth, opPI, opShS, opSpd, opDP, tstCS) false
if k > 1 do (
--
-- Event 2
opDP2 = displayParticles color:green type:6
evn2 = apActFn pF #(opDP2) false
tstCS.SetNextActionList evn2 tstCS
)
if k == 3 then
animate on (
at time 0f cn.Pos = cn2.Pos = pF.Pos = [-25, -30, pZ]
at time 20f cn.Pos = cn2.Pos = pF.Pos = [0, 30, pZ]
at time 40f cn.Pos = cn2.Pos = pF.Pos = [25, -30, pZ]
at time 50f cn.Pos = cn2.Pos = pF.Pos = [15, -7, pZ]
at time 60f cn.Pos = cn2.Pos = pF.Pos = [-15, -7, pZ]
)
else
animate on (
at time 0f pF.Pos = [-25, -30, pZ]
at time 20f pF.Pos = [0, 30, pZ]
at time 40f pF.Pos = [25, -30, pZ]
at time 50f pF.Pos = [15, -7, pZ]
at time 60f pF.Pos = [-15, -7, pZ]
)
playAnimation()
)
)
fEmSpdShSEvn 1
Два кадра анимации приведены на рис. 1.74, а структурная схема системы – на рис. 1.75.
Рис. 1.74. Система в точках t = 33f и t = 90f
Рис. 1.75. Структурная схема рассмотренной системы частиц (k = 2)
Если в систему частиц добавить оператор Position_Object, то в сцену можно ввести геометрический образ источника частиц, например в виде конуса. Это позволит наглядно имитировать процесс написания текста с перерывами, например, показывая частицы при наличии конуса в сцене, и прекращая их вывод при его отсутствии. При работе с Position_Object оператор Position_Icon может быть опущен.
Рассмотренный вариант анимации реализуется в вышеприведенном коде при k = 3.
В примере из частиц создается буква "А". Эмиттером частиц является коническое перо, определяемое посредством свойства Emitter_Objects оператора Position_Object. Для пополнения чернилами перо опускается в чернильницу.
Оператор позволяет использовать свою иконку для управления величиной и направлением скорости частиц. При анимации движение иконки оператора передается частицам.
В течение одной системной единицы времени (числа кадров в секунду) частицы, следуя за иконкой оператора, могут изменить скорость на величину, не превышающую значения свойства Accel_Limit.
В каждый момент времени применяемая скорость вычисляется с учетом значения скорости в предшествующий момент времени. Степень влияния траектории иконки оператора на траекторию частиц регулируется свойством Influence, изменяемым в диапазоне 0.0 – 100.0.
Частицы в процессе анимации будут следовать ориентации иконки оператора, если свойство User_Icon_Orientation установить в true (on).
При движении частицы будут смещаться к иконке оператора, если установлено в true свойство Steer_Towards_Trajectory и расстояние частицы от траектории иконки превышает Distance единиц.
В примере в событие вводится оператор SpeedByIcon, и для анимации его движения используется ограничение пути path_Constraint, в качестве которого берется задаваемый в программе сплайн. Частицы, покидая эмиттер (введен оператором Position_Icon), следуют вдоль траектории, перенимая движение иконки оператора SpeedByIcon.
Между двумя отражателями совершают колебательные движения частицы, имитирующие кипящий водоем. В 4-х местах этого водоема вверх устремляются частицы (рис. 1.78).
Корпус песочных часов создается из двух конусов. В сцену вводятся 2 отражателя. Первый POmniFlect-отражатель располагается в основании корпуса часов. А в качестве второго используется корпус часов.
Всего создаются 3 системы частиц. Частицы верхней системы заполняют одномоментно в t = 0f верхнюю часть часов, а затем устремляются вниз, исчезая при попадании в нижнюю часть корпуса. Средняя система частиц размещается в шейке часов. Ее частицы устремляются вниз и тормозятся POmniFlect-отражателем. Частицы, выходящие за пределы второго UOmniFlect-отражателя, уничтожаются. Частицы третьей системы генерируются в основании часов.
Совместное функционирование систем создает образ песочных часов.
Приведенный ниже код переносится в окно MAXSript редактора, а затем нажимается Ctrl+E (активно окно с кодом).
Анимации предшествуют 2 этапа. На первом запускается функция (fStSW 1), после исполнения которой будет открыта вкладка Modify отражателя UOmniFlect. Для завершения его формирования следует нажать на кнопку Pick Object секции Parameters и выбрать мышкой корпус часов.
На втором этапе исполняется функция (fPFSW 1), обеспечивающее инициализацию часов.
Анимация воспроизводится после запуска функции (fShSW 1): сначала показывается анимация на виде спереди, а затем в перспективе.
Функции tSttngs выполняет начальные установки, в том числе вывод фона в видовых портах Front и Perspective. Файл с фоном имеет имя white.bmp и должен находится по пути, указанном в глобальной переменной usbNm.
global usbNm = "E:\\"
global clr = [100, 100, 100] -- Some gray
global frmCnt, shMsg
global tSt, aRng
function apActFn pF arrOps hasRP = (
local k, evn
evn = event()
arrCnt = if hasRP then arrOps.Count - 1 else arrOps.Count
for k = 1 to arrCnt do evn.AppendAction arrOps[k]
if hasRP do pF.AppendAction arrOps[arrCnt + 1]
particleFlow.EndEdit()
pF.AppendInitialActionList evn
return evn
)
function tSttngs vPn frmCnt rtp gVsb dspBkg sef = (
delete $*
viewport.SetLayout #layout_4
viewport.ActiveViewport = vPn
if vPn == 2 then
viewport.SetType #view_front
else
viewport.SetType #view_persp_user -- Or: max vpt persp user
viewport.SetGridVisibility vPn gVsb
viewport.SetRenderLevel #smoothhighlights
viewport.SetShowEdgeFaces sef
fNmF = usbNm + "white.bmp" -- 525 * 336
if (getFiles fNmF).Count == 0 then
messageBox ("BkgImage File " + fNmF + " does not exist")
else (
viewport.DispBkgImage = dspBkg
setAsBackground (openBitmap fNmF)
completeRedraw()
)
sliderTime = 0f
animationRange = interval 0f frmCnt
timeConfiguration.RealTimePlayback = rtp
timeConfiguration.PlaybackLoop = false
timeConfiguration.PlaybackSpeed = 3 -- 1x
max tool zoomExtents
clearListener()
gc()
--max revert custom UI
return frmCnt
)
fn fStSW h = (
aRng = 300
tSt = 20
mtS = standard diffuse:clr showInViewport:true diffuseMapEnable:true opacity:20
global cnR1 = 20, cnR2 = 4, cnH = 40
global cnPs = [0, 0, -30]
global sF = 75, pSz = 3
global dZPln = pSz * sF /100
global pOFPs = cnPs + [0, 0, dZPln]
--
cn = cone radius1:cnR1 radius2:cnR2 height:cnH heightSegs:3 sides:12 \
material:mtS pos:cnPs
convertToPoly cn
-- polyop.DeleteFaces cn #{38} -- 74 if sides = 24
cn2 = copy cn
rotate cn2 180 [0, 1, 0]
global cn2Pos = cnPs + [0, 0, 2 * cnH]
cn2.Pos = cn2Pos
polyop.Attach cn cn2
shMdf = shell outerAmount:2
addModifier cn shMdf
global pOF = POmniFlect pos:pOFPs width:(2 * cnR1) height:(2 * cnR1) timeOff:aRng
global uOF = UOmniFlect timeOn:0f timeOff:aRng affects:100 refracts:100 \
diffusion:0 diffusionVar:0 radius:10 pos:[100, 0, 0] isSelected:on
hide #(pOF, uOF)
max modify mode
messageBox("Press Pick Object and Pick Sand Watch")
)
fn fPFSW h = (
clearSelection()
animationRange = interval tSt aRng
dZ = 0.1 * cnH
global pln = plane length:10 width:10 pos:[0, 0, 0]
hide #(pln)
global dZ0 = cnH + cnPs[3]
pAmt = 500
shE = off
dvg = 7
global pF = PF_Source enable_Particles:true emitter_Type:2 \
pos:(cn2Pos - [0, 0, dZ]) quantity_Viewport:100 \
emitter_Length:(2 * cnR1 - 1.5 * dZ) \
show_Logo:off show_Emitter:shE
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:0 amount:pAmt
opPI = position_Icon Location:3
opDP = displayParticles color:red type:6
opShS = shapeStandard shape:2 size:pSz
opSP = scaleParticles type:0 X_Scale_Factor:sF \
Y_Scale_Factor:sF Z_Scale_Factor:sF
opSpd = speed speed:50 direction:0
tstCS = collision collision_Nodes:#(uOF) test_Type:0 speed_Option:2
apActFn pF #(opBth, opPI, opDP, opShS, opSP, opSpd, tstCS) false
-- Event 2
particleFlow.BeginEdit()
opDP2 = displayParticles color:red type:6
opSO2 = script_Operator proceed_Script:"
on channelsUsed pCont do (
pCont.UseTime = true
pCont.UsePosition = true
)
on proceed pCont do (
if sliderTime > tSt do (
zDel = pln.Pos[3]
nP = pCont.NumParticles()
for k in 1 to nP do (
pCont.ParticleIndex = k
pPos = pCont.ParticlePosition
if pPos[3] >= zDel do pCont.DeleteParticle k
)
)
)"
evn2 = apActFn pF #(opDP2, opSO2) false
tstCS.SetNextActionList evn2 tstCS
--
pF_2 = PF_Source enable_Particles:true emitter_Type:2 \
pos:[0, 0, dZ0] quantity_Viewport:100 \
emitter_Length:cnR2 distinct_Points_Only:on total_Distinct_Points:1 \
show_Logo:off show_Emitter:shE
particleFlow.BeginEdit()
opBth_2 = birth emit_Start:(160 * tSt) emit_Stop:(0.95 * 160 * (aRng - tSt)) amount:pAmt
opPI_2 = position_Icon Location:3
opDP_2 = displayParticles color:red type:6
opShS_2 = shapeStandard shape:2 size:pSz
opSP_2 = scaleParticles type:0 X_Scale_Factor:sF \
Y_Scale_Factor:sF Z_Scale_Factor:sF
opSpd_2 = speed speed:100 direction:0 divergence:dvg
tstCS_2 = collision collision_Nodes:#(pOF) test_Type:0 speed_Option:2
apActFn pF_2 #(opBth_2, opPI_2, opDP_2, opShS_2, opSP_2, \
opSpd_2, tstCS_2) false
-- Event 2
particleFlow.BeginEdit()
opDlt2_2 = deleteParticles type:0
evn2_2 = apActFn pF_2 #(opDlt2_2) false
tstCS_2.SetNextActionList evn2_2 tstCS_2
--
global pF_3 = PF_Source enable_Particles:true emitter_Type:2 \
pos:pOFPs quantity_Viewport:100 \
emitter_Length:(2 * cnR1 - dZPln) \
show_Logo:off show_Emitter:shE
particleFlow.BeginEdit()
opBth_3 = birth emit_Start:(160 * tSt) emit_Stop:(160 * (aRng - tSt)) amount:pAmt
opPI_3 = position_Icon Location:3
opDP_3 = displayParticles color:red type:6
opShS_3 = shapeStandard shape:2 size:pSz
opSP_3 = scaleParticles type:0 X_Scale_Factor:sF \
Y_Scale_Factor:sF Z_Scale_Factor:sF
apActFn pF_3 #(opBth_3, opPI_3, opDP_3, opShS_3, opSP_3) false
)
fn fShSW h = (
pZMx = 0
with redraw off (
sliderTime = tSt
nP = pF.NumParticles()
for k in 1 to nP do (
pF.ParticleIndex = k
pPos = pF.ParticlePosition
pZMx = aMax #(pPos[3], pZMx)
)
pOFPsE = 0.9 * pZMx - 2 * dZ0
tgVal = (cnR1 - cnR2) / cnH as float
pF_3E_L = 2 * (cnR2 + (dZ0 - pOFPsE) * tgVal)
animate on (
at time tSt (
pOF.Pos = pOFPs
pln.Pos = [0, 0, pZMx]
pF_3.Pos = pOFPs
pF_3.Emitter_Length = 2 * cnR1 - dZPln
)
at time aRng (
pOF.Pos = cnPs + [0, 0, pOFPsE]
pln.Pos = [0, 0, dZ0]
pF_3.Pos = cnPs + [0, 0, pOFPsE]
pF_3.Emitter_Length = pF_3E_L
)
)
sliderTime = 20f
)
completeRedraw()
for k = 2 to 4 by 2 do (
viewport.ActiveViewport = k
if k == 2 do max tool zoomExtents
sliderTime = 20f
playAnimation()
)
)
aRng = tSttngs 4 300 true false true false
rt = quat 0.55 -0.2 -0.3 0.75
text text:"Sand Watch" size:20 wireColor:black rotation:rt pos:[0, -50, -50]
-- fStSW 1
-- fPFSW 1
-- fShSW 1
Реализована следующая история.
Жили-были синие частицы, видимо, не понимая, что их существование ограничено небольшим замкнутым пространством.
В один прекрасный день появилась красная частица, которая вскоре обнаружила, что она и синие сидельцы по сути живут за решеткой (рис. 1.80), и эта частица решила вырваться на свободу, но в одиночестве ее усилия оказались тщетны.
Рис. 1.80. Красная и синие частицы
Тогда она стала искать помощников, и они, о чудо, появились. Это были зеленые частицы. Под предводительством красной частицы они обрели свободу. За ними последовали и некоторые синие частицы (рис. 1.81).
Рис. 1.81. На свободу
Однако свобода требовала постоянных напряжений, поскольку "лишь тот достоин жизни и свободы, кто каждый день идет за них на бой". Поэтому синие частицы, а вслед и зеленые вернулись в свою клетку. Кроме того, последние, чтобы не выделяться, изменили свой цвет на синий.
Красная частица в одиночестве устремилась в "прекрасное далеко" (рис. 1.82).
Рис. 1.82. В прекрасное далеко
global usbNm = "E:\\"
global frmCnt
function apActFn pF arrOps hasRP = (
-- Код функции приведен выше
…
)
function tSttngs vPn frmCnt rtp gVsb dspBkg sef = (
-- Код функции приведен выше
…
)
frmCnt = tSttngs 4 1500 true false true false
sliderTime = 0f
--
kT = 0.7
--
pSz = 2
sph = sphere radius:(2.25 * pSz)
sph2 = sphere radius:pSz
hide #(sph, sph2)
spdVal = 30
pH = frmCnt / 10
pRedSpd = 0.03
--
rBF = rPF_ = rAF = rAF2 = rAF3 = gPF = rGAF = \
gRAF = bBF = gBF = gTBF = rCF = rFF = false
--
rB = 1 * pH; rBS = "Red particle is born"
rP = (1 * pH + 10); rPS = "Red particle is passsed"
rA = 2 * pH; rAS = "Red particle: attemp 1"
rA2 = 3 * pH; rAS2 = "Red particle: attemp 2"
rA3 = 4 * pH; rAS3 = "Red particle: attemp 3"
gP = 5 * pH; gPS = "Green particles are passsed"
rGA = 6 * pH; rGAS = "Red particle: attemp with green ones"
gRA = (6 * pH + 10); gRAS = "Green particles: attemp with red one"
bmDt = 8 * pH
bB = 9 * pH; bBS = "Black particles are back"
gB = 10 * pH; gBS = "Green particles are back"
gTB = 11 * pH; gTBS = "Green particles becomes black"
rC = (11 * pH + 10); rCS = "Red particle is back"
rF = (11 * pH + 100); rFS = "Red particle flies away"
--
rB *= kT
rP *= kT
rA *= kT
rA2 *= kT
rA3 *= kT
gP *= kT
rGA *= kT
gRA *= kT
bmDt *= kT
bB *= kT
gB *= kT
gTB *= kT
rC *= kT
rF *= kT
if rF > frmCnt do messageBox("Invalid time for particles events")
--
cbSz = 50
cbSz2 = cbSz / 2
--
mtR = standard diffuse:red showInViewport:true opacity:100
mtG = standard diffuse:green showInViewport:true opacity:100
mtB = standard diffuse:blue showInViewport:true opacity:100
--
opc0 = 25
mtS = standard diffuse:gray showInViewport:true opacity:opc0
opc2 = 50
mtS2 = standard diffuse:gray showInViewport:true opacity:opc0
bx = box length:cbSz width:cbSz height:(cbSz / 20) material:mtS \
lengthSegs:20 widthSegs:20 heightSegs:2 pos:[0, 0, cbSz / 2]
bx2 = box length:cbSz width:cbSz height:cbSz material:mtS2 \
lengthSegs:4 widthSegs:4 heightSegs:4 pos:[0, 0, -cbSz / 2]
lttc = Lattice strut_Radius:1 strut_Segments:1 strut_Sides:3 joint_Radius:2
addmodifier bx2 lttc
rotate #(bx, bx2) -10 [0, 0, 1]
meshBm = bomb strength:0.05 gravity:-0.05 detonation:bmDt \
minFragmentSize:2 maxFragmentSize:5 falloff:cbSz \
pos:(bx.Pos + [0, 0, bx.Height / 2]) spin:30
bindSpaceWarp bx meshBm
hide meshBm
animate on (
at time 0f (mtS.Opacity = opc0; mtS2.Opacity = opc0)
at time (rP - 1) mtS2.Opacity = opc2
at time rP mtS2.Opacity = 100
at time rA mtS2.Opacity = 100
at time (rA + 1) mtS2.Opacity = opc0
at time bmDt mtS.Opacity = opc0
at time (bmDt + 1) mtS.Opacity = 100
at time (gTB - 1) mtS2.Opacity = opc0
at time gTB mtS2.Opacity = 100
)
--
pOF = #()
pOFPvP3 = #()
append pOF (POmniFlect pos:[0, 0, 0] width:cbSz height:cbSz timeOff:frmCnt)
for k = 1 to 6 do append pOF (copy pOF[1])
move pOF[1] [0, 0, cbSz2]
move pOF[7] [0, 0, cbSz2]
move pOF[3] [0, 0, -cbSz2]
rotate pOF[2] (eulerAngles 0 90 0); move pOF[2] [cbSz2, 0, 0]
rotate pOF[4] (eulerAngles 0 -90 0); move pOF[4] [-cbSz2, 0, 0]
rotate pOF[5] (eulerAngles 90 0 0); move pOF[5] [0, -cbSz2, 0]
rotate pOF[6] (eulerAngles -90 0 0); move pOF[6] [0, cbSz2, 0]
for k = 1 to 6 do append pOFPvP3 pOF[k].Pivot
for k = 1 to 6 do pOF[k].Pivot = [0, 0, 0]
rotate pOF -10 [0, 0, 1]
-- Or
--rotate pOF (angleaxis -15 [0, 0, 1])
for k = 1 to 6 do pOF[k].Pivot = pOFPvP3[k]
hide pOF
pOF[1].TimeOff = bmDt
pOF[7].TimeOn = bB
--
pF = PF_Source enable_Particles:true emitter_Type:3 \
pos:[0, 0, 0] quantity_Viewport:100 \
show_Logo:off show_Emitter:off integration_for_Viewport:3
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:0 amount:100
opPI = position_Icon location:4 subframe_Sampling:on
opDP = displayParticles color:black type:6 -- Geometry
opMSt = material_Static()
opMSt.Assigned_Material = mtB
opSI = shape_Instance shape_Object:sph2
opSpd = speed direction:3 speed:spdVal variation:(spdVal / 2)
opSP = scaleParticles type:0 X_Scale_Factor:50 \
Y_Scale_Factor:50 Z_Scale_Factor:50
opSO = script_Operator proceed_Script:"
on channelsUsed pCont do (
pCont.UseTime = true
pCont.UseShape = true
pCont.UsePosition = true
)
on proceed pCont do (
if not rBF and sliderTime > rB then (
rBF = true
print rBS
nP = pCont.NumParticles()
pCont.AddParticle()
pCont.ParticleIndex = nP + 1
pCont.ParticleShape = sph.Mesh
)
if not bBF and sliderTime > bB then (
bBF = true
print bBS
nP = pCont.NumParticles()
for k = 1 to nP do (
pCont.ParticleIndex = k
pCont.ParticlePosition = (random [-cbSz2 + 5, -cbSz2 + 5, -cbSz2 + 1] \
[cbSz2 - 5, cbSz2 - 5, cbSz2 - 1])
)
)
)"
tstST = script_Test proceed_Script:"
on channelsUsed pCont do pCont.UseTime = true
on proceed pCont do (
if not rPF_ and rbF then (
rPF_ = true
print rPS
nP = pCont.NumParticles()
pCont.ParticleIndex = nP
pCont.ParticleTestStatus = true
pCont.ParticleTestTime = pCont.ParticleTime
)
)"
tstST_2 = script_Test proceed_Script:"
on channelsUsed pCont do pCont.UseTime = true
on proceed pCont do (
if not gPF and sliderTime > gP then (
gPF = true
print gPS
nP = pCont.NumParticles()
for k = 1 to nP by 4 do (
pCont.ParticleIndex = k
pCont.ParticleTestStatus = true
pCont.ParticleTestTime = pCont.ParticleTime
)
)
)"
tstCS = collision collision_Nodes:pOF test_Type:0 speed_Option:0
opRP = renderParticles type:2 split_Type:0
apActFn pF #(opBth, opPI, opSI, opSO, opDP, opMSt, opSpd, opSP, tstCS, tstST, tstST_2, opRP) true
--
-- Event 2
particleFlow.BeginEdit()
opDP2 = displayParticles type:6 color:red
opMSt2 = material_Static()
opMSt2.Assigned_Material = mtR
opSO2 = script_Operator proceed_Script:"
on channelsUsed pCont do (
pCont.UseTime = true
pCont.UseSpeed = true
pCont.UsePosition = true
)
on proceed pCont do (
if not rAF and sliderTime > rA then (
rAF = true
print rAS
pCont.ParticleIndex = 1
pCont.ParticleSpeed = [0, 0, pRedSpd]
pCont.ParticlePosition = [0, 0, 0]
)
if not rAF2 and sliderTime > rA2 then (
rAF2 = true
print rAS2
pCont.ParticleIndex = 1
pCont.ParticleSpeed = [pRedSpd, 0, 0]
pCont.ParticlePosition = [0, 0, 0]
)
if not rAF3 and sliderTime > rA3 then (
rAF3 = true
print rAS3
pCont.ParticleIndex = 1
pCont.ParticleSpeed = random [0, 0, 0] [pRedSpd, pRedSpd, pRedSpd]
pCont.ParticlePosition = [0, 0, 0]
)
if not rGAF and sliderTime > rGA then (
rGAF = true
print rGAS
pCont.ParticleIndex = 1
pCont.ParticleSpeed = [0, 0, pRedSpd]
pCont.ParticlePosition = [0, 0, 0]
)
if not rCF and sliderTime > rC then (
rCF = true
print rCS
pCont.ParticleIndex = 1
pCont.ParticleSpeed = [0, 0, 0]
pCont.ParticlePosition = [cbSz2 + 5, -cbSz2 - 5, cbSz2 + 5]
)
if not rFF and sliderTime > rF then (
rFF = true
print rFS
pCont.ParticleIndex = 1
pCont.ParticleSpeed = [0, -0.1 * pRedSpd, -0.1 * pRedSpd]
)
)"
tstCS2 = collision collision_Nodes:pOF test_Type:0 speed_Option:0
evn2 = apActFn pF #(opDP2, opMSt2, opSO2, tstCS2) false
-- Connect the second Event to the Script_Test
tstST.SetNextActionList evn2 tstST
--
-- Event 3
particleFlow.BeginEdit()
opSP3 = scaleParticles type:2 X_Scale_Factor:100 \
Y_Scale_Factor:100 Z_Scale_Factor:100
opDP3 = displayParticles type:6 color:green
opMSt3 = material_Static()
opMSt3.Assigned_Material = mtG
opSO3 = script_Operator proceed_Script:"
on channelsUsed pCont do (
pCont.UseTime = true
pCont.UseSpeed = true
pCont.UsePosition = true
)
on proceed pCont do (
if not gRAF and sliderTime > gRA then (
gRAF = true
print gRAS
nP = pCont.NumParticles()
for k = 1 to nP do (
pCont.ParticleIndex = k
pCont.ParticleSpeed = [0, 0, pRedSpd]
pCont.ParticlePosition = (random [-cbSz2 + 5, -cbSz2 + 5, 0] \
[cbSz2 - 5, cbSz2 - 5, 0])
)
)
if not gBF and sliderTime > gB then (
gBF = true
print gBS
nP = pCont.NumParticles()
for k = 1 to nP do (
pCont.ParticleIndex = k
pCont.ParticleSpeed = (random [0, 0, 0] [pRedSpd / 2, pRedSpd / 2, pRedSpd / 2])
pCont.ParticlePosition = (random [-cbSz2 + 5, -cbSz2 + 5, -cbSz2 + 1] \
[cbSz2 - 5, cbSz2 - 5, cbSz2 - 1])
)
)
)"
tstCS3 = collision collision_Nodes:pOF test_Type:0 speed_Option:0
tstST3 = script_Test proceed_Script:"
on channelsUsed pCont do pCont.UseTime = true
on proceed pCont do (
if not gTBF and sliderTime > gTB then (
gTBF = true
print gTBS
nP = pCont.NumParticles()
for k = 1 to nP do (
pCont.ParticleIndex = k
pCont.ParticleTestStatus = true
pCont.ParticleTestTime = pCont.ParticleTime
)
)
)"
evn3 = apActFn pF #(opSP3, opDP3, opMSt3, opSO3, tstCS3, tstST3) false
-- Connect the second Event to the Script_Test
tstST_2.SetNextActionList evn3 tstST_2
--
-- Event 4
particleFlow.BeginEdit()
opSP4 = scaleParticles type:2 X_Scale_Factor:50 \
Y_Scale_Factor:50 Z_Scale_Factor:50
opDP4 = displayParticles type:6 color:black
tstCS4 = collision collision_Nodes:pOF test_Type:0 speed_Option:0
evn4 = apActFn pF #(opSP4, opDP4, tstCS4) false
-- Connect the second Event to the Script_Test
tstST3.SetNextActionList evn4 tstST3
max tool zoomExtents
--
rt = quat 0.55 -0.2 -0.3 0.75
text text:"Red particle" size:14 wireColor:black rotation:rt pos:[40, -50, -50]
--
playAnimation()
В клетке находятся частицы (рис. 1.83, а). Время от времени небольшая крышка, расположенная в верхней части клетки, ненадолго открывается, и часть частиц устремляется наружу (рис. 1.83, б).
Рис. 1.83. Два кадра анимации: а - выход почти закрыт; б - крышка приоткрыта
Как и в предыдущем примере, доступное для частиц пространство ограничивается отражателями POmniFlect-отражателями, расположенными на сторонах клетки. Всего отражателей 8, пи этом 3 из них расположены на верхней грани клетки. Два из этих отражателей неподвижны, а один, имитирующий крышку, периодически открывается, позволяя части частиц покинуть клетку, а затем закрывается. Используемое в этом действии аффинное преобразование координат – Rotate.
Все отражатели указываются в качестве значения свойства Collision_Nodes теста Collision первого события системы частиц.
Частицы, покидающие клетку, меняют свой цвет с синего на красный.
Для отображения клетки употреблен модификатор Lattice (решетка).
В примере воздушные суда Красной Армии уничтожают наземные технические средства противника (рис. 1.85).
Рис. 1.85. Красная армия всех сильней
Воздушные суда Красной Армии создаются как частицы с формой звезды. Силы противника в первом событии второго потока частиц (потока частиц противника) – это box-частицы, а во втором перешедшая в него частица удаляется, а на ее место помещается примитив Box таких же размеров, как и частица. Эффект уничтожения этого примитива обеспечивается ассоциированной с ним mesh-бомбой Bomb.
Заметим, что такая бомба генерирует осколки из Mesh объекта, имеющие регулярную форму (в примере осколок состоит из прямоугольников). Для получения осколков нерегулярной формы следует использовать бомбу PBomb, которая связывается не с объектом, а с массивом частиц PArray и берет в качестве осколков частицы этого массива.
В примере отступающая мишура бытия открывает Троицу в окружении мерцающих звезд (рис. 1.86).
Рис. 1.86. Истина
За создание мишуры отвечают анимированные материалы (всего их создается 12), а два потока частиц обеспечивают воспроизведение мерцающих звезд. Троица отображается в результате употребления с плоскостью материала, диффузионная карта которого основана на образе, хранимом в файле rblv.jpg.
function apActFn pF arrOps hasRP = (
local k, evn
evn = event()
arrCnt = if hasRP then arrOps.Count - 1 else arrOps.Count
for k = 1 to arrCnt do evn.AppendAction arrOps[k]
if hasRP do pF.AppendAction arrOps[arrCnt + 1]
particleFlow.EndEdit()
pF.AppendInitialActionList evn
return evn
)
delete $*
fNm = "G:/rblv.jpg"
dMp = bitmapTexture fileName:fNm
sMt = standard diffuseMap:dMp showInViewport:true diffuseMapEnable:true
--meditMaterials[1] = sMt
pY = 265
pX = pY * 0.8
eW = 95
p = [0, 0, 0]
rs1 = 5
eX = (pX + eW)/ 2 + rs1
p2 = p + [eX, 0, 0]
p3 = p + [-eX, 0, 0]
pln = plane length:pY width:pX pos:p material:sMt
str = star radius1:rs1 radius2:10 fillet1:0 fillet2:0 numPoints:5 distort:0 pos:p2
convertToPoly str
hide str
tE = 1000
tE2 = 1.2 * tE
animationRange = interval 0f tE2
for m = 1 to 2 do (
pM = if(m == 1) then p2 else p3
pF = PF_Source enable_Particles:true emitter_Type:0 \
pos:pM quantity_Viewport:50 \
emitter_Length:pY emitter_Width:eW emitter_Height:0 \
show_Logo:off show_Emitter:on
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:(tE2 * 160) amount:2000
opPI = position_Icon Location:3
opDP = displayParticles color:yellow type:6 -- 9 - Asterisks (6 - Geometry)
opSI = shape_Instance shape_Object:str
opSP = scaleParticles type:0 X_Scale_Factor:50 \
Y_Scale_Factor:50 Z_Scale_Factor:100
tstAT = age_Test test_Value:(100 * 160) variation:0
opRP = renderParticles split_Type:0 -- 2 - Mesh for particle
apActFn pF #(opBth, opPI, opSI, opSP, opDP, tstAT, opRP) true
--
-- Event 2
particleFlow.BeginEdit()
opDlt2 = deleteParticles type:1 --life_Span:(100 * 160)
evn2 = apActFn pF #(opDlt2) false
tstAT.SetNextActionList evn2 tstAT
)
--
mClr = cellular()
mDnt = dent()
mMbl = marble()
mNs = noise()
mPMbl = perlin_Marble()
mPlnt = planet()
mSmk = smoke()
mSpckl = speckle()
mSplt = splat()
mStcc = stucco()
mSwrl = swirl()
mWtr = water()
arrM = #(mClr, mDnt, mMbl, mNS, mPMbl, mPlnt, mSmk, \
mSpckl, mSplt, mStcc, mSwrl, mWtr)
arrNg = #()
arrMCnt = arrM.Count
qt = eulerToQuat (eulerAngles 0 0 0.0)
qt2 = eulerToQuat (eulerAngles 0 0 270.0)
r1 = 75.0
r2 = 105.0
ngMtX = 5
ngMtY = 4
xP = 170.0
yP0 = yP = 100.0
xP2 = 270.0
yP2 = 200.0
dx = 2.0 * xP / (ngMtX - 1)
dy = 2.0 * yP / (ngMtY - 1)
k = 0
for kX = 1 to ngMtX do (
for kY = 1 to ngMtY do (
nMp = random 1 arrMCnt
dMp = arrM[nMp]
--nMp = 0
case nMp of (
1: (
dMp.DivColor1 = color 150 52 8
dMp.DivColor2 = color 187 152 124
animate on (
at time 0f dMp.Size = 5.0
at time tE dMp.Size = 10.0
)
)
2: (
dMp.Color1 = color 74 0 210
dMp.Color2 = color 226 231 87
swap dMp.Color1 dMp.Color2
animate on (
at time 0f dMp.Size = 100.0
at time tE dMp.Size = 300.0
)
)
3: (
dMp.Width = 0.75
swap dMp.Color1 dMp.Color2
animate on (
at time 0f dMp.Coords.Tiling = [0.1, 1, 1]
at time tE dMp.Coords.Tiling = [0.7, 1, 1]
)
)
4: (
dMp.Color1 = color 255 75 0
dMp.Color2 = color 100 50 200
dMp.Size = 5
animate on (
at time 0f dMp.ThresholdHigh = 0.5
at time tE dMp.ThresholdHigh = 0.7
)
)
5: (
animate on (
at time 0f dMp.Size = 10.0
at time tE dMp.Size = 100.0
)
)
6: (
dMp.Color4 = color 205 220 205
dMp.Color6 = color 155 40 15
animate on (
at time 0f dMp.ContinentSize = 5.0
at time tE dMp.ContinentSize = 10.0
)
)
7: (
dMp.Color1 = color 145 10 10
animate on (
at time 0f (
dMp.Exponent = 0.75
dMp.Phase = 1.0
)
at time tE (
dMp.Exponent = 2.0
dMp.Phase = 3.0
)
)
)
8: (
dMp.Size = 10
dMp.Color1 = color 180 0 120
swap dMp.Color1 dMp.Color2
animate on (
at time 0f dMp.Size = 100.0
at time tE dMp.Size = 200.0
)
)
9: (
dMp.Threshold = 0.4
animate on (
at time 0f dMp.Size = 60.0
at time tE dMp.Size = 20.0
)
)
10: (
dMp.Size = 10
dMp.Threshold = 0.4
animate on (
at time 0f dMp.Thickness = 0.05
at time tE dMp.Thickness = 0.35
)
)
11: (
animate on (
at time 0f dMp.Color_Contrast = 0.5
at time tE dMp.Color_Contrast = 1.5
)
)
12: (
dMp.WaveRadius = 2
dMp.WaveLenMax = 60
dMp.WaveLenMin = 40
dMp.Amplitude = 1.4
dMp.Color1 = color 45 30 255
dMp.Color2 = color 240 180 0
animate on (
at time 0f dMp.Phase = 1
at time tE dMp.Phase = 4
)
)
)
if nMp > 0 then (
sMt = standard diffuseMap:dMp showInViewport:true diffuseMapEnable:true
--meditMaterials[1] = sMt
)
r = random r1 r2
x = random 15.0 30.0
y = random -20.0 20.0
ng = nGon radius:r cornerRadius:0 nsides:3 circular:off scribe:1
convertToPoly ng
ng.Verts[1].pos += [x, y, 0]
ng.Rotation = random qt qt2
ng.Pos = [xP, yP, 1]
yP -= dy
if (nMp > 0) do (
uvMp = uvwMap mapType:0
addModifier ng uvMp
ng.Material = sMt
)
k += 1
arrNg[k] = ng
)
xP -= dx
yP = yP0
)
ngMt = ngMtX * ngMtY
k = ngMt / 2
for kX = 1 to ngMtX do (
for kY = 1 to ngMtY do (
k += 1
k2 = if (k > ngMt) then k - ngMt else k
ps = arrNg[k2].Pos
rnd = random 1 10
k3 = if(rnd < 9) then 1 else -1
x = if (ps[1] > 0) then xP2 * k3 else -xP2 * k3
y = if (ps[2] > 0) then yP2 * k3 else -yP2 * k3
animate on at time tE2 arrNg[k2].Pos = [x, y, 1]
)
)
viewport.ActiveViewport = 1 -- Top
backgroundColor = black
render framerange:(interval 0f tE2) outputwidth:320 outputheight:240
В примере иллюстрируется процесс шлифовки изделия на абразивном круге (рис. 1.89).
Рис. 1.89. Шлифовка
Приводимый ниже код устроен так, что при соприкосновении изделия с абразивным кругом создается 30 сферических частиц стандартной формы, размер которых уменьшается в 2 раза за счет масштабирования.
fn fPEmr h = (
delete $*
sliderTime = 0f
max tool zoomExtents
frmCnt = 100
msg = "Add Particles: Emery"
rt = quat 0.55 -0.2 -0.3 0.75
text text:msg size:15 wireColor:black rotation:rt pos:[5, 0, 40]
--
dnt = dent()
dnt.Coords.Tiling = [2, 2, 1]
mSt = standard diffuseMap:dnt showInViewport:true diffuseMapEnable:true
meditMaterials[1] = mSt
--
rT = eulerToQuat (eulerAngles 0 90 0)
r = 20
h = 10
chCl = chamferCyl radius:20 height:10 Fillet:1 pos:[0, r, -h / 2] rotation:rT \
height_Segments:1 fillet_Segments:1 sides:12 \
cap_Segments:1 material:mSt
cnH = 60
cn = cone radius1:1 radius2:2 height:cnH wireColor:gray
rotate cn (eulerAngles 0 -90 20)
cnPs = cn.Pos
chClCntrl = chCl.Rotation.Controller
-- addNewKey chClCntrl 0
-- addNewKey chClCntrl frmCnt
max tool animmode
set animate on
with redraw off (
sliderTime = 0f
chClCntrl.X_Rotation = 90; chClCntrl.Y_Rotation = 0; chClCntrl.Z_Rotation = -90
sliderTime = 100f
chClCntrl.X_Rotation = 90; chClCntrl.Y_Rotation = 720; chClCntrl.Z_Rotation = -90
)
set animate off
max tool animmode
global p2 = frmCnt / 3 - 10
animate on (
at time 0f cn.Pos = cnPs + [0, -40, 0]
at time p2 cn.Pos = cnPs
at time (p2 + 10) cn.Pos = cnPs
at time (2 * p2) cn.Pos = cnPs + [0, -40, 0]
at time (3 * p2) cn.Pos = cnPs
)
pF = PF_Source emitter_Type:0 quantity_Viewport:100 \
emitter_Length:3 emitter_Length:3 show_Logo:off show_Emitter:off
particleFlow.BeginEdit()
opPI = position_Icon location:4 distinct_Points_Only:on total_Distinct_Points:1
opShp = shapeStandard shape:2 size:1
opSP = scaleParticles type:0 X_Scale_Factor:50 \
Y_Scale_Factor:50 Z_Scale_Factor:50
opDP = displayParticles type:6 color:red
opSpd = speed speed:250 variation:50 divergence:10
opSO = script_Operator proceed_Script:"
on channelsUsed pCont do
pCont.UseTime = pCont.UseAge = true
on proceed pCont do (
if (sliderTime >= p2 and sliderTime <= p2 + 10) \
or sliderTime >= 3 * p2 then
pCont.AddParticles 30
)"
apActFn pF #(opSO, opPI, opDP, opSpd, opShp, opSP) false
timeConfiguration.PlaybackLoop = true
playAnimation()
)
В примере создаются системы частиц для двух следующих целей: одна совместно с объектом Blob воспроизводит поток жидкого вещества, а вторая генерирует частицы перенимающие форму ранее созданных полигональных объектов (см.°анимацию):
Движение каждой порции жидкого вещества обеспечивается соответствующим оператором SpeedByIcon, положение которого регулируется ограничением Path_Constraint. Форма частиц, позиционированных на плоскости, меняется в proceed-обработчике оператора Script_Operator при выходе частицы за пределы круга. При этом также обнуляется и скорость частицы.
function apActFn pF arrOps hasRP = (
local evn
evn = event()
arrCnt = if hasRP then arrOps.Count - 1 else arrOps.Count
for k = 1 to arrCnt do evn.AppendAction arrOps[k]
if hasRP do pF.AppendAction arrOps[arrCnt + 1]
particleFlow.EndEdit()
pF.AppendInitialActionList evn
return evn
)
fn addPth arrPt = (
pth = line pos:arrPt[1] wireColor:black
addNewSpline pth
for k = 1 to arrPt.Count do
addKnot pth 1 #smooth #curve arrPt[k]
updateShape pth
hide pth
return pth
)
fn oneTb arrPt strch2 std sldT sldT2 = (
pth = addPth arrPt
r = 3
cl = cylinder radius:r height:20 heightSegs:20 material:std
ep = edit_Poly animationMode:1
addModifier cl ep
select cl
max modify mode
subobjectLevel = 4 -- Polygon
ep.Select #Face #{482}
max tool animMode
set animate on
sliderTime = sldT
ep.SetOperation #Bevel
ep.BevelHeight = 0
ep.BevelOutline = 0
sliderTime = sldT2
ep.BevelHeight = 1
ep.BevelOutline = 3
max tool animMode
set animate off
subobjectLevel = 0
spd = spacePathDeform() -- PathDeform WSM
addModifier cl spd
spd.Path = pth
cl.Transform = pth.Transform -- Imitates "Move To Path" button
spd.Axis = 2 -- Axis Z
animate on (
at time sldT spd.Stretch = 0.1
at time sldT2 spd.Stretch = strch2
)
)
fn oneFlw arrPt sldT sldT2 aRng amt dst = (
cnt = arrPt.Count
pth = addPth arrPt
sliderTime = sldT
pF = PF_Source pos:arrPt[1] quantity_Viewport:100 emitter_Type:2 emitter_Length:(2 * dst)
bM = blobMesh wireColor:[200, 42, 42]
bM.BlobMeshOps.AddBlob pF
particleFlow.BeginEdit()
opBth = birth emit_Start:(160 * (sldT + 5)) emit_Stop:(160 * aRng) amount:amt
opPI = position_Icon()
opSBI = speedByIcon pos:arrPt[1] use_Icon_Orientation:off \
steer_Towards_Trajectory:on distance:dst isSelected:on
pc = path_Constraint follow:off axis:1 path:pth percent:0
opSBI.Pos.Controller = pc
opShS = shapeStandard shape:2 size:1
opDP = displayParticles type:0
apActFn pF #(opBth, opPI, opShS, opSBI, opDP) false
)
fn w sldT sldT2 aRng = (
clr2 = [47, 47, 0]
clr3 = [144, 186, 32]
clr4 = [165, 42, 42]
bx = box length:8 width:3 height:2 lengthSegs:3 widthSegs:1 heightSegs:1
convertToPoly bx
tp = taper amount:-0.25 primaryAxis:1 effectAxis:2 symmetry:off limit:off
tp.Gizmo.Pos = [0, 6, 0]
select bx
max modify mode
subobjectLevel = 4
bx.SetSelection #Face #{2..3, 5..6, 9..13}
modPanel.AddModToSelection tp
subobjectLevel = 0
convertToPoly bx
hide bx
bx2 = box length:1 width:3 height:10 lengthSegs:1 widthSegs:3 heightSegs:7
convertToPoly bx2
polyOp.ExtrudeFaces bx2 #{61} 4
polyOp.ExtrudeFaces bx2 #{33} 4
update bx2
hide bx2
global r2 = 75, ps2 = [0, -15, 0], crss = bx2.Mesh, amt2 = 30
df = 360 / amt2
global arrF = for k = 1 to amt2 collect df * (k - 1)
global arrF2 = #()
for k = 1 to amt2 do (
k3 = 0
fnd = 1
for k2 = 1 to 10 * amt2 do (
k3 = random 1 amt2
fnd = findItem arrF2 k3
if fnd == 0 then exit
)
if k3 == 0 do
for k3 = 1 to amt2 do (
fnd = findItem arrF2 k3
if fnd == 0 then exit
)
if fnd == 0 then arrF2 = append arrF2 k3
)
pF = PF_Source pos:ps2 quantity_Viewport:100 emitter_Type:2 emitter_Length:(2 * r2)
particleFlow.BeginEdit()
opBth = birth emit_Start:(160 * (sldT + 50)) emit_Stop:(160 * (aRng - 200)) amount:amt2
opPI = position_Icon()
opSI = shape_Instance shape_Object:bx
opDP = displayParticles color:clr2 type:6
opSO = script_Operator proceed_Script:"
on channelsUsed pCont do (
pCont.UsePosition = true
pCont.UseSpeed = true
pCont.UseShape = true
)
on proceed pCont do (
nP = pCont.NumParticles()
s = 0.0012
for k = 1 to nP do (
pCont.ParticleIndex = k
if pCont.ParticleNew then (
k3 = arrF2[k]
f = arrF[k3]
sx = s * cos f
sy = s * sin f
spd = [sx, sy, 0]
pCont.ParticlePosition = ps2 + 31000 * spd
pCont.ParticleSpeed = spd
)
else (
p = pCont.ParticlePosition - ps2
x = p[1]
y = p[2]
if x * x + y * y > r2 * r2 do (
pCont.ParticleSpeed = [0, 0, 0]
pCont.ParticleShape = crss
)
)
)
)"
opRP = renderParticles()
apActFn pF #(opBth, opPI, opSI, opDP, opSO, opRP) true
crcl = circle pos:ps2 radius:r2
convertToPoly crcl
std2 = standard diffuse:clr3 showInViewPort:true
crcl.Material = std2
meditMaterials[1] = std2
animate on (
at time sldT2 std2.Diffuse = clr3
at time aRng std2.Diffuse = clr4
)
)
delete $*
max tool zoomExtents
clr = [100, 100, 100]
std = standard diffuse:clr showInViewport:true opacity:75
ps = [0, 140, -10]
sp = sphere radius:25 segs:32 pos:ps wireColor:clr
wnd = wind windtype:1 turbulence:2 frequency:0 pos:ps
flx = flex samples:1 chase:on
addModifier sp flx
flx.FlexOps.AddForce wnd
--
aRng = 1000
dSld = 50
dSld2 = 200
sldT = 0
sldT2 = sldT + dSld
sldT3 = sldT2 + dSld2
sldT4 = sldT3 + dSld
sldT5 = sldT4 + dSld2
sldT6 = sldT5 + dSld
sldT7 = sldT6 + dSld2
amt = 3000
dst = 1.5
arrPt = #(ps, [0, 65, 30], [20, 40, 55], [40, 20, 45], [45, 0, 30], [40, 0, 0])
arrStrtch = #(10.15, 11.5, 11.6)
oneTb arrPt arrStrtch[1] std sldT sldT2
cnt = arrPt.Count
arrPt2 = (for k = 1 to cnt collect arrPt[cnt - k + 1]) + #(arrPt[1] - [0, 0, 50])
oneFlw arrPt2 sldT2 sldT3 aRng amt dst
arrPt = #(ps, [5, 65, 50], [15, -5, 40], [20, -15, 30], \
[20, -45, 20], [20, -55, 0])
oneTb arrPt arrStrtch[2] std sldT3 sldT4
cnt = arrPt.Count
arrPt2 = (for k = 1 to cnt collect arrPt[cnt - k + 1]) + #(arrPt[1] - [0, 0, 100])
oneFlw arrPt2 sldT4 sldT5 aRng amt dst
arrPt = #(ps, [-40, 65, 30], [-25, 10, 35], [-45, -10, 35], \
[-60, -30, 35], [-65, -35, 0])
oneTb arrPt arrStrtch[3] std sldT5 sldT6
cnt = arrPt.Count
arrPt2 = (for k = 1 to cnt collect arrPt[cnt - k + 1]) + #(arrPt[1] - [0, 0, 150])
oneFlw arrPt2 sldT6 sldT7 aRng amt dst
w sldT3 sldT5 aRng
animationRange = interval 0 aRng
sliderTime = 0f
--playAnimation()
backgroundColor = [200, 200, 200]
Рыба, работая хвостом, удаляется от начальной точки вдоль отрицательного направления оси Х, а затем возвращается в начальную точку (и так 2 раза). В точке с наименьшей Х-координатой рыба пускает 2 пузыря (ее рот закрыт), а при возвращении в начальную позицию рыба приоткрывает рот. Для генерации пузырей применена систему частиц PF Source, для придания толщины модели головы рыбы употреблен модификатор Shell. Полость рта рыбы закрашена розовым цветом, а граница полости рта - белым.
В примере создаются системы частиц для двух следующих целей: одна совместно с объектом Blob воспроизводит поток жидкого вещества, а вторая генерирует частицы перенимающие форму ранее созданных полигональных объектов (см.°анимацию):
delete $*
r = 20
r2 = r / 3
r3 = 2 * r2
h = 5 * r
sh = 3
n = 30
aRng = 300
dt = aRng / n as integer
ang = 15
bAng = 40
dMx = 40
ang2 = -15
ang3 = 5
nF = 10
sp = sphere radius:r segs:16
addModifier sp (shell outerAmount:sh)
convertToPoly sp
sp.SetSelection #Face #{65..128, 193..256}
sp.delete #Face
sp.SetSelection #Face #{1..8, 17..24, 33..40, 49..56, 65..72, 81..88, 97..104, 113..120}
sp.delete #Face
--rotate sp (eulerangles 0 0 90)
sp.createFace #(29,20,57,66)
sp.createFace #(20,11,48,57)
sp.createFace #(11,2,39,48)
sp.createFace #(2,1,38,39)
sp.createFace #(1,3,40,38)
sp.createFace #(3,12,49,40)
sp.createFace #(12,21,58,49)
sp.createFace #(21,30,67,58)
sp.SetSelection #Face #{65..72}
sp.setMaterialIndex 1 1
sp.SetSelection #Face #{33..64}
sp.setMaterialIndex 3 1
mmt = Multimaterial showInViewport:on
meditMaterials[1] = mmt
mmt.material[1].Diffuse = white
mbl = marble()
mbl.coords.Tiling = [4, 4, 1]
mmt.material[2].DiffuseMap = mbl
mmt.material[3].Diffuse = color 255 100 100
sp.material = mmt
scale sp [1, 1, 2]
sp2 = copy sp
rotate sp2 (eulerangles 0 0 180)
mbl2 = marble()
mbl2.coords.Tiling = [4, 4, 1]
std = standard diffuseMap:mbl2 showInViewport:on
cn = cone radius1:(r + sh) radius2:r2 height:-h heightSegs:10 material:mmt
convertToPoly cn
cn2 = cone radius1:r2 radius2:r3 height:(-0.25 * h) heightSegs:2 pos:[0, 0, -h]
convertToPoly cn2
cn.attach cn2 cn
cn.SetSelection #Face #{1..21, 23..292}
cn.setMaterialIndex 2 1
cn.SetSelection #Face #{241}
cn.setMaterialIndex 3 1
move cn [0, 0, 1]
y14 = cn.verts[290].pos.y -- coordSys local
y15 = cn.verts[291].pos.y -- coordSys local
y16 = cn.verts[292].pos.y -- coordSys local
y17 = cn.verts[293].pos.y -- coordSys local
y18 = cn.verts[294].pos.y -- coordSys local
y19 = cn.verts[295].pos.y -- coordSys local
x14 = cn.verts[314].pos.x -- coordSys local
x15 = cn.verts[315].pos.x -- coordSys local
x16 = cn.verts[316].pos.x -- coordSys local
x17 = cn.verts[317].pos.x -- coordSys local
x18 = cn.verts[318].pos.x -- coordSys local
x19 = 0
z = cn.verts[314].pos.z
cn.verts[314].pos = [x14, y14, z] -- coordSys local
cn.verts[315].pos = [x15, y15, z] -- coordSys local
cn.verts[316].pos = [x16, y16, z] -- coordSys local
cn.verts[317].pos = [x17, y17, z] -- coordSys local
cn.verts[318].pos = [x18, y18, z] -- coordSys local
cn.verts[319].pos = [x19, y19, z] -- coordSys local
cn.verts[320].pos = [-x18, y18, z] -- coordSys local
cn.verts[321].pos = [-x17, y17, z] -- coordSys local
cn.verts[322].pos = [-x16, y16, z] -- coordSys local
cn.verts[323].pos = [-x15, y15, z] -- coordSys local
cn.verts[324].pos = [-x14, y14, z] -- coordSys local
--
cn.verts[326].pos = [-x14, -y14, z] -- coordSys local
cn.verts[327].pos = [-x15, -y15, z] -- coordSys local
cn.verts[328].pos = [-x16, -y16, z] -- coordSys local
cn.verts[329].pos = [-x17, -y17, z] -- coordSys local
cn.verts[330].pos = [-x18, -y18, z] -- coordSys local
cn.verts[331].pos = [-x19, -y19, z] -- coordSys local
cn.verts[332].pos = [x18, -y18, z] -- coordSys local
cn.verts[333].pos = [x17, -y17, z] -- coordSys local
cn.verts[334].pos = [x16, -y16, z] -- coordSys local
cn.verts[335].pos = [x15, -y15, z] -- coordSys local
cn.verts[336].pos = [x14, -y14, z] -- coordSys local
--
p23 = sp.verts[23].pos
p27 = sp.verts[27].pos
rE = 3.0
ey = sphere radius:rE pos:p23 wireColor:[50, 50, 255]
ey2 = sphere radius:rE pos:p27 wireColor:[50, 50, 255]
ey.parent = sp
ey2.parent = sp
--
bnd = bend BendDir:90 BendAxis:2
addModifier cn bnd
gr = group #(sp, sp2, cn)
scale gr [1, 0.3, 1]
rotate gr (eulerangles 0 -90 0)
--gr.pos = [dMx, 0, -39]
ungroup gr
t = 0
animate on (
at time 0 (
cn.pos = [dMx, 0, -39]
sp.pos = [dMx, 0, -39]
sp2.pos = [dMx, 0, -39]
rotate sp (eulerangles 0 ang 0)
rotate sp2 (eulerangles 0 -ang 0)
)
at time 50 (
cn.pos = [0, 0, -39]
sp.pos = [1, 0, -39]
sp2.pos = [1, 0, -39]
rotate sp (eulerangles 0 (-ang + ang3) 0)
rotate sp2 (eulerangles 0 (ang - ang3) 0)
)
at time 100 (
cn.pos = [0, 0, -39]
sp.pos = [1, 0, -39]
sp2.pos = [1, 0, -39]
rotate sp (eulerangles 0 -ang3 0)
rotate sp2 (eulerangles 0 ang3 0)
)
at time 150 (
cn.pos = [dMx, 0, -39]
sp.pos = [dMx, 0, -39]
sp2.pos = [dMx, 0, -39]
rotate sp (eulerangles 0 ang 0)
rotate sp2 (eulerangles 0 -ang 0)
)
at time 200 (
cn.pos = [0, 0, -39]
sp.pos = [1, 0, -39]
sp2.pos = [1, 0, -39]
rotate sp (eulerangles 0 (-ang + ang3) 0)
rotate sp2 (eulerangles 0 (ang - ang3) 0)
)
at time 250 (
cn.pos = [0, 0, -39]
sp.pos = [1, 0, -39]
sp2.pos = [1, 0, -39]
rotate sp (eulerangles 0 -ang3 0)
rotate sp2 (eulerangles 0 ang3 0)
)
at time 300 (
cn.pos = [dMx, 0, -39]
sp.pos = [dMx, 0, -39]
sp2.pos = [dMx, 0, -39]
rotate sp (eulerangles 0 ang 0)
rotate sp2 (eulerangles 0 -ang 0)
)
for k = 1 to n do (
t += dt
at time t (
bnd.bendAngle = -bAng
bAng = -bAng
if mod k 4 == 0 then (
rotate selection (eulerangles 0 0 ang2)
ang2 = -ang2
)
)
)
)
--
global spM3
sp3 = sphere radius:4 segs:32 pos:[0, 0, -350]
spM3 = sp3.mesh
pF = PF_Source enable_Particles:true quantity_Viewport:100 Emitter_Type:2 Emitter_Length:3
particleFlow.BeginEdit()
opDP = displayParticles color:gray type:6 -- 1 - Dots; 2 - Ticks; 3 - Circles; 9 - Asterisks (6 - Geometry)
opRP = renderParticles()
opBthS = birth_Script proceed_Script:"
on channelsUsed pCont do (
pCont.UseTime = true
pCont.UseAge = true
pCont.UsePosition = true
pCont.UseSpeed = true
pCont.UseShape = true
)
on proceed pCont do (
nP = pCont.NumParticles()
if nP == 0 then (
pCont.addParticle()
pCont.particleIndex = 1
pCont.ParticleTime = 0
pCont.ParticleAge = 0
pCont.particleShape = spM3
pCont.particlePosition = [0, 0, -350]
pCont.particleSpeed = [0, 0, 0]
)
else (
pCont.particleIndex = 1
pAge = pCont.particleAge
if pAge == 60 or pAge == 100 or pAge == 210 or pAge == 250 do (
pCont.addParticle()
pCont.particleIndex = pCont.NumParticles()
pCont.ParticleTime = pAge
pCont.ParticleAge = 0
pCont.particleShape = spM3
pCont.particlePosition = [-45.5, 0, -35]
pCont.particleSpeed = [0, 0, 0.005]
)
)
)"
evn = event()
particleFlow.EndEdit()
evn.AppendAction opBthS
evn.AppendAction opDP
pF.AppendAction opRP
pF.AppendInitialActionList evn
--
sliderTime = 0f
animationRange = interval 0f aRng
backgroundColor = [177, 176, 241]
playAnimation()
В примере создается массив частиц, использующий в качестве эмиттера стандартный примитив чайник Teapot (Emitter = tp). Частицы создаются при t = 0f (Emitter_Start = 0f) из фрагментов чайника, число фрагментов не менее 200, поскольку FragChunkMinimum = 200. При этом сам чайник сохраняется.
Созданные частицы обладают небольшой линейной скоростью (Speed = 0.5 и Speed_Variation = 50) и спином (Spin_Time = 5f и Spin_Time_Variation = 50). Кроме того, введенная в сцену бомба PBomb, взрываемая в 10-м кадре (Start_Time = 10f) придает частицам дополнительный импульс, направление которого определяется положением бомбы и ее параметрами.
В примере чайник используется в качестве эмиттера массива частиц PArray. Решаемая задача – это демонстрация фрагмента, имеющего наибольшее число граней.
Порядок решения следующий:
Создать, используя PArray, неподвижные частицы из фрагментов чайника.
Создать, используя конструктор Mesher, объект класса Mesher.
Создать Editable_Mesh-объект (totalMesh) и определить его Mesh, как Mesh объекта Mesher. Число элементов в полученном объекте будет равно числу частиц (фрагментов чайника), созданных на этапе 1.
Последовательно удаляя элементы Editable_Mesh-объекта, запомнить в переменной nFMax максимальное число граней.
Восстановить объект totalMesh и найти первый элемент с числом граней, равным nFMax. Результат сохранить в переменной fragmentMesh.
Отобразить найденный элемент.
delete $*
tp = teapot radius:10 smooth:on segs:6 wireColor:red \
body:on handle:on spout:on lid:on mapCoords:off pos:[-40, 0, 0]
a = PArray name:"aNm" speed:0.0 emitter:tp wireColor:yellow \
fragment_Thickness:1 fragChunkMinimum:200
a.ParticleType = 2 -- Particle Type: Object fragments
a.ViewType = 2 -- Viewport Display:Mesh
a.FragmentMethod = 1 -- Object Fragment Controls: Number of Chunks
-- Assign the PArray to the Mesher
mshr = mesher name:"mshr" pick:a
classOf mshr -- Mesher
mshr.Pos = [40, 0, 0]
sliderTime = 25f
totalMesh = editable_Mesh pos:[0, -40, 0] name:"tM" wireColor:blue
totalMesh.Mesh = mshr.Mesh
update totalMesh
hide #(tp, a, mshr)
nfMin = totalMesh.Numfaces
nFMax = 0
while totalMesh.Numfaces != 0 do (
-- Get the mesh element the first face belongs to
el = meshOp.GetElementsUsingFace totalMesh #{1}
tMesh = meshOp.DetachFaces totalMesh el delete:true asMesh:true
nF = tMesh.NumFaces
if nF > nFMax then nFMax = nF
if nF < nFMin then nFMin = nF
)
nFMax
nFMin
totalMesh.Mesh = mshr.Mesh
fragmentMesh = editable_Mesh name:"fM" pos:[0, -60, 0]
while totalMesh.Numfaces != 0 do (
el = meshOp.GetElementsUsingFace totalMesh #{1}
tMesh = meshOp.DetachFaces totalMesh el delete:true asMesh:true
nF = tMesh.NumFaces
if nF == nFMax then (
fragmentMesh.Mesh = tMesh
update fragmentMesh
fragmentMesh.WireColor = color 225 198 87
exit
)
)
hide totalMesh
max tool zoomextents
Результат приведен на рис. 2.2.
Рис. 2.2. Элемент totalMesh с наибольшим числом граней
В примере на основе чайников в t = 0f создаются 4 массива неподвижных частиц (Speed = 0), с каждым из которых связывается бомба. Первая бомба взрывается при t = 10f, а каждая последующая с интервалом 20f. Геометрия взрыва деформируется демпфером Drag, препятствующим распространению частиц по осям X и Y (DampingX > 0, DampingY > 0, но DampingZ = 0).
delete $*
pCrd = 60; sT = 10f
arrTp = #(); arrA = #(); arrPB = #()
arrTp.Count = arrA.Count = arrPB.Count = 4
arrTp[1] = teapot radius:10 smooth:on segs:6 wireColor:red \
body:on handle:on spout:on lid:on mapCoords:off pos:[-pCrd, 0, 0]
arrA[1] = PArray name:"aNm" emitter_Start:0f speed:0 emitter:arrTp[1] wireColor:yellow \
fragment_Thickness:1 fragChunkMinimum:200 life:100f iconHidden:on
arrA[1].ParticleType = 2 -- Particle Type: Object fragments
arrA[1].ViewType = 2 -- Viewport Display:Mesh
arrA[1].FragmentMethod = 1 -- Object Fragment Controls: Number of Chunks
drg = drag dampingX:20 dampingY:20 dampingZ:0 pos:[0, 0, 0]
arrPB[1] = pBomb pos:[-pCrd, 0, 0] strength:0.1 chaos:80 icon_Size:10 start_Time:sT
for k = 2 to 4 do arrTp[k] = copy arrTp[1]
arrTp[2].Pos = [0, -pCrd, 0]
arrTp[3].Pos = [pCrd, 0, 0]
arrTp[4].Pos = [0, pCrd, 0]
for k = 2 to 4 do (
arrA[k] = copy arrA[1]; arrA[k].Emitter = arrTp[k]
arrPB[k] = copy arrPB[1]; arrPB[k].Pos = arrTp[k].Pos
arrPB[k].Start_Time = (20 * k - sT)
)
for k = 1 to 4 do (
bindSpaceWarp arrA[k] arrPB[k]
bindSpaceWarp arrA[k] drg
)
animate on (
at time 0 drg.DampingX = drg.DampingY = 20
at time 50 drg.DampingX = drg.DampingY = 40
)
hide arrTp
hide arrPB
hide drg
playAnimation()
В примере отображается только первая частица потока PF_Source. Ее форма определяется как отпечаток Snapshot составного объекта Mesher. Форма прочих PF_Source-частиц определяется как пустая TriMesh.
Отпечаток Snapshot – это объект типа Editable_Mesh, содержащий в рассматриваемом примере элементы сферической формы. На каждом шаге число элементов равно числу PF_Source –частиц.
Объект Mesher создается как Mesh из metaParticles-частиц (ParticleType = 1) массива частиц PArray. При этом эмиттером частиц является Editable_Mesh (nMesh = Editable_Mesh()), создаваемая в proceed-обработчике оператора Script_Operator потока частиц.
Число вершин и граней nMesh равно числу PF_Source –частиц. Особенность nMesh в том, что каждая ее грань построена на одной вершине (nMesh.Faces[k] = [k, k, k]), поэтому nMesh не видна. Правда, можно отобразить маркеры на месте вершин nMesh, установив ее свойство VertexTicks в on (рис. 2.4, а).
Рис. 2.4. Эмиттер и частицы: а - маркеры вершин одного экземпляра nMesh;
б - metaParticles-частицы массива частиц PArray
PArray, используя текущую nMesh в качестве эмиттера, генерирует metaParticles-частицы (рис. 2.4, б). Размер частиц Size = 5.
function apActFn pF arrOps hasRP = (
local evn
evn = event()
arrCnt = if hasRP then arrOps.Count - 1 else arrOps.Count
for k = 1 to arrCnt do evn.AppendAction arrOps[k]
if hasRP do pF.AppendAction arrOps[arrCnt + 1]
particleFlow.EndEdit()
pF.AppendInitialActionList evn
return evn
)
fn fPShp5 h = (
animationRange = interval 0f 100f
sliderTime = 0f
delete $*
messageBox("Metaball Mesh")
global a, empMesh, mshr
-- Create PArray with particles on vertices of the mesh
-- At all vertices (formation = 2); Use Rate (quantityMethod = 0)
-- Total_Number instead of Birth_Rate if Use Total (quantityMethod = 1)
a = PArray speed:0 formation:2 quantityMethod:1 birth_Rate:100 \
emitter_Start:0 emitter_Stop:0 \
viewPercent:100 subsampleCreationTime:off viewType:2 \
display_Until:100f life:100f growth_Time:0f fade_Time:0f
a.ParticleType = 1 -- Metaballs
a.MetaballAutoCoarsness = off
a.MetaballRenderCoarsness = 0.9
a.MetaballViewCoarsness = 0.9
a.Metaparticle_Tension = 1.0
a.Size = 5
empMesh = triMesh() -- Empty mesh to assign to all particles
mshr = mesher pick:a -- A Mesher Compound object
hide mshr
global nAmt = 30
pF = PF_Source enable_Particles:true quantity_Viewport:10
particleFlow.BeginEdit()
opBth = birth amount: nAmt emit_Stop:4800
opPI = position_Icon()
opSpd = speed speed:50
opRP = renderParticles()
opDP = displayParticles color:blue type:6
opSO = script_Operator proceed_Script:"
on channelsUsed pCont do (
pCont.UsePosition = true
pCont.UseTime = true
pCont.UseSpeed = true
pCont.UseShape = true
)
on proceed pCont do (
nP = pCont.NumParticles()
if nP > 0 and nP < nAmt then (
-- Disable screen redraws to keep speed up
with redraw off (
nMesh = editable_Mesh()
nMesh.NumVerts = nP
nMesh.NumFaces = nP
-- We will make a single vertex for each particle
for k in 1 to nP do (
nMesh.Verts[k] = random [-20, -20, -20] [20, 20, 20]
-- The faces references the same vertex 3 times
nMesh.Faces[k] = [k, k, k]
)
a.Emitter = nMesh
metaballMesh = snapshot mshr
-- Assign the empty mesh to all particles. This makes them invisible
pCont.SetShapeForAllParticles(empMesh)
-- First particle carries all the mesh data
pCont.SetParticleShape 1 metaballMesh.Mesh
delete #(nMesh, metaballMesh)
)
)
)"
apActFn pF #(opBth, opPI, opSO, opSpd, opDP, opRP) true
backgroundColor = white
renderWidth = 320; renderHeight = 240
render frameRange:(interval 0f 35f)
)
fPShp5 1
Два кадра анимации приведены на рис. 2.5.
Рис. 2.5. Поток частиц (его первая частица) при t = 10f и t = 30f
В коде координаты вершин nMesh случайным образом выбираются из указанного интервала. При необходимости можно создать из вершин геометрическую фигуру, например окружность (рис. 2.6), увеличивая ее радиус по мере роста числа PF_Source-частиц.
Рис. 2.6. Окружность на основе metaParticles-частицы
Для получения такого результата подойдет следующий proceed-обработчик:
on proceed pCont do (
nP = pCont.NumParticles()
if nP > 0 and nP < nAmt then (
with redraw off (
nMesh = editable_Mesh()
nMesh.NumVerts = nP
nMesh.NumFaces = nP
ang = 360 / nP
r = 10 + nP
for k in 1 to nP do (
x = r * sin (ang * k)
y = r * cos (ang * k)
nMesh.Verts[k] = [x, y, 0]
nMesh.Faces[k] = [k, k, k]
)
a.Emitter = nMesh
metaballMesh = snapshot mshr
pCont.SetShapeForAllParticles(empMesh)
pCont.SetParticleShape 1 metaballMesh.Mesh
delete #(nMesh, metaballMesh)
)
)
В примере чайник используется в качестве эмиттера массива частиц PArray. Созданные частицы облают нулевой скоростью. Падение частиц, составляющих чайник, обеспечивается введенной в сцену силой гравитации Gravity. В момент столкновения с отражателем частицы подвергаются воздействию бомбы и разлетаются по плоскости (рис. 2.7), совмещенной с отражателем.
Рис. 2.7. Осколки чайника на плоскости отражателя
Сборка чайника из осколков наблюдается в видовом порте благодаря введенному в конец кода циклу
for k = 1 to 100 do sliderTime -= 1
возвращающему ползунок временной шкалы из ее конца в ее начало.
Аналогичный результат можно получить при совместном употреблении PArray и PF_Source. Последовательность действий следующая:
Создать чайник, PArray и Mesher; указать чайник в качестве эмиттера PArray и получить Mesh из сгенерированных PArray частиц, употребив Mesher.
Создать отражатель и совместить с ним плоскость.
Создать PF_Source, необходимые операторы и тест столкновения. В proceed-обработчике Script_Operator выполнить totalMesh.Mesh = mesher.Mesh и создать PF_Source-частицы – по одной частице на элемент totalMesh (этот элемент является частицей чайника, полученного PArray и Mesher). Определить надлежащим образом свойства новой частицы (см. код).
Воспроизвести анимацию (см. рис. 2.7).
function apActFn pF arrOps hasRP = (
local evn
evn = event()
arrCnt = if hasRP then arrOps.Count - 1 else arrOps.Count
for k = 1 to arrCnt do evn.AppendAction arrOps[k]
if hasRP do pF.AppendAction arrOps[arrCnt + 1]
particleFlow.EndEdit()
pF.AppendInitialActionList evn
return evn
)
fn fShp4 h = (
animationRange = interval 0f 100f
delete $*
messageBox("Teapot")
global mshr, totalMesh, fragmentMesh, pr, pr2, nsg = true
sliderTime = 0f
pr = "Making fragments and assigning particles"
pr2 = "Assigned particles "
-- Disable screen redraw to speed up execution
with redraw off (
tp = teapot radius:20 smooth:on segs:6 \
body:on handle:on spout:on lid:on mapCoords:off
a = PArray speed:0 emitter:tp particleType:2 viewType:2 \
fragment_Thickness:1 fragmentMethod:1 fragChunkMinimum:100
mshr = mesher pick:a -- Assign the PArray to the Mesher
totalMesh = editable_Mesh()
fragmentMesh = editable_Mesh()
pOF = POmniFlect pos:[0, 0, -50] width:100 height:100
pln = plane pos:[0, 0, -50] length:100 width:100 wireColor:[135, 110, 8]
hide #(tp, a, mshr, totalMesh, fragmentMesh, pOF)
)
pF = PF_Source enable_Particles:true \
quantity_Viewport:100 show_Logo:off show_Emitter:on
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:0 amount:0
opSO = script_Operator proceed_Script:"
on channelsUsed pCont do (
pCont.UseTime = true
pCont.UseAge = true
pCont.UsePosition = true
pCont.UseShape = true
pCont.UseSpeed = true
)
on proceed pCont do (
if nsg then (
nsg = false
k = 0
print pr
totalMesh.Mesh = mshr.Mesh
while totalMesh.Numfaces != 0 do (
-- Get the mesh element the first face belongs to
el = meshOp.GetElementsUsingFace totalMesh #{1}
-- Detach the faces of the element
tMesh = meshOp.DetachFaces totalMesh el delete:true asMesh:true
-- Set the mesh of the fragment Emesh to the element
fragmentMesh.Mesh = tMesh
-- Get the center of the fragment mesh
meshCenter = fragmentMesh.Center
-- Adjust vertex location to center around the chunk pivot
fVCount = fragmentMesh.Verts.Count
for j in 1 to fVCount do (
curVert = getVert fragmentMesh.Mesh j
curVert = curVert - meshCenter
setVert fragmentMesh.Mesh j curVert
)
-- Move the pivot of the fragment to the original center of the fragment
fragmentMesh.Pivot = meshCenter
-- Give birth to a new particle
pCont.AddParticle()
k += 1
pCont.ParticleIndex = k
pCont.ParticleTime = 0
pCont.ParticleAge = 0
-- Set the particle's position to the original fragment position
pCont.ParticlePosition = meshCenter
-- Set the shape of the particle to the mesh of the fragment
pCont.ParticleShape = fragmentMesh.Mesh
pCont.ParticleSpeed = [0, 0, -0.005]
)
print (pr2 + k as string) -- Assigned particles
)
)"
opPI = position_Icon()
opDP = displayParticles color:green type:6
-- 0 - Bounce; 2 - Stop; 3 - Random
tstCllsn = collision collision_Nodes:#(pOF) test_Type:0 speed_Option:3
apActFn pF #(opBth, opPI, opDP, opSO, tstCllsn) false
playAnimation()
)
timeConfiguration.RealTimePlayback = true
timeConfiguration.PlaybackLoop = false
fShp4 1
Код вводит в сцену чайник tp (конструктор Teapot) и массив частиц pArr (конструктор PArray). Форму эмиттера определяет чайник (Emitter = tp). Образ чайника не нужен, поэтому он скрывается (hide tp).
Массив частиц используется только для разрушения эмиттера на фрагменты (ParticleType = 2). При этом указывается, что создается не менее 75 фрагментов (FragChunkMinimum = 75). Разрушение выполняется в нулевой момент времени мгновенно (Emitter_Start = Emitter_Stop = 0f). После разрушения скорость фрагментов равна нулю (speed = 0).
При воспроизведении анимации фрагменты под действием силы тяжести grv (конструктор Gravity) двигаются вниз. В момент времени tBm срабатывает бомба pb, введенная конструктором pBomb, и куски эмиттера разлетаются, продолжая движение вниз. При достижении отражателя pOF, созданного конструктором POmniFlect, движение вниз прекращается: частицы продолжают движение по плоскости отражателя.
Находясь в 3ds Max, мы можем проиграть анимацию начиная с последнего кадра и завершая нулевым. Для этого в окне Time Configuration (рис. 2.10) снимается флажок Real Time и устанавливается переключатель Reverse или Ping-Pong.
Рис. 2.10. Часть диалога Time Configuration
Эти установки, однако, не влияют на функционирование процедуры воспроизведения. Оно возможно только в возрастающем временном диапазоне.
Поэтому с целью отображения процесса сборки кусков чайника в процедуре воспроизведения, необходимо принять дополнительные меры, например следующие:
Хаос создается из фрагментов сферы и конуса с использованием двух PArray: сфера и конус подаются в качестве эмиттеров частиц соответствующего PArray. Разрушение объектов выполняется одномоментно в t = 0f. Из полученных фрагментов создаются два массива и затем реализуется анимация, состоящая в перемещении элементов массивов в случайно выбранные точки.
Чтобы вернуться от хаоса к исходной сцене, нужно либо проиграть анимацию в обратном направлении, либо инвертировать ключи. В примере реализован второй вариант.
Для повышения наглядности создается фон из мелких частиц, получаемых в результате разрушения 10 примитивов Box. Время детонации бомбы, разрушающей объект, выбирается произвольно из диапазона 50f – 700f.
Кроме геометрического хаоса в сцену вводится и цветовой хаос: материал каждого фрагмента определяется случайно задаваемым WireColor-цветом. К концу анимации хаос уничтожается: все фрагменты сферы имеют цвет [6, 135, 113], а конуса – [135, 110, 8].
Процесс реализуется следующим кодом:
global clr2 = [135, 110, 8]
global clr3 = [6, 135, 113]
--
fn mkMsh pArr pS tE tE2 clr = (
arrFrMsh = #()
mshr = mesher pick:pArr pos:pS
addModifier mshr (edit_Mesh())
totalMesh = editable_Mesh()
totalMesh.Mesh = mshr.Mesh
while totalMesh.Numfaces != 0 do (
el = meshOp.GetElementsUsingFace totalMesh #{1}
tMesh = meshOp.DetachFaces totalMesh el delete:true asMesh:true
frM = editable_Mesh()
frM.Mesh = tMesh
frM.Pivot = frM.Center
append arrFrMsh frM
)
mCnt = arrFrMsh.Count
for m = 1 to mCnt do (
ps = random [-100, -100, -100] [100, 100, 100]
frM = arrFrMsh[m]
fClr = frM.WireColor
sMt = standard diffuse:fClr showInViewport:true
frM.Material = sMt
animate on (
at time tE2 (
sMt.Diffuse = fClr
frM.Pos = ps
frM.Rotation = (quat 0 0 0 1)
)
at time tE (
sMt.Diffuse = clr
)
)
)
nK = 2
for m = 1 to mCnt do (
frM = arrFrMsh[m]
frPC = frM.Pos.Controller
frRC = frM.Rotation.Controller
arrV = #()
arrV2 = #()
frRC.X_Rotation.Controller.Keys[2].Value = random 360 720
frRC.Y_Rotation.Controller.Keys[2].Value = random 360 720
frRC.Z_Rotation.Controller.Keys[2].Value = random 360 720
for k = 1 to nK do (
xP = frPC.X_Position.Controller.Keys[k].Value
yP = frPC.Y_Position.Controller.Keys[k].Value
zP = frPC.Z_Position.Controller.Keys[k].Value
append arrV [xP, yP, zP]
xR = frRC.X_Rotation.Controller.Keys[k].Value
yR = frRC.Y_Rotation.Controller.Keys[k].Value
zR = frRC.Z_Rotation.Controller.Keys[k].Value
append arrV2 [xR, yR, zR]
)
for k = 1 to nK do (
k2 = nK - k + 1
if (k != k2) do (
v = arrV[k2]
frPC.X_Position.Controller.Keys[k].Value = v[1]
frPC.Y_Position.Controller.Keys[k].Value = v[2]
frPC.Z_Position.Controller.Keys[k].Value = v[3]
v2 = arrV2[k2]
frRC.X_Rotation.Controller.Keys[k].Value = v2[1]
frRC.Y_Rotation.Controller.Keys[k].Value = v2[2]
frRC.Z_Rotation.Controller.Keys[k].Value = v2[3]
)
)
)
hide #(mshr)
)
fn cmpttn5 h = (
delete $*
sliderTime = 0f
tE = 1000f
tE2 = 800f
animationRange = interval 0f tE
r = 20
cnPs = [0, 0, -40]
cn = cone radius1:r radius2:0 height:60 pos:cnPs heightSegs:15
zS = cn.Pos[3] + cn.Height + r
sph = sphere radius:r pos:[0, 0, zS] segs:32
pArr = PArray formation:0 numDistinctPoints:20 \
fragment_Thickness:2 fragChunkMinimum:100 \
quantityMethod:1 viewPercent:100 emitter_Start:0f emitter_Stop:0f \
display_Until:tE life:tE \
pos:[0, 0, zS] particleType:2 viewType:2 fragmentMethod:1
pArr2 = copy pArr
pArr.Emitter = sph
pArr2.Emitter = cn
mkMsh pArr [0, 0, zS] (tE - 50) tE2 clr3
mkMsh pArr2 cnPs (tE - 50) tE2 clr2
hide #(cn, sph, pArr, pArr2)
h = 15
bx = box length:100 width:100 height:h pos:[0, 0, -r - cn.Height - h] \
wireColor:[224, 86, 86]
for k = 1 to 10 do (
pS = random [-20, 80, 15] [100, 100, 20]
s = 10
bx2 = box length:s width:s height:s pos:pS wireColor:black \
lengthSegs:10 widthSegs:10 heightSegs:10
tD = random 50 700
mB = bomb strength:0.2 gravity:0.2 minFragmentSize:1 \
maxFragmentSize:10 spin:150 chaos:10 seed:(random 0 1000)
mB.Detonation = tD
bindSpaceWarp bx2 mB
)
)
cmpttn5 1
В примере 100-долларовая банкнота разрывается на частицы (рис. 2.12); полученные частицы сжимаются в область исчезающего размера.
Рис. 2.12. Начальная и конечная стадии разбиения купюры
Результат достигается после выполнения следующей последовательности действий:
В сцену вводятся плоскость, массив частиц, а также объект Mesher и два объекта Editable_Mesh.
Плоскость снабжается стандартным материалом, диффузионная карта которого построена на основе файла 100d.gif, содержащего образ 100-долларовой банкноты.
Частицы создаются из фрагментов плоскости, а объект Mesher содержит полигональные модели (Mesh) этих частиц.
Mesh объектов Editable_Mesh определяется как Mesh объекта Mesher.
На основе одного Editable_Mesh-объекта формируется массив arrMsh, каждый элемент которого содержит список граней соответствующего элемента анализируемого Editable_Mesh-объекта. Кроме того, создается и массив arrCntr, содержащий координаты центров элементов Editable_Mesh. (Напомним, что метод GetElementsUsingFace возвращает битовый массив граней, находимых в примере по параметру #{arrNF[1]}.) Элемент, грани которого включены в массив arrMsh, а координаты центра – в arrCntr, удаляется из анализируемой Editable_Mesh. Также грани, включенные в arrMsh, удаляются из массива arrNF.
Используя элементы полученного массива arrMsh, соответствующие элементы второй Editable_Mesh (ttlMsh2) располагаются по вращающейся и сжимающейся окружности. Затем элементы устремляются к центру этой окружности. На завершающем этапе анимации Editable_Mesh ttlMsh2 сжимается (Scale ttlMsh2 [0.1, 0.1, 1]).
В другом варианте анимации предусмотрена предварительная сортировка массива по убыванию числа граней элементов массива (qsort arrMsh cmpVN). В этом варианте элементы Editable_Mesh ttlMsh2 не будут, однако, размещены по окружности.
fn cmpVN el el2 = (
local d = el.Count - el2.Count
case of (
(d < 0): -1
(d > 0): 1
default: 0
)
)
delete $*
max tool zoomExtents
timeConfiguration.PlaybackLoop = false
sliderTime = 0f
aRng = 800
nA = aRng / 200
animationRange = interval 0f aRng
usbNm = "e:\\"
fNm = usbNm + "100d.gif"
dMp = bitmapTexture fileName:fNm
sMt = standard diffuseMap:dMp showInViewport:true diffuseMapEnable:true
meditMaterials[1] = sMt
pA = 0
ps = [20, 0, pA]
pln = plane length:90 width:200 lengthSegs:15 widthSegs:30 material:sMt pos:ps \
mapCoords:on realWorldMapSize:off
a = PArray name:"aNm" speed:0 emitter:pln material:sMt \
emitter_Start:0f emitter_Stop:0f \
fragment_Thickness:1 fragChunkMinimum:30 \
display_Until:aRng life:aRng pos:ps iconHidden:on
a.Speed_Variation = 0
a.Spin_Time = 0f
a.ParticleType = 2 -- Object fragments
a.ViewType = 2 -- Viewport Display:Mesh
a.FragmentMethod = 1 -- Object Fragment Controls: Number of Chunks
a.MaterialSource = 1
mshr = mesher pick:a
-- convertToMesh mshr
ttlMsh = editable_Mesh pos:ps
ttlMsh.Mesh = mshr.Mesh
ttlMsh.Material = sMt
ttlMsh2 = copy ttlMsh
ttlMsh2.Pos = ttlMsh.Pos
hide #(a, pln, mshr)
arrMsh = #()
arrCntr = #()
ttlMshNF = ttlMsh.NumFaces
arrNF = for k in 1 to ttlMshNF collect k
while arrNF.Count > 0 do (
el = meshOp.GetElementsUsingFace ttlMsh #{arrNF[1]}
el2 = el as array
append arrMsh el2
ttlMsh3 = copy ttlMsh
ttlMsh3.Pos = ttlMsh.Pos
tMsh = meshOp.DetachFaces ttlMsh3 el delete:true asMesh:true
frgmntMsh = editable_Mesh()
frgmntMsh.Mesh = tMsh
mshCntr = frgmntMsh.Center
append arrCntr mshCntr
for fc in el2 do (
k = findItem arrNF fc
deleteItem arrNF k
)
delete #(ttlMsh3, frgmntMsh)
)
hide #(a, pln, mshr, ttlMsh)
-- qsort arrMsh cmpVN
aCnt = arrMsh.Count
f = 0
dF = 360 / aCnt
r = 0.25 * (pln.Length + pln.Width)
select ttlMsh2
max modify mode
subobjectLevel = 3
animate on (
at time 50 (
for k = 1 to aCnt do (
el = arrMsh[k]
move ttlMsh2.Faces[el] [0, 0, 0]
)
rotate ttlMsh2 0 [0, 0, 1]
)
at time (2.5 * aRng / nA) (
for k = 1 to aCnt do (
el = arrMsh[k]
ps = arrCntr[k]
x = r * (cos f)
y = r * (sin f)
z = ps[3]
f += dF
ps2 = [x, y, z]
mv = ps2 - ps
move ttlMsh2.Faces[el] mv
arrCntr[k] = ps2
)
rotate ttlMsh2 0 [0, 0, 1]
)
at time (3 * aRng / nA) rotate ttlMsh2 -540 [0, 0, 1]
at time (3.5 * aRng / nA) (
scale ttlMsh2 [1, 1, 1]
for k = 1 to aCnt do (
el = arrMsh[k] as bitArray
ps = arrCntr[k]
z = ps[3]
ps2 = [0, 0, z]
mv = ps2 - ps
move ttlMsh2.Faces[el] mv
)
)
at time (4 * aRng / nA) scale ttlMsh2 [0.1, 0.1, 1]
)
-- backgroundColor = [6, 135, 113] -- [135, 110, 8]
playAnimation()
В примере огонь (рис. 2.13) создается средствами массива частиц PArray и эффекта Lens Effects Glow, вводимого в сцену как событие Image Filter Event.
Рис. 2.13. Огонь с PArray и Lens Effects Glow
Движение частиц обеспечивается ветром. Кроме того, для PArray задаются такие свойства, как Material, GbufferChannel, Motionblur, ImageMotionBlurMultiplier и MotionBlurOn. Указанные свойства (кроме Material) можно задать в окне свойств объекта, открываемом при выбранном объекте после нажатия на правую кнопку мыши выбора Object Properties.
После выполнения следующего кода
Для Perspective (событие Scene Event) устанавливается флажок Scene Motion Blur, а в качестве программы воспроизведения выбирается Default Scanline Renderer.
Сцена воспроизводится после нажатия в Video Post на кнопку Execute.
Наилучший эффект достигается при работе с черным фоном.
В примере посредством конструктора BlobMesh (капля) создается Mesh из частиц большого спрея (BlobMeshOps.AddBlob ssp). Далее эта Mesh средствами объекта Scatter тиражируется (Duplicates = 10) и распределяется по сфере (рис. 2.15).
После выполнения приведенного кода создаются сфера sph, большой спрей ssp и составной объект BlobMesh (капля) bM; выбран объект bM. Далее интерактивно создается составной объект распределитель Scatter (рис. 2.16, а).
Рис. 2.16. Scatter: а - ввод в сцену распределителя;
б - выбраны источник и цель распределителя; в - параметры цели
Его первым операндом будет bM. В качестве второго операнда после нажатия на кнопку Pick Distribution Object одноименной секции выбирается сфера sph и переключатель Distribution устанавливается в положение Use Distribution Object (рис. 2.16, б).
Затем переключатель Distribute Using устанавливается в Skip N 2 (рис. 2.16, в).
Прочие свойства Scatter не изменяются (берутся значения, заданные После чего выполняется следующий код:
В примере посредством конструктора Mesher (создатель Mesh) создается Mesh из частиц большого спрея (mesher pick:ssp). Далее эта Mesh средствами объекта Scatter тиражируется (Duplicates = 9) и распределяется по поверхности сферы (рис. 2.17).
После выполнения приведенного кода создаются сфера sph, большой спрей ssp и составной объект Mesher msh; выбран объект msh. Далее интерактивно создается составной объект распределитель Scatter с переключателем Distribute Using, установленным в положение Area. После чего выполняется следующий код:
В примере вертолет поражает цель. Роль оружия выполняет большой спрей (рис. 2.20).
Рис. 2.20. Атака
В первой части кода создаются вертолет и траектория его движения, во второй – цель, бомба, разрушающая цель, и большой спрей в качестве стрелкового оружия.
В примере пламя (рис. 2.21) имитируется средствами системы частиц Blizzard (вьюга, пурга).
Рис. 2.21. Blizzard-пламя с деформированными сферами в качестве частиц
Генерируемые системой частицы имеют форму сферы sph с радиусом в 4 единицы (ParticleType = 2, InstancingObject = sph). К сфере применен модификатор Noise (шум), искажающий ее форму. Также система использует материал, назначенный этой сфере (MaterialSource = 1).
Получение материала системой частиц выполняется после запуска кода интерактивно. Для этого в секции Particle Generation нажимается кнопка Get Material From. После чего можно воспроизвести анимацию.
В материале сферы определены два цвета и одна карта: диффузионный, излучения и карта прозрачности, как Smoke (дым).
Всего генерируется 100 частиц, время жизни частицы берется из интервала 5f - 75f (Life = 40f, Life_Variation = 35f)