Список работ

Система частиц Particle Flow

Содержание

ВВЕДЕНИЕ

Рассматриваются механизмы управления частицами средствами 3ds Max и языка программирования MAXSript. Для освоения рассматриваемых механизмов не требуется навыков работы с 3ds Max и MAXSript. Желательно, однако, иметь некоторый опыт программирования на любом языке высокого уровня.
Управлять частицами – это в каждый момент времени соответствующим образом регулировать количество частиц в сцене, изменять их координаты, скорость, форму, цвет и прочие свойства частиц. Все средства, необходимые для решения указанных задач, имеются.
Дополнительно освещаются методы работы с материалами и картами цветов, силами и отражателями. Так же даются сведения о механизме анимации координат и параметров объектов и о некоторых модификаторах приложения.
Рассматриваемые системы частиц позволяют, в частности, воспроизводить различные природные явления, например огонь, дым, снег, дождь или песчаную бурю, анимировать различные процессы, например разрушение предмета на фрагменты или его сборку из уже имеющихся фрагментов (обратный разрушению процесс).

1. ПОТОК ЧАСТИЦ. PARTICLE FLOW

1.1. СИСТЕМНЫЕ УСТАНОВКИ

При работе с системами частиц будем придерживаться приведенных на рис. 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.

        Time Configuration

Рис. 1.2. Окно Time Configuration

Окно Time Configuration открывается при нажатии на кнопку Time Configuration нижней панели инструментов.
Приведенные установки обеспечит следующий код:

animationRange = interval 0f 100f
timeConfiguration.PlayActiveOnly = true
timeConfiguration.RealTimePlayback = true
timeConfiguration.PlaybackLoop = true
timeConfiguration.PlaybackSpeed = 3    -- 1x

В некоторых примерах используются иные временные характеристики.

1.2. ОПИСАНИЕ СИСТЕМЫ PARTICLE FLOW

Система Particle Flow (поток частиц) обладает наиболее широкими возможностями управления частицами по сравнению с иными имеющимися в 3ds Max системами, такими, как Snow (снег), Spray (распылитель), Blizzard (вьюга, пурга, буран) и др.
Управление частицами реализуется в виде последовательности связанных событий. Каждое событие содержит некоторый набор операторов и тестов, влияющих на поведение частиц и способ их отображения.
По умолчанию частицы генерируются в области расположении эмиттера объекта PF_Source, вводимого в сцену либо интерактивно после выбора на командной панели Particle Flow - Particle Flow, либо программно следующим конструктором:

pF = PF_Source()

В конструкторе или отдельными выражениями могут быть определены значения свойств объекта, например:

pF = PF_Source enable_Particles:true quantity_Viewport:100

Для неуказанных свойств берутся заданные по умолчанию значения.
Эмиттер отображается, если свойство 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, открываемой после выбора системы либо интерактивно (Particle View– список Setup – кнопка Particle View), либо программно:

particleFlow.OpenParticleView()

Созданная интерактивно система частиц имеет название Standard Flow, содержит объект PF_Source и одно событие со стандартным набором операторов и представляется в форме Particle View приведенными на рис. 1.4 образами.

        Event 01

Рис. 1.4. Структурная схема создаваемой по умолчанию системы Particle Flow

Программно Standard Flow вводится в сцену следующим кодом:

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 -- Tetra
-- Better
opShS = ShapeLibrary()
opShS.'3D_Type' = 18    -- Tetra
opDP = displayParticles color:pF.WireColor type:6
opRP = renderParticles type:2
--
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
--
pF.SetPViewLocation 0 0
evn.SetPViewLocation 0 100
particleFlow.OpenParticleView()

Код набирается в 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, открываемого при нажатии на кнопку Time Configuration нижней панели инструментов.
Кроме этих свойств, конструктор Birth задает значение свойства Amount, устанавливающее общее число генерируемых частиц.
После нажатия на кнопку Play Animation частицы наблюдаются в активном видовом порте (рис. 1.5).

        Частицыв форме Tick

Рис. 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.

        Частицыв форме Tetra

Рис. 1.6. Частицы Standard Flow в окне воспроизведения

По умолчанию свойство Quantity_Viewport = 50. То есть при просмотре анимации в видовом порте генерируется половина от числа частиц, которые окажутся в видовом порте в заданный момент времени, если Quantity_Viewport = 100. В рассматриваемом случае в точке t = 10f при Quantity_Viewport = 100 метод NumParticles вернет число 67.

1.3. ОПТИМИЗАЦИЯ КОДА

Для сокращения кода, приводимого в пособии, следующая часть кода

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 будет опускаться. Однако при запуске примеров, имеющих обращения к этой функции, она должна быть доступна.

1.4. ПРОГРАММНОЕ УПРАВЛЕНИЕ ЧАСТИЦАМИ

Осуществляется в скриптах 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 цилиндра и конуса, вводимых в сцену и скрываемых начальными строчками кода.

        Cone- и cylinder-частицы форме Tetra

Рис. 1.7. Система, порождающая частицы двух форм: а - структурная схема; б - сцена при t = 30f

1.5. ЧИСЛО ОБРАЩЕНИЙ К PROCEED_SCRIPT-ОБРАБОТЧИКАМ

В нижеприводимом коде вычисляется число вызовов различных обработчиков оператора 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.

1.6. КАНАЛЫ УПРАВЛЕНИЯ ЧАСТИЦАМИ

Каналы управления частицами указываются в обработчике ChannelsUsed свойства Proceed_Script операторов Birth_Script, Script_Operator и Script_Test. По каналам, получившим статус true, можно получать и изменять значения соответствующих свойств частиц. Могут быть использованы следующие каналы:

<particeContainer>.useTime : bool : Read|Write
<particeContainer>.useAge : bool : Read|Write
<particeContainer>.useLifespan : bool : Read|Write
<particeContainer>.useEventTime : bool : Read|Write
<particeContainer>.usePosition : bool : Read|Write
<particeContainer>.useSpeed : bool : Read|Write
<particeContainer>.useAcceleration : bool : Read|Write
<particeContainer>.useOrientation : bool : Read|Write
<particeContainer>.useSpin : bool : Read|Write
<particeContainer>.useScale : bool : Read|Write
<particeContainer>.useTM : bool : Read|Write (Transformation Matrix)
<particeContainer>.useSelected : bool : Read|Write
<particeContainer>.useShape : bool : Read|Write
<particeContainer>.useMtlIndex: bool: Read|Write
<particeContainer>.useMapping: bool: Read|Write
<particeContainer>.useInteger : bool : Read|Write
<particeContainer>.useFloat : bool : Read|Write
<particeContainer>.useVector : bool : Read|Write

Например:

on channelsUsed pCont do (
    pCont.UseTime = true        -- Активизируется канал Time
    pCont.UseSpeed = true        -- Активизируется канал Speed
    pCont.UseInteger = true        -- Активизируется канал Integer
)

1.7. ОПЕРАТОРЫ И ТЕСТЫ СИСТЕМЫ ЧАСТИЦ PARTICLE FLOW

Операторы, управляющие частицами, разделяются на следующие категории:
  1. Генерация и удаление (Birth, BirthScript и DeleteParticles).
  2. Преобразование координат (Position_Icon, Position_Object, Rotation, ScaleParticles и Spin).
  3. Скорость (Keep_Apart, Speed, Speed_By_Surface и SpeedByIcon).
  4. Форма (ShapeStandard, Shape_Facing, Shape_Instance и Shape_Mark).
  5. Материал (Mapping, Material_Dynamic, Material_Frequency и Material_Static).
  6. Иные (Force и Script_Operator).
  7. Метаоператоры (Cache, Notes, Event, RenderParticles и DisplayParticles).
Кроме операторов, поведение частиц регулируется тестами, например Age_Test (тест возраста) или Collision (столкновение). Частицы, успешно завершившие тест, могут быть переданы следующему событию.
В форме Particle View оператор или тест можно выбрать на складе и перетащить мышкой в подходящую строку нужного события.
Объекты и тесты системы частиц принадлежат классу Helper, что устанавливается следующим кодом:

opBth = birth()                -- $Birth:Birth 04 @ [0.000000,0.000000,0.000000]
classOf opBth                    -- Birth
superClassOf opBth            -- Helper

1.8. ПРИМЕРЫ УПОТРЕБЛЕНИЯ ОПЕРАТОРОВ И ТЕСТОВ СИСТЕМЫ ЧАСТИЦ PARTICLE FLOW

1.8.1. ОПЕРАТОР POSITION_ICON И ТЕСТ COLLISION

Оператор 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 обеспечивает расположение частиц на плоскости в виде буквы А.

delete $*
rAng = (eulerAngles 0 10 0) as quat
pOF = POmniFlect pos:[-5, -5, -30] rotation:rAng width:80 height:80
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:(62 * 160) amount:50
opPI = position_Icon distinct_Points_Only:on total_Distinct_Points:1
opShS = shapeStandard shape:2 size:4
opSpd = speed speed:200
opDP = displayParticles color:red type:6    -- Geometry
tstCS = collision collision_Nodes:#(pOF) test_Type:0 speed_Option:2
apActFn pF #(opBth, opPI, opShS, opSpd, opDP, tstCS) false
--
-- Event 2
opDP2 = displayParticles color:green type:6
evn2 = apActFn pF #(opDP2) false
tstCS.SetNextActionList evn2 tstCS
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]
)
timeConfiguration.RealTimePlayback = true
timeConfiguration.PlaybackLoop = false
playAnimation()

Два кадра анимации приведены на рис. 1.8, а структурная схема системы - на рис. 1.9.

        Рисуем частицами букву A

Рис. 1.8. Система в точках t = 33f и t = 90f

        Рисуем частицами букву A

Рис. 1.9. Структурная схема рассмотренной системы частиц

1.8.2. ОПЕРАТОР SHAPE_MARK

Аналогичный результат можно получить, если в предыдущем примере вместо теста 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.

        Shape_Mark при Geometry и Asterisks

Рис. 1.10. Представление частиц системы: а – type = 6 (Geometry); б – type = 9 (Asterisks)

1.8.3. ОПЕРАТОР POSITION_OBJECT И ТЕСТ COLLISION

По умолчанию эмиттером частиц является иконка системы частиц Particle Flow. Оператор Position_Object позволяет указать объект сцены в качестве иного эмиттера частиц. При этом частицы могут генерироваться любой точкой поверхности или объема объекта либо ребрами, либо вершинами, либо базовой точкой, либо выборкой подобъектов. Также генерация частиц может управляться примененным к объекту материалом.
В примере в качестве эмиттера частиц берется сфера, размеры которой в процессе анимации уменьшаются во времени, и вдобавок сфера перемещается по оси X. Рожденные частицы падают вниз и останавливаются на плоскости, указанной в тесте Collision. Частицы останавливаются, поскольку в тесте свойство Speed_Option = 2 (Speed Stop).

fn fPOCS h = (
    animationRange = interval 0f 100f
    messageBox("Position_Object and Collision")
    delete $*
    sph = sphere radius:30 pos:[0, 0, 40] wireColor:black
    pOF = POmniFlect timeOn:0f timeOff:100f width:160 height:70 pos:[0, 0, -40]
    pF = PF_Source enable_Particles:true quantity_Viewport:100
    particleFlow.BeginEdit()
    opBth = birth emit_Start:0 emit_Stop:14000 amount:300
    opPO = position_Object Location:3 emitter_Objects:#(sph) random_Seed:1 \
        animated_Shape:on distinct_Points_Only:on total_Distinct_Points:10
    opSpd = speed speed:100 direction:0
    opShS = shapeStandard shape:1 size:4
    opDP = displayParticles color:(color 100 100 100) type:6
    tstCS = collision collision_Nodes:#(pOF) test_Type:0 speed_Option:2
    apActFn pF #(opBth, opPO, opSpd, opShS, opDP, tstCS) false
    animate on (
        at time 0f (sph.Radius = 30; sph.Pos = [-60, 0, 0])
        at time 100f (sph.Radius = 3; sph.Pos = [60, 0, 0])
    )
    playAnimation()
)
fPOCS 1

Для сокращения кода опущены операторы Rotation и RenderParticles. Оператор Position_Icon заменен оператором Position_Object.
Заданные свойства оператора обеспечивают генерацию частиц из 10 точек поверхности, а установленный флажок animated_Shape обеспечивает изменение координат этих точек при изменении размера сферы (эмиттера). Успокоившиеся на плоскости частицы системы показаны на рис. 1.11, а структурная схема системы - на рис. 1.12.
        Position_Object с animated_Shape

Рис. 1.11. Система при t = 90f

        Структурная схема системы с Position_Object

Рис. 1.12. Структурная схема системы с Position_Object

В случае задания оператором Position_Object нескольких эмиттеров частицы распределяются между эмиттерами пропорционально их объемам.
В следующем примере в качестве эмиттеров указываются две сферы разных радиусов. Как и ранее сгенерированные частицы падают на плоскость отражателя POmniFlect и останавливаются на этой плоскости.

fn fPO2CS h = (
    animationRange = interval 0f 100f
    messageBox("Two Position_Object operators and Collision")
    delete $*
    sph = sphere radius:10 pos:[-40, 0, 20] wireColor:black
    sph2 = sphere radius:20 pos:[40, 0, 20] wireColor:black
    pOF = POmniFlect timeOn:0f timeOff:100f width:140 height:60 pos:[0, 0, -20]
    pF = PF_Source enable_Particles:true quantity_Viewport:100
    particleFlow.BeginEdit()
    opBth = birth emit_Start:0 emit_Stop:12000 amount:500
    opPO = position_Object Location:3 emitter_Objects:#(sph, sph2)
    opSpd = speed speed:100 direction:0
    opShS = shapeStandard shape:0 size:4
    opDP = displayParticles color:(color 100 100 100) type:6
    tstCS = collision collision_Nodes:#(pOF) test_Type:0 speed_Option:2
    apActFn pF #(opBth, opPO, opSpd, opShS, opDP, tstCS) false
    playAnimation()
)
fPO2CS 1

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

        Position_Objectс двумя сферическими эмиттерами

Рис. 1.13. Система с двумя эмиттерами при t = 100f

1.8.4. ОПЕРАТОР SPEED_BY_SURFACE

Свойство Surface_Objects оператора Speed_By_Surface определяется значением массива объектов, перемещение которых взывает воздействие на вектор скорости частиц. При этом характер воздействия зависит как от траектории движения объектов, так и от положения их базовых точек.
В примере в качестве действующего на систему частиц объекта употребляется цилиндр, перемещающийся по эллипсу. Все частицы генерируются в одной точке эмиттера (свойство Distinct_Points_Only установлено в on, свойство Total_Distinct_Points = 1). Перемещение объекта обусловливает спиралевидную траекторию движения частиц, смещаемую вниз по оси Z (рис. 1.14, а), если уменьшена Z-координата базовой точки цилиндра (k = 2), или вверх по оси Z (рис. 1.14, б) – в противном случае (k = 3).

        Speed_By_Surface

Рис. 1.14. Оператор Speed_By_Surface: а - базовая точка объекта смещена вниз; б - базовая точка объекта смещена вверх

Решение обеспечивается следующим кодом:

fn fSpdByS h = (
    animationRange = interval 0f 100f
    messageBox("Speed By Surface")
    for k = 1 to 3 do (
        delete $*
        cH = 30
        clO = cylinder radius:10 height:cH
        ellps = ellipse length:100 width:90 pos:[0, 0, 0] wireColor:black
        pc = path_Constraint()
        clO.Pos.Controller = pc
        pc.Path = ellps
        pvt = clO.Pivot
        pc.Percent.Keys[2].Value = 300
        pF = PF_Source enable_Particles:true quantity_Viewport:100
        particleFlow.BeginEdit()
        opBth = birth emit_Start:0 emit_Stop:16000 amount:150
        opPI = position_Icon distinct_Points_Only:on total_Distinct_Points:1
        opSBS = speed_By_Surface surface_Objects:#(clO) speed_Type:1 \
            set_Speed_Magnitude:on speed_Value:60 direction_Type:1
        opShS = shapeStandard shape:1 size:2
        opDP = displayParticles color:black type:6
        apActFn pF #(opBth, opPI, opShS, opSBS, opDP) false
        case k of (
            2 : clO.Pivot = pvt - [0, 0, cH / 5]
            3 : clO.Pivot = pvt + [0, 0, cH + cH / 7]
        )
        playAnimation()
    )
)
fSpdByS 1

1.8.5. ТЕСТ FIND_TARGET И ОПЕРАТОР SPEEDBYICON

Тест 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.

fn fFt h = (
    animationRange = interval 0f 100f
    messageBox("Find_Target: Cruise_Speed")
    delete $*
    pF = PF_Source enable_Particles:true quantity_Viewport:100 pos:[-40, 0, -20]
    particleFlow.BeginEdit()
    opBth = birth emit_Start:0 emit_Stop:4800 amount:500
    opPI = position_Icon()
    opDP = displayParticles color:black type:2
    tstFT = find_Target speed_Type:0 test_Type:1 use_Cruise_Speed:on \
        cruise_Speed:50 Acceleration_Limit:1000 target_Type:0 pos:[40, 0, 20]
    apActFn pF #(opBth, opPI, opDP, tstFT) false
    playAnimation()
)
fFt 1

Результат приведен на рис. 1.15.

        Find_Target и SpeedByIcon

Рис. 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.
        Find_Target и SpeedByIcon, Speed_Type = 1 (Control By Time)

Рис. 1.16. Частицы при t = 60f

После достижения цели частицы, если они остаются в текущем событии, продолжают движение, направление и скорость которого определяются вектором скорости частицы. Для изменения поведения частиц после достижения цели их можно передать следующему соответствующим образом оформленному событию.
В следующем примере частицы, достигшие цель, останавливаются. Это обеспечивается вторым событием, в котором имеется оператор Speed, содержащий нулевое значение скорости. Вдобавок частицы приобретают красный цвет. Разобранный пример реализуется вышеприведенным кодом при k = 3.
Один кадр анимации приведен на рис. 1.17.

        Find_Target и SpeedByIcon, во втором событии Speed = 0

Рис. 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).
        Два Find_Target и три события

Рис. 1.18. Система при t = 80f

Тесты Find_Target позволяют передать часть частиц связанным с ним событиям (рис. 1.19), каждое из которых обладает оператором SpeedByIcon.

        Два Find_Target и три события, в коджом имеется 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. В противном случае в отводок, для которого это условие нарушено, уйдут все частицы основного потока.

fn fFtSByI h = (
    animationRange = interval 0f 100f
    for k = 1 to 2 do (
        delete $*
        messageBox("Find Target and Speed By Icon" \
            + (if k == 2 then " with BlobMesh" else ""))
        psn = [-110, 0, 0]
        pth = line pos:[0, 0, 0] wireColor:black
        addNewSpline pth
        addKnot pth 1 #corner #line psn
        addKnot pth 1 #corner #line [130, 0, 0]
        updateShape pth
        pth2 = line pos:[50, 0, -40] wireColor:black
        offX2 = [-140, 0, 0]
        knt2 = [80, 0, 0] + offX2
        addNewSpline pth2
        addKnot pth2 1 #smooth #curve knt2
        addKnot pth2 1 #smooth #curve ([115, 0, -21] + offX2)
        addKnot pth2 1 #smooth #curve ([120, 0, -85] + offX2)
        addKnot pth2 1 #smooth #curve ([130, 0, -105] + offX2)
        addKnot pth2 1 #smooth #curve ([160, 0, -110] + offX2)
        addKnot pth2 1 #smooth #curve ([300, 0, -110] + offX2)
        updateShape pth2
        pth3 = line pos:[12, 0, 30] wireColor:black
        offX3 = [-20, 0, 0]
        knt3 = [15, 0, 0] + offX3; knt3_2 = [20, 0, 23] + offX3
        addNewSpline pth3
        addKnot pth3 1 #corner #line knt3
        addKnot pth3 1 #smooth #curve knt3_2
        addKnot pth3 1 #smooth #line ([45, 0, 45] + offX3)
        addKnot pth3 1 #corner #line ([200, 0, 60] + offX3)
        updateShape pth3
        max tool zoomExtents
        qt = eulerToQuat (eulerAngles 0 90 0)
        pF = PF_Source rotation:qt pos:psn quantity_Viewport:100
        if k == 2 do (
            bM = blobMesh pos:[-10, -200, 0] wireColor:[135, 110, 8]
            bM.BlobMeshOps.AddBlob pF
        )
        particleFlow.BeginEdit()
        opBth = birth emit_Start:0 emit_Stop:16000 amount:2000
        opPI = position_Icon()
        opSBI = speedByIcon pos:psn use_Icon_Orientation:off \
            steer_Towards_Trajectory:on distance:9
        pc = path_Constraint follow:on axis:1 path:pth
        opSBI.Pos.Controller = pc
        if k == 2 do opShS = shapeStandard shape:2 size:5
        opDP = displayParticles type:(2 - k) color:green
        tstFT = find_Target speed_Type:2 test_Type:0 test_Distance:5 pos:knt2
        tstFT2 = find_Target speed_Type:2 test_Type:0 test_Distance:7 pos:knt3
        if k == 1 then
            apActFn pF #(opBth, opPI, opSBI, opDP, tstFT, tstFT2) false
        else
            apActFn pF #(opBth, opPI, opShS, opSBI, opDP, tstFT, tstFT2) false
        --
        -- Event 2
        particleFlow.BeginEdit()
        opSBI2 = speedByIcon steer_Towards_Trajectory:on distance:5
        pc2 = path_Constraint follow:on axis:2 path:pth2
        opSBI2.Pos.Controller = pc2
        opDP2 = displayParticles type:(2 - k) color:red
        evn2 = apActFn pF #(opSBI2, opDP2) false
        tstFT.SetNextActionList evn2 tstFT
        --
        -- Event 3
        particleFlow.BeginEdit()
        opSBI3 = speedByIcon steer_Towards_Trajectory:on distance:5
        pc3 = path_Constraint follow:on axis:2 path:pth3 allowUpsideDown:on
        opSBI3.Pos.Controller = pc3
        opDP3 = displayParticles type:(2 - k) color:blue
        evn3 = apActFn pF #(opSBI3, opDP3) false
        tstFT2.SetNextActionList evn3 tstFT2
        playAnimation()
    )
)
fFtSByI 1

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

        BlobMesh с Particle Flow

Рис. 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 основного потока частиц.

delete $*
psn = [-110, 0, 0]
animationRange = interval 0f 100f
pth = line pos:[0, 0, 0] wireColor:black
addNewSpline pth
addKnot pth 1 #corner #line psn
addKnot pth 1 #corner #line [130, 0, 0]
updateShape pth
pth2 = line pos:[50, 0, -40] wireColor:black
offX2 = [-140, 0, 0]
knt2 = [80, 0, 0] + offX2
addNewSpline pth2
addKnot pth2 1 #smooth #curve knt2
addKnot pth2 1 #smooth #curve ([115, 0, -21] + offX2)
addKnot pth2 1 #smooth #curve ([120, 0, -85] + offX2)
addKnot pth2 1 #smooth #curve ([130, 0, -105] + offX2)
addKnot pth2 1 #smooth #curve ([160, 0, -110] + offX2)
addKnot pth2 1 #smooth #curve ([300, 0, -110] + offX2)
updateShape pth2
pth3 = line pos:[12, 0, 30] wireColor:black
offX3 = [-20, 0, 0]
knt3 = [15, 0, 0] + offX3; knt3_2 = [20, 0, 23] + offX3
addNewSpline pth3
addKnot pth3 1 #corner #line knt3
addKnot pth3 1 #smooth #curve knt3_2
addKnot pth3 1 #smooth #line ([45, 0, 45] + offX3)
addKnot pth3 1 #corner #line ([200, 0, 60] + offX3)
updateShape pth3
qt = eulerToQuat (eulerAngles 0 90 0)
pF = PF_Source rotation:qt pos:psn quantity_Viewport:100
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:16000 amount:2000
opPI = position_Icon()
opSBI = speedByIcon pos:psn use_Icon_Orientation:off \
    steer_Towards_Trajectory:on distance:5
pc = path_Constraint follow:on axis:1 path:pth
opSBI.Pos.Controller = pc
opDP = displayParticles type:1 color:green
tstST = script_Test proceed_Script:"
on channelsUsed pCont do pCont.UseTime = pCont.UseAge = true
on proceed pCont do (
    nP = pCont.NumParticles()
    for k in 1 to nP by 4 do (
        pCont.ParticleIndex = k
        pAg = pCont.ParticleAge as integer
        if pAg > 2800 and pAg < 3200 then (
            pCont.ParticleTestStatus = true
            pCont.ParticleTestTime = pCont.ParticleTime
        )
    )
)"
tstST2 = script_Test proceed_Script:"
on channelsUsed pCont do pCont.UseTime = pCont.UseAge = true
on proceed pCont do (
        nP = pCont.NumParticles()
    for k in 1 to nP by 2 do (
        pCont.ParticleIndex = k
        pAg = pCont.ParticleAge as integer
        if pAg > 6300 and pAg < 6700 then (
            pCont.ParticleTestStatus = true
            pCont.ParticleTestTime = pCont.ParticleTime
        )
    )
)"
apActFn pF #(opBth, opPI, opSBI, opDP, tstST, tstST2) false
-- Event 2
particleFlow.BeginEdit()
opSBI2 = speedByIcon steer_Towards_Trajectory:on distance:5
pc2 = path_Constraint follow:on axis:2 path:pth2
opSBI2.Pos.Controller = pc2
opDP2 = displayParticles type:1 color:red
evn2 = apActFn pF #(opSBI2, opDP2) false
tstST.SetNextActionList evn2 tstST
-- Event 3
particleFlow.BeginEdit()
opSBI3 = speedByIcon steer_Towards_Trajectory:on distance:5
pc3 = path_Constraint follow:on axis:2 path:pth3 allowUpsideDown:on
opSBI3.Pos.Controller = pc3
opDP3 = displayParticles type:1 color:blue
evn3 = apActFn pF #(opSBI3, opDP3) false
tstST2.SetNextActionList evn3 tstST2
playAnimation()

1.8.6. ТЕСТ FIND_TARGET И СВОЙСТВО PARTICLEINTEGER

Тест позволяет указать объекты, к которым устремятся генерируемые частицы. Если свойство теста Lock_On_Target установлено в on, то частицы позиционируются на достигнутой цели. Распределение частиц по целям регулируется свойством ParticleInteger. В примере частицы распределяются по целям равномерно. В примере две цели: цилиндр, имитирующий монету, и плоскость. При этом если ParticleInteger = 0, то частица устремится к цилиндру и окажется на плоскости, если ParticleInteger = 1.
При анимации частицы, имитирующие песок, заносят монету, и плоскость, не которой монета лежит. Для сдувания песка с монеты можно либо проиграть сцену в обратную сторону (переключатель Direction окна Time Configuration устанавливается в положение Reverse), либо инвертировать последовательность ключей анимации.

fn fPInt2 h = (
    animationRange = interval 0f 100f
    delete $*
    messageBox("ParticleInteger: Sand on coin")
    sliderTime = 0f
    cl = cylinder radius:10 height:2 wireColor:blue
    pln = plane length:50 width:50 wireColor:green
    qt = eulerToQuat (eulerAngles 0 90 0)
    pF = PF_Source enable_Particles:true \
        emitter_Length:50 emitter_Width:5 \
        quantity_Viewport:100 rotation:qt pos:[-80, 0, 0]
    particleFlow.BeginEdit()
    opBth = birth emit_Start:0 emit_Stop:7000 amount:5000
    opPI = position_Icon()
    opShS = shapeStandard shape:2 size:1        -- Sphere with R = 1
    opSpd = speed speed:0 direction:0
    opSP = scaleParticles type:0 X_Scale_Factor:50 \
        Y_Scale_Factor:50 Z_Scale_Factor:50
    opDP = displayParticles type:6 color:yellow
    tstFT = find_Target assignment_Type:4 speed_Type:1 test_Type:1 \
        time:9600 time_Variation:800 lock_On_Target:on \
        target_Type:1 target_Objects:#(cl, pln) \
        use_Docking_Speed:on docking_Speed:0 icon_Size:0
    opSO = script_Operator proceed_Script:"
    on channelsUsed pCont do (
        pCont.UseTime = true
        pCont.UseInteger = true
    )
    on proceed pCont do (
        nP = pCont.NumParticles()
        for k = 1 to nP do (
            pCont.ParticleIndex = k
            if pCont.ParticleNew then pCont.ParticleInteger = random 0 1
        )
    )"
    apActFn pF #(opBth, opPI, opShS, opSpd, opSP, opDP, opSO, tstFT) false
    timeConfiguration.RealTimePlayback = false
    playAnimation()
)
fPInt2 1

Сцена в двух точках анимации приведена на рис. 1.21.

        Find_Target; pCont.ParticleInteger = random 0 1

Рис. 1.21. Монета, заносимая песком, при t = 30f и t = 100f

1.8.7. ТЕСТ AGE_TEST

По умолчанию тест Age_Test проверяет возраст частицы (Particle Age) на предмет превышения заданного свойством Test_Value значения. Если такое превышение наблюдается, то тест завершается со значением true и частица передается следующему событию.
Условие тестирования можно изменить на противоположное, то есть завершать тест положительно, если возраст частицы меньше Test_Value. Кроме того, взамен возраста частицы можно тестировать ее абсолютное время или время ее присутствия в текущем событии.
В примере цвет частицы меняется с белого на серый (рис. 1.22), если ее возраст превышает заданное значение.

        Age_Test > 20

Рис. 1.22. Частицы, прошедшие Age Test, имеют серый цвет

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

fn fAg h = (
    animationRange = interval 0f 50f
    delete $*
    messageBox("Age Test")
    pF = PF_Source enable_Particles:true quantity_Viewport:100
    particleFlow.BeginEdit()
    opBth = birth emit_Start:0 emit_Stop:8000 amount:20
    opPI = position_Icon location:4
    opSpd = speed speed:100 direction:3        -- 3D
    opShS = shapeStandard shape:2
    opDP = displayParticles color:white type:6
    tstAT = age_Test test_Type:1 condition_Type:1 test_Value:3200 variation:0
    apActFn pF #(opBth, opPI, opSpd, opShS, opDP, tstAT) false
    -- Event 2
    particleFlow.BeginEdit()
    opDP2 = displayParticles color:gray type:6
    evn2 = apActFn pF #(opDP2) false
    tstAT.SetNextActionList evn2 tstAT
    playAnimation()
)
fAg 1

Генерируемые частицы вылетают из эмиттера по случайно выбранным направлениям (opSpd.Direction = 3). Цвет частиц регулируется свойством Color оператора DisplayParticles. В качестве начального цвета частиц выбран белый (opDP.Color = white).
Тест Age_Test передает второму событию Частицы, возраст которых больше 10f, что равно 1600 тиков (в коде теста время указывается в тиках). Если свойство Variation (отклонение) теста Age_Test задать отличным от нуля (значение задается в тиках), то тестируемое время будет отклоняться случайным образом от значения, заданного свойством Test_Value, на Variation. Вероятность передачи следующему событию «неподходящих» частиц будет ниже, если установить в true свойство Subframe_Sampling. Это обеспечит более частое выполнение теста.
Второе событие снабжено лишь одним оператором opDP2, обеспечивающим замену текущего цвета частицы на серый.
Структурная схема созданной системы приведена на рис. 1.23.

        Age_Test > 10

Рис. 1.23. Система частиц с изменяющимся цветом

1.8.8. ТЕСТ SRIPT_TEST И ОПЕРАТОР KEEP_APART

В примере после прохождения теста Sript_Test сферические, цилиндрические и конические частицы направляются соответственно по осям -X, -Y и X. Кроме того, во втором событии меняется цвет частиц и вводится посредством оператора Keep_Apart сила взаимного отталкивания (рис. 1.24).

        Sript_Test: UseShape = UseAge = UseTime = UseSpeed = true

Рис. 1.24. Пройден Sript_Test

fn fPST h = (
    animationRange = interval 0f 100f
    delete $*
    messageBox("Sript_Test and Keep_Apart")
    global aRngEnd = animationRange.End / 3
    global arrMsh = #(1, 2, 3, 4)
    global arrMsh = #(), arrNv = #()
    arrMsh.Count = arrNv.Count = 3
    sp = sphere radius:5 segs:8
    cl = cylinder radius:5 height:10
    cn = cone radius1:5 radius2:0 height:10
    hide #(sp, cl, cn)
    arrMsh[1] = sp.Mesh; arrNv[1] = arrMsh[1].NumVerts
    arrMsh[2] = cl.Mesh; arrNv[2] = arrMsh[2].NumVerts
    arrMsh[3] = cn.Mesh; arrNv[3] = arrMsh[3].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:10
    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 (
        nP = pCont.NumParticles()
        s = 0.007
        for i in 1 to nP do (
            pCont.ParticleIndex = i
            if pCont.ParticleNew then pCont.ParticleShape = arrMsh[random 1 3]
            if pCont.ParticleAge > aRngEnd then (
                pCont.ParticleTestStatus = true
                pCont.ParticleTestTime = pCont.ParticleTime
                pShpNv = pCont.ParticleShape.Numverts
                case of (
                    (pShpNv == arrNv[1]) : pCont.ParticleSpeed = [-s, 0, 0]
                    (pShpNv == arrNv[2]) : pCont.ParticleSpeed = [s, 0, 0]
                    default : pCont.ParticleSpeed = [0, -s, 0]
                )
            )
        )
    )"
    apActFn pF #(opBth, opPI, opSpd, opDP, tstST) false
    --
    -- Event 2
    particleFlow.BeginEdit()
    opDP2 = displayParticles color:[50, 50, 50] type:6
    opKA2 = keep_Apart force:0.005
    evn2 = apActFn pF #(opDP2, opKA2) false
    tstST.SetNextActionList evn2 tstST
    playAnimation()
)
fPST 1

Структурная схема рассмотренной системы частиц приведена на рис. 1.25.

        Sript_Test: UseShape = UseAge = UseTime = UseSpeed = true

Рис. 1.25. Пройден Sript_Test

В следующем примере генерируются частицы, имеющие форму сферы, цилиндра, конуса или чайника. Форма случайным образом назначается частице в proceed-обработчике теста Sript_Test. Частицы, возраст которых превышает заданную величину (переменная aRngEnd), если их форма отлична от формы чайника, не проходят Sript_Test и останавливаются. Частицы в форме чайники тест проходят и поэтому передаются следующему событию, в котором меняется их цвет. Кроме того, они разлетаются в разные стороны (рис. 1.26) благодаря имеющемуся во втором событии оператору Keep_Apart.

        Sript_Test: UseShape = UseAge = UseTime = UseSpeed = true

Рис. 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.

        Sript_Test: UseShape = UseAge = UseTime = UseSpeed = true

Рис. 1.27. Структурная схема системы частиц

1.8.9. ОПЕРАТОРЫ SHAPE_FACING И MATERIAL_DYNAMIC С КАРТОЙ ВОЗРАСТ ЧАСТИЦЫ PARTICLE_AGE

В примере генерируется одна частица, которая благодаря оператору Material_Dynamic и примененному с ним стандартным материалом с диффузионной картой Particle_Age изменяет по мере старения свой цвет.
При k = 1, частица имеет стандартную сферическую форму, при k = 2 и 3 – плоскую форму (употреблен оператор Shape_Facing). При этом при k = 3, поскольку стандартный материал вдобавок снабжен картой OpacityMap на основе маски Gradient, частица имеет форму круга (рис. 1.28).

        Shape_Facing, Material_Dynamic и Particle_Age

Рис. 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

1.8.10. ОПЕРАТОРЫ SHAPE_FACING И MATERIAL_STATIC С КАРТОЙ ГРАДИЕНТНЫЙ СКАТ GRADIENT RAMP

В примере генерируется одна частица, которая благодаря оператору 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.

        Shape_Facing, Material_Static, Gradient Ramp

Рис. 1.29. В начале анимации заметен желтый цвет; затем он замещается синим цветом

В следующем примере оператор Material_Static используется с частицей цилиндрической формы, устанавливаемой как значение свойства ParticleShape частицы. При k = 3 вдобавок используется оператор Shape_Facing (рис. 1.30).

        Shape_Facing, Material_Static и Particle_Age

Рис. 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

1.8.11. ОПЕРАТОР MATERIAL_FREQUENCY С ЧАСТИЦАМИ ЗАДАННОЙ ФОРМЫ

В примере создаются 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.

        Material_Frequency

Рис. 1.31. Частицы с оператором Material_Frequency

1.8.12. КОМЕТА С POSITION_OBJECT И PARTICLE_AGE


В примере эмиттером частиц является сфера, указанная в свойстве Emitter_Objects оператора Position_Object. Изначально все частицы рождаются в первом событии, когда t = 0. частицы. Далее они посредством теста Spawn (тиражирование частиц) передаются второму событию и подвергаются воздействию ветра, что вызывает их смещение. Частицы, вышедшие из заданного пространства, передаются третьему событию и уничтожаются.
Цвет хвоста кометы обусловлен картой Particle_Age, примененной к частицам посредством оператора Material_Dynamic и стандартного материала, диффузионная карта которого определена ссылкой на карту Particle_Age. Эта карта имеет три цвета: желтый, красный и черный. Первый используется для вновь появившихся частиц, затем их цвет последовательно меняется на красный и черный.
Структурная схема системы частиц приведена на рис. 1.32.

        Position_Object, Shape_Facing, Spawn, Material_Dynamic с Particle_Age

Рис. 1.32. Структурная схема системы частиц

fn fPAgCmt h = (
    animationRange = interval 0f 50f
    sliderTime = 0f
    delete $*
    messageBox("Particle Age: Comet")
    dfMp = particle_Age color1:yellow age1:5 \
        color2:red age2:15 color3:black age3:20
    mtS = standard diffuseMap:dfMp showInViewport:true diffuseMapEnable:true
    meditMaterials[1] = mtS
    sp = sphere radius:7 material:mtS pos:[0, 0, 0] segs:8
    qt = eulerToQuat (eulerAngles 0 90 0)
    wnd = wind strength:0.5 decay:0 turbulence:0.75 frequency:2 \
        scale:1 pos:[30, -30, 0] rotation:qt
    qt = eulerToQuat (eulerAngles 0 -90 0)
    pF = PF_Source enable_Particles:true quantity_Viewport:100 rotation:qt
    particleFlow.BeginEdit()
    opBth = birth emit_Start:0 emit_Stop:0 amount:100
    opPI = position_Icon()
    opPO = position_Object emitter_Objects:#(sp)
    opDP = displayParticles color:yellow
    opRP = renderParticles()
    opShFc = shape_Facing()            -- Flat particles
    -- Per Second (By Rate)
    tstSpw = spawn spawn_Type:1 spawn_Rate:100 restart_Particle_Age:on \
        speed_Inherited:0 divergence:1 scale_Factor:100
    apActFn pF #(opBth, opPI, opPO, opDP, opShFc, tstSpw, opRP) true
    --
    -- Event 2
    particleFlow.BeginEdit()
    opFc2 = force force_Space_Warps:#(wnd)
    opDP2 = displayParticles type:2        -- Ticks
    -- By Age
    opDlt2 = deleteParticles type:2 life_Span:12000 variation:0
    opMtD2 = material_Dynamic assigned_Material:mtS
    tstST2 = script_Test proceed_Script:"
    on channelsUsed pCont do (
        pCont.UseTime = true
        pCont.UsePosition = true
    )
    on proceed pCont do (
        nP = pCont.NumParticles()
        for i in 1 to nP do (
            pCont.ParticleIndex = i
            pPos = pCont.ParticlePosition
            ppY = abs(pPos[2]);
            ppZ = abs(pPos[3]);
            pLngth = length(pPos)
            if (pLngth > 80) \
                or (pLngth > 60 and (ppY > 10) or (ppZ > 10)) \
                or (pLngth > 70 and (ppY > 5) or (ppZ > 5)) then (
                pCont.ParticleTestStatus = true
                pCont.ParticleTestTime = pCont.ParticleTime
            )
        )
    )"
    evn2 = apActFn pF #(opFc2, opDP2, opDlt2, opMtD2, tstST2) false
    tstSpw.SetNextActionList evn2 tstSpw
    --
    -- Event 3
    particleFlow.BeginEdit()
    opDlt3 = deleteParticles type:0        -- All particles
    evn3 = apActFn pF #(opDlt3) false
    tstST2.SetNextActionList evn3 tstST2
    backgroundColor = white
    renderWidth = 320; renderHeight = 240
    render frameRange:(interval 0f 20f)
)
fPAgCmt 1

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

        Position_Object, Shape_Facing, Spawn, Material_Dynamic с Particle_Age

Рис. 1.33. Комета с Particle_Age

1.8.13. ВЗРЫВ С PARTICLE AGE И GRADIENT_RAMP

В примере одномоментно создаются 30 частиц, к которым посредством оператора Material_Dynamic применяются карты Particle Age и Gradient_Ramp. Перемещение частиц обеспечивается приложенными силами ветра, торможения и гравитации (соответственно Wind, Drag и Gravity). Наличие оператора Shape_Facing обеспечивает плоскую форму частиц, а также их ориентацию относительно введенной в сцену камеры.

fn fPExplsn h = (
    animationRange = interval 0f 50f
    sliderTime = 0f
    delete $*
    messageBox("Material_Dynamic: Particle Age, Gradient_Ramp")
    pSob = sphere radius:30 pos:[60, 0, 0] mapCoords:on segs:16
    -- pSob = box length:40 width:40 height:40 pos:[60, 0, 0] \
    --     lengthSegs:4 widthSegs:4 heightSegs:4 mapCoords:on
    -- pSob = cylinder radius:20 height:50 pos:[60, 0, 0] mapCoords:on
    global pSobMsh
    pSobMsh = pSob.Mesh
    hide pSob
    -- Regular (type = 0); Turbulence (type = 2)
    ns = noise type:2 thresholdHigh:1 thresholdQLow:0.1 levels:10 size:30
    -- Gradient_Type = 4 (Linear); 5 - Mapped
    -- Noise_Type = 1 (Fractal; Levels = 4)
    gR = gradient_Ramp gradient_Type:5 \
        noise_Type:1 amount:0.5 size:5 levels:4 \
        low_Threshold:0 high_Threshold:1
    gR.Coordinates.MappingType = 0        -- Texture
    gR.Coordinates.Mapping = 0        -- Explicit Map Channel
    -- gR.Coordinates.W_Angle = -45        -- Gradient from bottom to top
    gR.Gradient_ramp.Flag__1.Color = yellow
    gR.Gradient_ramp.Flag__3.Color = (color 255 0 0)
    gR.Gradient_ramp.Flag__3.Position = 25
    -- gR.Gradient_ramp.Flag__4.Color = black
    -- gR.Gradient_ramp.Flag__4.Position = 30
    -- gR.Gradient_ramp.Flag__5.Color = black
    -- gR.Gradient_ramp.Flag__5.Position = 40
    gR.Gradient_ramp.Flag__2.Color = black
    gR.Source_Map_On = 1
    -- gR.Source_Map = ns
    gR2 = copy gR
    gR2.Gradient_ramp.Flag__1.Color = (color 255 130 0)
    gR2.Gradient_ramp.Flag__3.Color = (color 100 30 0)
    gR2.Gradient_ramp.Flag__3.Position = 15
    -- gR2.Gradient_ramp.Flag__4.Color = (color 0 0 0)
    -- gR2.Gradient_ramp.Flag__4.Position = 40
    gR2.Gradient_ramp.Flag__2.Color = (color 50 50 50)
    gR3 = copy gR
    gR3.Gradient_ramp.Flag__1.Color = (color 30 30 30)
    gR3.Gradient_ramp.Flag__3.Color = black
    gR3.Gradient_ramp.Flag__3.Position = 50
    gR3.Gradient_ramp.Flag__2.Color = (color 40 40 40)
    pA = particle_Age color1:yellow map1:gR age1:0 \
        color2:red map2:gR2 age2:20 \
        color3:black map3:gR3 age3:40
    -- Radial (gradientType = 1)
    grd = gradient gradientType:1 noiseType:1 noiseAmount:1 noiseSize:5
    -- Fractal noise (noiseType = 1)
    opcMp = mask map:grd            -- mask:pA
    mtS = standard diffuseMap:pA showInViewport:true diffuseMapEnable:true \
        samplerUseGlobal:off
    mtS.SelfillumMap = pA
    mtS.UseSelfIllumColor = on
    mtS.OpacityMap = opcMp
    mtS.Opacity = 100
    meditMaterials[1] = mtS
    -- Planar (windType = 0); Spherical (windType = 1)
    wnd = wind strength:0.01 decay:0 turbulence:1 scale:1 windType:0 pos:[-30, 30, 0]
    rotate wnd (eulerAngles 0 90 0)
    drg = drag dampingX:15 dampingY:15 dampingZ:15 pos:[0, 0, 0]
    grv = gravity strength:0.05 decay:0 pos:[-30, -30, 0]
    cmr = freeCamera fov:45 targetDistance:160 pos:[0, -300, 0]
    rotate cmr (eulerAngles 90 0 0)
    --
    -- PF
    qt = eulerToQuat (eulerAngles 0 180 0)
    pF = PF_Source enable_Particles:true quantity_Viewport:100 rotation:qt
    particleFlow.BeginEdit()
    opBth = birth emit_Start:0 emit_Stop:0 amount:30
    opPI = position_Icon()
    opRP = renderParticles()
    opDP = displayParticles 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 pCont.ParticleNew then pCont.ParticleShape = pSobMsh
        )
    )"
    -- Flat particles
    -- In Local Space (Size_Space = 1); In Screen Space (Size_Space = 2)
    -- Pivot_At Center (pivot_At = 1)
    -- Align to Horizont (orientation = 0)
    opShFc = shape_Facing look_At_Object:cmr size_Space:1
    opFc = force force_Space_Warps:#(drg, grv, wnd)
    opMtD = material_Dynamic assigned_Material:mtS
    -- By Particle Age (type = 2)
    opDlt = deleteParticles type:2 life_Span:32000 variation:1600
    apActFn pF #(opBth, opPI, opSO, opDP, opShFc, opFc, opMtD, opDlt, opRP) true
    backgroundColor = white
    renderWidth = 320; renderHeight = 240
    render camera:cmr frameRange:(interval 40f 43f)
    --render camera:cmr frame:40f
)
fPExplsn 1

На рис. 1.34 приведен один кадр анимации.

        Script_Operator, Shape_Facing, Force, Material_Dynamic с Particle Age и Gradient_Ramp

Рис. 1.34. Взрыв с Particle Age и Gradient_Ramp

Для повышения наглядности в карту Gradient_Ramp следует интерактивно добавить шум с параметром type = 2 (Turbulence), а также флаги 4 и 5 и флаг 4 соответственно в карты gR и gR2.

1.9. СВОЙСТВА ЧАСТИЦЫ

Частица – это элемент подмножества частиц, называемого контейнером частиц. В каждый момент времени частица принадлежит одному событию.
Геометрия частицы описывается в ее локальной системы координат. Частицы обладают массой, величина которой пропорциональна объему частицы.
При программировании операторов Birth_Script, Script_Operator и Script_Test управление частицами обеспечивает интерфейс MaxscriptParticleContainer. В прочих скриптах доступ к частицам осуществляется средствами интерфейса ParticleObjectExt.
Интерфейс MaxscriptParticleContainer позволяет получать и изменять значения следующих свойств частицы (в скобках указывается тип свойства): Интерфейс ParticleObjectExt оперирует свойствами ParticleIndex, ParticleID, ParticleAge, ParticlePosition, ParticleSpeed, ParticleOrientation, ParticleSpin, ParticleScale, ParticleScaleXYZ, ParticleTM, ParticleSelected и ParticleShape, а также свойством ParticleGroupTime (time), содержащим положение частицы на временной оси.
В обоих интерфейсах число доступных частиц возвращается методом NumParticles. При этом в обработчиках оператор Birth_Script, Script_Operator и Script_Test доступны частицы, принадлежащие только событию, которому операторы принадлежат.

1.10. УПРАВЛЕНИЕ КОЛИЧЕСТВОМ ЧАСТИЦ

1.10.1. ГЕНЕРАЦИЯ ЧАСТИЦ

Генерацию частиц обеспечивает оператор 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) порождает три новые частицы. При этом родительская частица удаляется из системы частиц. Форма исходных частиц - сфера, а возникших после столкновения - куб.

fn fCS h = (
    animationRange = interval 0f 100f
    delete $*
    messageBox("Collision_Spawn")
    pF = PF_Source enable_Particles:on rotation:(eulerToQuat (eulerAngles 0 90 0)) \
        emitter_Type:2 emitter_Length:20 \
        show_Logo:on show_Emitter:on quantity_Viewport:100
    pOF = POmniFlect timeOn:0f timeOff:100f width:80 height:80 pos:[50, 0, 0]
    rotate pOF (eulerAngles 0 90 0)
    particleFlow.BeginEdit()
    opBth = birth emit_Start:0 emit_Stop:0 amount:3
    opPI = position_Icon distinct_Points_Only:on total_Distinct_Points:1
    opSpd = speed speed:40 variation:10 direction:0 divergence:30
    opSpn = spin rate:180 variation:0
    opShS = shapeStandard shape:2 size:5        -- Sphere with R = 5
    opDP = displayParticles color:red type:6
    tstCS = collision_Spawn collision_Nodes:#(pOF) number_of_Offsprings:3 \
        delete_Parent:on speed_Variation:50 divergence:30
    apActFn pF #(opBth, opPI, opSpd, opSpn, opShS, opDP, tstCS) false
    --
    -- Event 2
    opShS2 = shapeStandard shape:1 size:8        -- Cube
    opDP2 = displayParticles color:blue type:6
    evn2 = apActFn pF #(opShS2, opDP2) false
    tstCS.SetNextActionList evn2 tstCS
    playAnimation()
)
fCS 1

Два кадра анимации приведены на рис. 1.36.

        Collision_Spawn

Рис. 1.36. Изменение формы и количества частиц после столкновения (t = 0 и t = 70)

1.10.2. УДАЛЕНИЕ ЧАСТИЦ

Удаление частиц выполняют оператор 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.

        DeleteParticles

Рис. 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.

        DeleteParticles

Рис. 1.38. Система с тестом Script_Test

В следующем примере генерируемые частицы направляются от центра иконки эмиттера (в операторе Speed свойство Direction = 1, или Icon Center Out). При этом частицы не выходят за пределы некоторого круга, поскольку при достижении заданного возраста они удаляются из системы.

fn fDP2 h = (
    animationRange = interval 0f 100f
    delete $*
    messageBox("Delete Particle out of circle")
    cl = cylinder radius:2 height:-80 pos:[0, 0, 20] wireColor:black
    pF = PF_Source enable_Particles:on pos:[0, 0, 20] \
        quantity_Viewport:100 show_Logo:off show_Emitter:off
    rotate #(pF, cl) -45 [0, 1, 0]
    particleFlow.BeginEdit()
    opBth = birth emit_Start:0 emit_Stop:16000 amount:5000
    opPI = position_Icon()
    opSpd = speed speed:50 direction:1
    opShS = shapeStandard()
    opDP = displayParticles color:black type:9
    tstAT = age_Test test_Value:1600 variation:0
    apActFn pF #(opBth, opPI, opSpd, opDP, tstAT) false
    --
    -- Event 2
    opDlt2 = deleteParticles type:0
    evn2 = apActFn pF #(opDlt2) false
    tstAT.SetNextActionList evn2 tstAT
    playAnimation()
)
fDP2 1

Результат приведен на рис. 1.39.

        DeleteParticles

Рис. 1.39. Система при t = 75f и структурная схема системы

1.11. ПРИМЕРЫ УПОТЕБЛЕНИЯ СВОЙСТВ ЧАСТИЦ

1.11.1. СВОЙСТВО PARTICLESPEED

В примере направление движения частицы изменяется на противоположное, если ее возраст кратен 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.

        Свойство ParticleSpeed

Рис. 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.

        Свойство ParticleSpeed

Рис. 1.41. Система при t = 80f и структурная схема системы

После генерации все частицы перемещаются вниз по одной линии, что обеспечивается соответствующими свойствами оператора Position_Icon.

1.11.2. СВОЙСТВО PARTICLESPIN

В примере увеличивается скорость вращения частиц по мере их старения.

fn fPSpn h = (
    animationRange = interval 0f 100f
    delete $*
    messageBox("Particle Spin")
    pF = PF_Source enable_Particles:true quantity_Viewport:100
    particleFlow.BeginEdit()
    opBth = birth emit_Start:0 emit_Stop:480 amount:2
    opPI = position_Icon location:4
    opSpd = speed speed:20 direction:4        -- Random Horizontal
    opShS = shapeStandard shape:0 size:15
    opDP = displayParticles color:[30, 30, 30] type:6
    opSO = script_Operator proceed_Script:"
    on channelsUsed pCont do (
        pCont.UseTime = true
        pCont.UseAge = true
        pCont.UseOrientation = true
        pCont.UseSpin = true
    )
    on proceed pCont do (
        nP = pCont.NumParticles()
        for k = 1 to nP do (
            pCont.ParticleIndex = k
            pAg = pCont.ParticleAge as float / 100000
            pCont.ParticleSpin = (angleAxis (2 * pAg) [1, 0, 1])
        )
    )"
    apActFn pF #(opBth, opPI, opSpd, opShS, opDP, opSO) false
    playAnimation()
)
fPSpn 1

Два кадра анимации приведены на рис. 1.42.

        Свойство ParticleSpin

Рис. 1.42. Частицы при t = 40f и t = 78f

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

delete $*
pF = PF_Source enable_Particles:true quantity_Viewport:100
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:0 amount:2
opPI = position_Icon location:4
opSpd = speed speed:25 direction:4        -- Random Horizontal
opShS = shapeStandard shape:0 size:20
opDP = displayParticles color:gray type:6
opSpn = spin direction:1 spinRate:720 \
    spin_X_Axis:1 spin_Y_Axis:0 spin_Z_Axis:0
apActFn pF #(opBth, opPI, opSpd, opShS, opDP, opSpn) false
playAnimation()

1.11.3. СВОЙСТВО PARTICLESHAPE

В примере частица после генерации имеет форму сферы, при достижении возраста 25f она приобретает форму цилиндра, в возрасте 50f – форму конуса, а в возрасте 75f – форму куба.

fn fPShp h = (
    animationRange = interval 0f 100f
    delete $*
    messageBox("Particle Shape")
    global sphM, clM, cnM, cbM
    sph = sphere radius:5
    cl = cylinder radius:5 height:7
    cn = cone radius1:5 radius2:0 height:7
    cb = box length:7 width:7 height:7
    sphM = sph.Mesh; clM = cl.Mesh; cnM = cn.Mesh; cbM = cb.Mesh
    hide #(sph, cl, cn, cb)
    pF = PF_Source enable_Particles:true quantity_Viewport:100 pos:[0, 30, 0]
    particleFlow.BeginEdit()
    opBth = birth emit_Start:0 emit_Stop:0 amount:3
    opPI = position_Icon location:4
    opSpd = speed speed:30 variation:0 direction:4    -- 4 - Random Horizontal
    opDP = displayParticles color:gray type:6
    opSO = script_Operator proceed_Script:"
    on channelsUsed pCont do (
        pCont.UseTime = true
        pCont.UseAge = true
        pCont.UseShape = true
    )
    on proceed pCont do (
        nP = pCont.NumParticles()
        for k = 1 to nP do (
            pCont.ParticleIndex = k
            pAg = (pCont.ParticleAge as integer) / 160
            case pAg of (
                0f : pCont.ParticleShape = sphM
                25f : pCont.ParticleShape = clM
                50f : pCont.ParticleShape = cnM
                75f : pCont.ParticleShape = cbM
            )
        )
    )"
    apActFn pF #(opBth, opPI, opSpd, opDP, opSO) false
    playAnimation()
)
fPShp 1

Смену формы частицы иллюстрирует рис. 1.43.

        Свойство ParticleShape

Рис. 1.43. Частицы в точках 24f, 49f, 74f и 85f

Оператора задания форма частицы в вышеприведенном коде опущен, поскольку форма частицы в каждый момент времени известна и задается как значение свойства ParticleShape.
Структурная схема рассмотренной системы проста (рис. 1.44).

        Свойство ParticleShape

Рис. 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 ссылкой на соответствующий объект.

delete $*
sph = sphere radius:5
cl = cylinder radius:5 height:7
cn = cone radius1:5 radius2:0 height:7
cb = box length:7 width:7 height:7
hide #(sph, cl, cn, cb)
pF = PF_Source enable_Particles:true quantity_Viewport:100
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:0 amount:3
opPI = position_Icon location:4
opSpd = speed speed:25 direction:4    -- Random Horizontal
opDP = displayParticles color:gray type:6
opSI = shape_Instance shape_Object:sph
tstAT = age_Test test_Value:3200 variation:0
apActFn pF #(opBth, opPI, opSpd, opDP, opSI, tstAT) false
--
-- Event 2
particleFlow.BeginEdit()
opDP2 = displayParticles color:gray type:6
opSI2 = shape_Instance shape_Object:cl
tstAT2 = age_Test test_Value:6400 variation:0
evn2 = apActFn pF #(opDP2, opSI2, tstAT2) false
tstAT.SetNextActionList evn2 tstAT
--
-- Event 3
opDP3 = displayParticles color:gray type:6
opSI3 = shape_Instance shape_Object:cn
tstAT3 = age_Test test_Value:9600 variation:0
evn3 = apActFn pF #(opDP3, opSI3, tstAT3) false
tstAT2.SetNextActionList evn3 tstAT2
--
-- Event 4
opDP4 = displayParticles color:gray type:6
opSI4 = shape_Instance shape_Object:cb
evn4 = apActFn pF #(opDP4, opSI4) false
tstAT3.SetNextActionList evn4 tstAT3
playAnimation()

Структурная схема рассмотренной системы приведена на рис. 1.45.
        Свойство ParticleShape

Рис. 1.45. Система с частицами, форма которых зависит от и возраста

Структурную схему можно упростить, изъяв оператор DisplayParticles из событий системы и включив его в состав объекта PF_Source (рис. 1.46).
        Свойство ParticleShape

Рис. 1.46. Редуцированная структурная схема системы частиц

1.11.4. ЧАСТИЦЫ - БУКВЫ ТЕКСТА. СВОЙСТВО PARTICLESHAPE

В примере задается произвольный текст, из букв которого формируется массив. Далее в 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.

        Свойство ParticleShape

Рис. 1.47. Форма частиц определяется как Mesh одной из букв текста

1.11.5. СВОЙСТВО PARTICLEINTEGER

В примере частицы случайным образом распределяются между тремя сферами. Это обеспечивается, во-первых, за счет задания Assignment_Type = 4 (By Script Integer) в тесте Find_Target, и, во-вторых, за счет определения значения свойства ParticleInteger случайным числом из диапазона 0 – 2. Частицы, у которых ParticleInteger = nI, будут устремляться к цели, индекс которой в массиве целей равен nI + 1. Массив целей содержит свойство Target_Objects теста Find_Target.

fn fPInt h = (
    animationRange = interval 0f 100f
    delete $*
    messageBox("Particle Integer")
    sph = sphere radius:5 pos:[0, 70, 5] wireColor:red    -- black
    sph2 = sphere radius:5 pos:[70, 0, 5] wireColor:red
    sph3 = sphere radius:5 pos:[30, 30, 10] wireColor:red
    pF = PF_Source enable_Particles:true quantity_Viewport:100
    particleFlow.BeginEdit()
    opBth = birth emit_Start:0 emit_Stop:16000 amount:500
    opPI = position_Icon()
    opSpd = speed speed:300 variation:30 divergence:40
    opShS = shapeStandard()
    opDP = displayParticles color:blue type:9    -- (color 100 100 100)
    tstFT = find_Target assignment_Type:4 speed_Type:0 target_Type:1 \
        target_Objects:#(sph, sph2, sph3) test_Type:1 timing_Type:2 \
        use_Cruise_Speed:on cruise_Speed:40 \
        icon_Size:0 lock_On_Target:on
    opSO = script_Operator proceed_Script:"
    on channelsUsed pCont do (
        pCont.UseTime = true
        pCont.UseInteger = true
    )
    on proceed pCont do (
        nP = pCont.NumParticles()
        for i = 1 to nP do
        (    pCont.ParticleIndex = i
            if pCont.ParticleNew then pCont.ParticleInteger = random 0 2
        )
    )"
    apActFn pF #(opBth, opPI, opSpd, opShS, opDP, opSO, tstFT) false
    playAnimation()
)
fPInt 1

Значение свойства ParticleInteger выбирается случайным образом из чисел 0, 1 и 2 и устанавливается для новой частицы (ее свойство ParticleNew = true). Такой подход позволяет равномерно распределить частицы между целями (рис. 1.48).

        Свойство ParticleInteger

Рис. 1.48. Система частиц с тремя целями при t = 28f

Если в тесте Find_Target свойство Lock_On_Target = on, то тест Find_Target должен находиться в структурной схеме после оператора Script_Operator, в котором частицам назначаются цели. Это связано с тем, что при такой установке частицы распределяются между целями единожды в начале анимации. Если же Lock_On_Target = off, то приложение может решать эту задачу и в процессе анимации.

1.11.6. СВОЙСТВО PARTICLEFLOAT

При наличии сил, действующих на частицы, можно интерпретировать массу частицы как функцию от ее объема. Для этого употребляется свойство ParticleFloat, регулирующее меру воздействия силы на частицы. Значение свойства определяется как величина, обратно пропорциональная кубу коэффициента масштабирования частицы, хранимого свойством ParticleScale. Таким образом, при уменьшении объема частицы значение свойства ParticleFloat будет резко увеличиваться и, следовательно, будет возрастать степень влияния силы (в нижеприводимом примере это ветер) на частицу.
В интерфейсе оператора Force следует выбрать переключатель Influence группы Use Script Float As списка Script Wiring интерфейса силы, действующей на частицы (рис. 1.49).

        Свойство ParticleFloat

Рис. 1.49. Значение ParticleFloat будет влиять на величину силы

Для выбора переключателя открывается Particle View, в контейнере события выбирается оператор Force – правая кнопка мыши – Use Script Wiring – раскрыть список Script Wiring – Influence. Программно этот результат достигается после установки следующих свойств силы, в качестве которой использован ранее введенный в сцену ветер wnd:

fc = force()
fc.force_Space_Warps = #(wnd)
fc.UseScriptWiring = 1
fc.Use_Script_Float = 1            -- Influence

В примере одномоментно генерируется 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.

        Свойство ParticleFloat

Рис. 1.50. Сцена при t = 30f и t = 70f

В примере старые частицы держатся кучкой, а молодые разлетаются (рис. 1.51).

        Свойство ParticleFloat

Рис. 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

1.11.7. СВОЙСТВО PARTICLEVECTOR

В примере частицы в результате выполнения теста Find_Target с Aim_Point_Type = 2 скапливаются в области, определяемой в proceed-обработчике оператора Script_Operator посредством задания значения свойства ParticleVector каждой частицы из специально подобранного диапазона [20, -20, 0] – [60, -60, 0].
При этом скопление частиц (рис. 1.52) будет наблюдаться, если изменять значение свойства ParticleVector при каждом вызове proceed-обработчика (частицы, словно, не знают, в каком направлении им двигаться).

        Свойство ParticleVector

Рис. 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).

        Свойство ParticleVector

Рис. 1.53. Обмен частицами: а – частицы покидают объекты; б – цели найдены

Структурная схема систем частиц рассматриваемого примера приведена на рис. 1.54.

        Свойство ParticleVector

Рис. 1.54. Системы частиц с двумя операторами Position_Object

Замечание.. Как и ранее, для сокращения кода опущены операторы RenderParticles и Rotation.

1.11.8. СВОЙСТВО PARTICLETM


В примере частицы замещаются сферическими помощниками 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.

        Свойство ParticleTM

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

Структурная схема системы частиц приведена на рис. 1.56.

        Свойство ParticleTM

Рис. 1.56. Структурная схема системы имитации дыма

1.11.9. СВОЙСТВО PARTICLEPOSITION

В примере генерируемые частицы располагаются по периметру квадрата, и им оператором 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.

        Свойство ParticlePosition

Рис. 1.57. Система частиц при t = 0f и t = 40f

        Свойство ParticlePosition

Рис. 1.58. Система частиц, генерирующая частицы в углах квадрата

1.11.10. СВОЙСТВА PARTICLEAGE, PARTICLESHAPE, PARTICLEPOSITION И PARTICLESPEED

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

fn fPPstnSpd h = (
    animationRange = interval 0f 100f
    delete $*
    messageBox("Particle Position and Particle Speed")
    global spMsh, clMsh
    sp = sphere radius:5 wireColor:red segs:8
    cl = cylinder radius:5 height:7 wireColor:blue
    spMsh = sp.Mesh
    clMsh = cl.Mesh
    hide #(sp, cl)
    pF = PF_Source enable_Particles:true quantity_Viewport:100 \
        show_Logo:off show_Emitter:off
    particleFlow.BeginEdit()
    opBth = birth amount:100
    opPI = position_Icon()
    opDP = displayParticles type:6 color:[50, 50, 50]
    opSO = script_Operator proceed_Script:"
    on channelsUsed pCont do (
        pCont.UseShape = true
        pCont.UseAge = true
        pCont.UsePosition = true
        pCont.UseSpeed = true
    )
    on proceed pCont do (
        nP = pCont.NumParticles()
        for i = 1 to nP do (
            pCont.ParticleIndex = i
            pAg = pCont.ParticleAge
            pAgM = 30
            posB = 1
            p0 = [0, 0, 0]
            s = 0.03
            case of (
                (mod i 4 == 0) : (
                        if pCont.ParticleNew do pCont.ParticleShape = spMsh
                        case of (
                            (pCont.ParticlePosition[1] < -posB and pAg > pAgM) : \
                                pCont.ParticleSpeed = [0, 0, 0]
                            (pAg < pAgM) : pCont.ParticleSpeed = random p0 [s, 0, 0]
                            default : pCont.ParticleSpeed = random p0 [-s, 0, 0]
                        )
                    )
                (mod i 3 == 0) : (
                        if pCont.ParticleNew do pCont.ParticleShape = clMsh
                        case of (
                            (pCont.ParticlePosition[2] < -posB and pAg > pAgM) : \
                                pCont.ParticleSpeed = p0
                            (pAg < pAgM) : pCont.ParticleSpeed = random p0 [0, s, 0]
                            default : pCont.ParticleSpeed = random p0 [0, -s, 0]
                        )
                    )
                (mod i 2 == 0) : (
                        if pCont.ParticleNew do pCont.ParticleShape = spMsh
                        case of (
                            (pCont.ParticlePosition[1] > posB and pAg > pAgM) : \
                                pCont.ParticleSpeed = p0
                            (pAg < pAgM) : pCont.ParticleSpeed = random p0 [-s, 0, 0]
                            default : pCont.ParticleSpeed = random p0 [s, 0, 0]
                        )
                    )
                default : (
                        if pCont.ParticleNew do pCont.ParticleShape = clMsh
                        case of (
                            (pCont.ParticlePosition[2] > posB and pAg > pAgM) : \
                                pCont.ParticleSpeed = p0
                            (pAg < pAgM) : pCont.ParticleSpeed = random p0 [0, -s, 0]
                            default : pCont.ParticleSpeed = random p0 [0, s, 0]
                        )
                    )
            )
        )
    )"
    apActFn pF #(opBth, opPI, opDP, opSO) false
    playAnimation()
)
fPPstnSpd 1

На рис. 1.59 показаны убежавшие, а затем вернувшиеся частицы

        Свойства ParticleAge, ParticleShape, ParticlePosition и ParticleSpeed

Рис. 1.59. Система частиц в точках t = 25f и t = 85f

1.12. УПРАВЛЕНИЕ ДВИЖЕНИЕМ ЧАСТИЦ

1.12.1. ВИДЫ ДВИЖЕНИЙ

Частицы могут совершать поступательное и движение и вращаться вокруг указанного вектора локальной системы координат частицы.
Первое движение определяется вектором скорости частицы, второе – ориентацией ее локальной системы координат в мировой систем координат и спином частицы.
Вектор скорости частицы хранит ее свойство ParticleSpeed, а ориентацию и спин – соответственно свойства ParticleOrientation и ParticleSpin.
Управлять движением частицы – это значит надлежащим образом изменять ее вектор скорости, ориентацию и спин.

1.12.2. ГЕНЕРАЦИЯ ЧАСТИЦ

В простейшем случае рождение и отображение частиц обеспечат три следующие оператора: Структурная схема системы, обеспечивающей появление и отображение частиц, приведена на рис. 1.60.

        Генерация частиц

Рис. 1.60. Структурная схема минимальной системы частиц

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

fn fETp h = (
    animationRange = interval 0f 50f
    messageBox("Emitter_Type")
    for k = 0 to 3 do (
        delete $*
        esZ = 40
        pF = PF_Source enable_Particles:true emitter_Type:k \
            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:9        -- Asterisks (6 - Geometry)
        apActFn pF #(opBth, opPI, opDP) false
        playAnimation()
    )
)

Оператор 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.
Это число вычисляется по следующей формуле

Amount * (Emit_Stop – Emit_Start) / sliderTime = 200 * (30 – 0) / 15

Также это число отображается в списке 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.

1.12.3. ПЕРЕМЕЩЕНИЕ ЧАСТИЦ

1.12.3.1. АНИМАЦИЯ ЭМИТТЕРА
Анимация эмиттера вызовет перемещение сгенерированных им частиц. Характер движения зависит как вида преобразований координат эмиттера, так и от значений его свойств.
В примере в сцену вводится дуга, используемая затем в качестве пути для движения эмиттера (используется ограничение 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)
)

Задача. Выполнить анимацию при скрытом пути, не показывая иконок эмиттера и его логотипа.

Решение.

hide pth
pF = PF_Source emitter_Type:1 quantity_Viewport:100 \
    show_Logo:off show_Emitter:off

Задача. Удалить ключи анимации радиуса окружности и масштабирования объекта PF_Source. Преобразовать окружность в фигуру, напоминающую цифру 8, и повторить анимацию.

Решение.
  1. Восстановить видимость пути, эмиттера и его логотипа. Удаления ключа выполняется в следующем порядке: выбрать объект – выбрать маркер ключа на временной шкале – правая кнопка мыши – Delete Key – выбрать, например Circle01: Radius, и удалить ключ – повторить операцию для всех имеющихся на временной шкале ключей.
  2. Выбрать и скрыть объект PF_Source (правая кнопка мыши – Hide Selection).
  3. Выбрать окружность и снабдить ее модификаторами Normalize Spline (например, с длиной сегмента в 20 единиц) и Edit_Spline.
  4. Ввести в видовом порте Top цифру 8 как примитив (сплайн) Text, уровнять ее высоту с диаметром окружности и расположить внутри окружности (рис. 1.67).

            Перемещение частиц

    Рис. 1.67. Окружность с модификатором Normalize Spline

  5. Выбрать в стеке модификаторов Edit Spline, перейти на уровень подобъекта Vertex и отредактировать надлежащим образом сплайн (окружность), выбирая и перемещая его вершины, добавляя при необходимости новые вершины посредством инструментов Refine и Insert, выполняя иные преобразования.
  6. Восстановить видимость иконки эмиттера и его логотипа, а затем удалить или скрыть примитив Text.
  7. Воспроизвести анимацию (рис. 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.
1.12.3.2. ПЕРЕМЕЩЕНИЕ ПОД ДЕЙСТВИЕМ СИЛ
Рожденные частицы начнут двигаться, если им придать некоторую скорость или воздействовать на них одной или несколькими силами.
В примере в сцену вводится слабый незатухающий без порывов сферический ветер, в структурную схему системы частиц добавляется оператор 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()
    )
)

Задача. Добавить в сцену и систему частиц незначительную направленную вниз гравитацию. Пронаблюдать одновременное действие ветра и гравитации на частицы.
Решение. В начало вышеприведенного кода добавляется следующее выражение:

grv = gravity strength:-0.01 gravityType:0 pos:[30, -30, 0]

Оператор Force вводится следующим конструктором:

opFc = force force_Space_Warps:#(wnd, grv)

Прочие строки кода остаются без изменений.
Структурная схема рассматриваемой системы частиц приведена на рис. 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

1.12.4. УПРАВЛЕНИЕ СКОРОСТЬЮ ПОСРЕДСТВОМ SPEED-ОПЕРАТОРОВ

1.12.4.1. ОПЕРАТОР SPEED
В системе частиц PF Source имеются следующие три Speed-оператора: Оператор Speed задает величину скорости и ее направление для всех частиц, которые они сохраняют до тех пор, пока на частицы не оказанного иного воздействия, например в виде силы ветра.
Частицы, рождаемые в одной точке в центре эмиттера, при различных вариантах задания скорости отображены на рис. 1.72 (использован видовой порт Front).
        Управление скоростью посредством Speed-операторов

Рис. 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 вводится в код одноименным конструктором:

opSpd = speed speed:100 variation:0 direction:k divergence:5 random_Seed:5430

в котором k изменяется в диапазоне 0 – 4.

fn fSpd h = (
    animationRange = interval 0f 50f
    messageBox("Speed Direction")
    for k = 0 to 4 do (
        delete $*
        pF = PF_Source enable_Particles:true emitter_Type:1 \
            pos:[0, 0, 0] quantity_Viewport:100
        rotate pF 90 [0, 1, 0]
        particleFlow.BeginEdit()
        opBth = birth emit_Start:0 emit_Stop:16000 amount:200
        opPI = position_Icon Location:4 distinct_Points_Only:on total_Distinct_Points:1
        opDP = displayParticles color:green type:9
        opSpd = speed speed: 100 variation:0 direction:k divergence:0 random_Seed:5430
        apActFn pF #(opBth, opPI, opDP, opSpd) false
        playAnimation()
    )
)

Свойство Divergence определяет предельно возможное отклонение частиц (в градусах) от заданного свойством Direction направления. Не используется, когда Direction = 3 (Random 3D). Пример действия свойства показан на рис. 1.73.

        Управление скоростью посредством Speed-операторов

Рис. 1.73. Направление Icon Center Out: а – Divergence = 0; б – Divergence = 30

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

fn fSpd2 h = (
    animationRange = interval 0f 50f
    messageBox("Speed Divergence")
    for k = 0 to 40 by 10 do (
        delete $*
        pF = PF_Source enable_Particles:true emitter_Type:2 \
            pos:[60, 0, 0] quantity_Viewport:100
        rotate pF 90 [0, 1, 0]
        particleFlow.BeginEdit()
        opBth = birth emit_Start:0 emit_Stop:16000 amount:200
        opPI = position_Icon Location:0 distinct_Points_Only:on total_Distinct_Points:1
        opDP = displayParticles color:green type:9
        opSpd = speed speed: 100 direction:0 divergence:k
        apActFn pF #(opBth, opPI, opDP, opSpd) false
        playAnimation()
    )
)

Следующий пример иллюстрирует совместное действие на частицы оператора 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.

        Управление скоростью посредством Speed-операторов

Рис. 1.74. Система в точках t = 33f и t = 90f

        Управление скоростью посредством Speed-операторов

Рис. 1.75. Структурная схема рассмотренной системы частиц (k = 2)

Если в систему частиц добавить оператор Position_Object, то в сцену можно ввести геометрический образ источника частиц, например в виде конуса. Это позволит наглядно имитировать процесс написания текста с перерывами, например, показывая частицы при наличии конуса в сцене, и прекращая их вывод при его отсутствии. При работе с Position_Object оператор Position_Icon может быть опущен.
Рассмотренный вариант анимации реализуется в вышеприведенном коде при k = 3.
1.12.4.2. EMITTER MOTION, SCRIPT_OPERATOR (ADDPARTICLE)
В примере из частиц создается буква "А". Эмиттером частиц является коническое перо, определяемое посредством свойства Emitter_Objects оператора Position_Object. Для пополнения чернилами перо опускается в чернильницу.

fn fEmSOPO h = (
    frmCnt = 300
    animationRange = interval 0f frmCnt
    msg = "Emitter motion, Script Operator (AddParticle)"
    messageBox(msg)
    pZ = 1
    sl = se = off
    clr = [135, 110, 8]
    mtS = standard diffuse:clr showInViewport:true
    cnH = 30
    frP = frmCnt / 5
    frAdd = frmCnt / 10
    frS = frP + frAdd
    frP2 = frmCnt / 2
    frS2 = frP2 + frAdd
    frE = 8 * frmCnt / 10
    pInk = [80, -40, pZ]
    rt = quat 0.55 -0.2 -0.3 0.75
    delete $*
    sliderTime = 0f
    text text:msg size:12 wireColor:black rotation:rt pos:[5, 0, -70]
    cn = cone radius1:2 radius2:1 height:cnH pos:[0, 0, pZ] material:mtS
    cn2 = copy cn
    cn2.Radius1 = 1; cn2.Height = 2
    cn.Pivot = [0, 0, pZ + cnH]
    rotate cn 190 [1, 0, 0]
    pOF = POmniFlect pos:[-5, -5, 0] width:80 height:80 timeOff:frmCnt
    arrPOF = #(pOF)
    cl = cylinder radius:4 height:5 pos:pInk wireColor:red
    pOF2 = POmniFlect pos:pInk width:5 height:5 timeOff:frmCnt
    arrPOF += pOF2
    hide pOF2
    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:(frmCnt * 160) type:1 rate:20
    opPO = position_Object Location:3 emitter_Objects:#(cn2) \
        animated_Shape:on distinct_Points_Only:on total_Distinct_Points:1
    opShS = shapeStandard shape:2 size:2
    opSpd = speed speed:50
    opDP = displayParticles color:red type:6    -- Geometry
    tstCS = collision collision_Nodes:arrPOF test_Type:0 speed_Option:2
    apActFn pF #(opBth, opPO, opShS, opSpd, opDP, tstCS) false
    animate on (
        at time 0f cn.Pos = cn2.Pos = [-23, -30, pZ]
        at time frP cn.Pos = cn2.Pos = [0, 30, pZ]
        at time (frP + 1) cn.Pos = cn2.Pos = pInk
        at time (frS - 1) cn.Pos = cn2.Pos = pInk
        at time frS cn.Pos = cn2.Pos = [0, 30, pZ]
        at time frP2 cn.Pos = cn2.Pos = [25, -30, pZ]
        at time (frP2 + 1) cn.Pos = cn2.Pos = pInk
        at time (frE - 1) cn.Pos = cn2.Pos = pInk
        at time frE cn.Pos = cn2.Pos = [13, -7, pZ]
        at time (frE + frAdd) cn.Pos = cn2.Pos = [-16, -7, pZ]
    )
    pcCn2X = cn2.Pos.X_Position.Controller
    pcCn2Y = cn2.Pos.Y_Position.Controller
    for kV = 3 to 6 do (
        kN = if kV > 4 then kV + 2 else kV
        gKX = getKey pcCn2X kN
        gKY = getKey pcCn2Y kN
        if mod kN 2 == 1 then (
            gKX.InTangentType = #step
            gKY.InTangentType = #step
        )
        else (
            gKX.OutTangentType = #step
            gKY.OutTangentType = #step
        )
    )
    playAnimation()
)
fEmSOPO 1

Два кадра анимации приведены на рис. 1.76.

        Управление скоростью посредством Speed-операторов

Рис. 1.76. Перо, чернильница и буква А

1.12.4.3. ОПЕРАТОР SPEEDBYICON
Оператор позволяет использовать свою иконку для управления величиной и направлением скорости частиц. При анимации движение иконки оператора передается частицам.
В течение одной системной единицы времени (числа кадров в секунду) частицы, следуя за иконкой оператора, могут изменить скорость на величину, не превышающую значения свойства Accel_Limit.
В каждый момент времени применяемая скорость вычисляется с учетом значения скорости в предшествующий момент времени. Степень влияния траектории иконки оператора на траекторию частиц регулируется свойством Influence, изменяемым в диапазоне 0.0 – 100.0.
Частицы в процессе анимации будут следовать ориентации иконки оператора, если свойство User_Icon_Orientation установить в true (on).
При движении частицы будут смещаться к иконке оператора, если установлено в true свойство Steer_Towards_Trajectory и расстояние частицы от траектории иконки превышает Distance единиц.
В примере в событие вводится оператор SpeedByIcon, и для анимации его движения используется ограничение пути path_Constraint, в качестве которого берется задаваемый в программе сплайн. Частицы, покидая эмиттер (введен оператором Position_Icon), следуют вдоль траектории, перенимая движение иконки оператора SpeedByIcon.

fn fSpdByI h = (
    animationRange = interval 0f 100f
    messageBox("SpeedByIcon: Steer_Towards_Trajectory")
    psn = [-75, -15, 0]
    psn2 = psn + [25, -15, -15]
    dst = 6.0
    for k = 1 to 3 do (
        delete $*
        std = if k == 3 then off else on
        pth = line pos:[0, 0, 0] wireColor:black
        addNewSpline pth
        addKnot pth 1 #smooth #curve psn
        addKnot pth 1 #smooth #curve [-25, 60, -10]
        addKnot pth 1 #smooth #curve [0, 0, 10]
        addKnot pth 1 #smooth #curve [60, -45, -10]
        addKnot pth 1 #smooth #curve [90, 20, 10]
        updateShape pth
        pF = PF_Source emitter_Type:0 pos:psn2 quantity_Viewport:100
        particleFlow.BeginEdit()
        opBth = birth emit_Start:0 emit_Stop:8000 amount:500
        opPI = position_Icon()
        opSBI = speedByIcon pos:psn use_Icon_Orientation:off \
            steer_Towards_Trajectory: std distance:(dst / k)
        pc = path_Constraint follow:on axis:2 path:pth
        opSBI.Pos.Controller = pc
        opDP = displayParticles type:1 color:(color 80 80 80)
        apActFn pF #(opBth, opPI, opSBI, opDP) false
        playAnimation()
    )
)
fSpdByI 1

На рис. 1.77 приведены результаты употребления оператора SpeedByIcon с различными значениями его свойства Steer_Towards_Trajectory.

        Управление скоростью посредством Speed-операторов

Рис. 1.77. Система частиц с оператором SpeedByIcon в точке t = 90f: а – Steer_Towards_Trajectory = on; б – Steer_Towards_Trajectory = off

1.13. НЕКОТОРЫЕ МАТЕРИАЛЫ САЙТА 100BYTE.RU

1.13.1. ГЕЙЗЕР

Между двумя отражателями совершают колебательные движения частицы, имитирующие кипящий водоем. В 4-х местах этого водоема вверх устремляются частицы (рис. 1.78).

        Гейзер

Рис. 1.78. Гейзер

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 fTstCS h = (
    aRng = 200
    animationRange = interval 0f aRng
    delete $*
    sliderTime = 0f
    msg = "Collision and Age Test"
    rt = quat 0.55 -0.2 -0.3 0.75
    text text:msg size:20 wireColor:black rotation:rt pos:[5, 0, -90]
    eSz = 75
    pOF = POmniFlect pos:[0, 0, -1] width:eSz height:eSz timeOff:aRng
    pOF2 = copy pOF; pOF2.Pos = [0, 0, 8]
    arrPOF = #(pOF, pOF2)
    hide arrPOF
    qt = eulerToQuat (eulerAngles 0 180 0)
    pF = PF_Source enable_Particles:true emitter_Type:0 \
        rotation:qt pos:[0, 0, 0] quantity_Viewport:100 \
        emitter_Length:eSz emitter_Width:eSz show_Logo:off show_Emitter:off
    particleFlow.BeginEdit()
    opBth = birth emit_Start:0 emit_Stop:0 amount:500
    opPI = position_Icon Location:4
    opSpd = speed speed:10 variation:5 direction:0 divergence:0
    opShS = shapeStandard shape:2 size:4
    opDP = displayParticles color:[153, 228, 214] type:6
    tstCS = collision collision_Nodes:arrPOF test_Type:0 speed_Option:0
        apActFn pF #(opBth, opPI, opShS, opSpd, opDP, tstCS) false
    eST = #(20 * 160, 40 * 160, 60 * 160, 20 * 160)
    eSP = for k2 = 1 to eST.Count collect eST[k2] + aRng * 160
    spd = #(70, 30, 40, 50)
    spdVr = for k2 = 1 to spd.Count collect (spd[k2] / 2)
    aTAdd = #(0.5 * 160, 1.0 * 160, 0.4 * 160, 0.5 * 160)
    aTV = for k2 = 1 to eST.Count collect (eST[k2] + aTAdd[k2])
    amt = #(90, 70, 60, 70)
    dvg = #(9, 7, 7, 7)
    pClr = #([255, 8, 137], [185, 8, 137], [8, 255, 8], [255, 137, 8])
    pV = eSz / 3
    pPs = #([-pV, -pV, 0], [-pV, pV, 0], [pV, 0, 0], [pV, -pV, 0])
    nEm = 4
    arrPF = #(); arrOpBth = #(); arrOpPI = #()
    arrOpSpd = #(); arrOpShS = #(); arrOpDP = #(); arrTstAg = #()
    arrOpDlt = #(); arrEvn2 = #()
    arrPF.Count = arrOpBth.Count = arrOpPI.Count = arrOpDlt.Count = arrEvn2.Count = \
    arrOpSpd.Count = arrOpShS.Count = arrOpDP.Count = arrTstAg.Count = nEm
    for k2 = 1 to nEm do (
        arrPF[k2] = PF_Source enable_Particles:true emitter_Type:0 \
            rotation:qt pos:pPs[k2] \
            quantity_Viewport:100     show_Logo:off show_Emitter:off
        particleFlow.BeginEdit()
        arrOpBth[k2] = birth emit_Start:eST[k2] emit_Stop:eSP[k2] amount:amt[k2]
        arrOpPI[k2] = position_Icon Location:4 distinct_Points_Only:on total_Distinct_Points:1
        arrOpSpd[k2] = speed speed:spd[k2] variation:spdVr[k2] direction:0 divergence:dvg[k2]
        arrOpShS[k2] = shapeStandard shape:2 size:2
        arrOpDP[k2] = displayParticles color:pClr[k2] type:6
        arrTstAg[k2] = age_Test test_Type:1 condition_Type:1 test_Value:aTV[k2] variation:0
        apActFn arrPF[k2] #(arrOpBth[k2], arrOpPI[k2], arrOpShS[k2], arrOpSpd[k2], \
        arrOpDP[k2], arrTstAg[k2]) false
        --
        -- Event 2
        particleFlow.BeginEdit()
        arrOpDlt[k2] = deleteParticles type:0
        arrEvn2[k2] = apActFn arrPF[k2] #(arrOpDlt[k2]) false
        arrTstAg[k2].SetNextActionList arrEvn2[k2] arrTstAg[k2]
    )
    playAnimation()
)
fTstCS h

1.13.2. ПЕСОЧНЫЕ ЧАСЫ


Корпус песочных часов создается из двух конусов. В сцену вводятся 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.79.

        Песочные часы

Рис. 1.79. Песочные часы: два кадра анимации

1.13.3. КРАСНАЯ ЧАСТИЦА


Реализована следующая история.
Жили-были синие частицы, видимо, не понимая, что их существование ограничено небольшим замкнутым пространством.
В один прекрасный день появилась красная частица, которая вскоре обнаружила, что она и синие сидельцы по сути живут за решеткой (рис. 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.13.4. В ОТКРЫТУЮ ДВЕРЬ

В клетке находятся частицы (рис. 1.83, а). Время от времени небольшая крышка, расположенная в верхней части клетки, ненадолго открывается, и часть частиц устремляется наружу (рис. 1.83, б).

        Свобода

Рис. 1.83. Два кадра анимации: а - выход почти закрыт; б - крышка приоткрыта

Как и в предыдущем примере, доступное для частиц пространство ограничивается отражателями POmniFlect-отражателями, расположенными на сторонах клетки. Всего отражателей 8, пи этом 3 из них расположены на верхней грани клетки. Два из этих отражателей неподвижны, а один, имитирующий крышку, периодически открывается, позволяя части частиц покинуть клетку, а затем закрывается. Используемое в этом действии аффинное преобразование координат – Rotate.
Все отражатели указываются в качестве значения свойства Collision_Nodes теста Collision первого события системы частиц.
Частицы, покидающие клетку, меняют свой цвет с синего на красный.
Для отображения клетки употреблен модификатор Lattice (решетка).

function apActFn pF arrOps hasRP = (
-- Код функции приведен выше

)
fn fPPstn2 h = (
    delete $*
    sliderTime = 0f
    msg = "Particle Position: Freedom"
    messageBox(msg)
    rt = quat 0.55 -0.2 -0.3 0.75
    max tool zoomExtents
    text text:msg size:20 wireColor:black rotation:rt pos:[5, 0, -90]
    aRng = 300
    animationRange = interval 0f aRng
    --
    spdVal = 50
    pSz = 2
    sph = sphere radius:pSz
    hide #(sph)
    global cbSz = 50, cbSz2
    cbSz2 = cbSz / 2
    --
    mtS = standard diffuse:gray showInViewport:true opacity:100
    opc2 = 100
    mtS2 = standard diffuse:gray showInViewport:true opacity:opc2
    pOF = #()
    append pOF (POmniFlect pos:[0, 0, 0] width:cbSz height:cbSz timeOff:aRng)
    for k = 1 to 7 do append pOF (copy pOF[1])
    rotate pOF[1] (eulerAngles 0 90 0); move pOF[1] [cbSz2, 0, 0]
    rotate pOF[2] (eulerAngles 0 -90 0); move pOF[2] [-cbSz2, 0, 0]
    rotate pOF[3] (eulerAngles 90 0 0); move pOF[3] [0, -cbSz2, 0]
    rotate pOF[4] (eulerAngles -90 0 0); move pOF[4] [0, cbSz2, 0]
    move pOF[5] [0, 0, -cbSz2]
    b3S = cbSz / 3
    b3S2 = b3S / 2
    bxPs2 = [b3S2, 0, cbSz / 2]
    x3 = cbSz2 - b3S2
    bxPs3 = [-x3, b3S2, cbSz / 2]
    bxPs4 = [-x3, -x3, cbSz / 2]
    bx = box length:cbSz width:cbSz height:cbSz material:mtS2 \
        lengthSegs:3 widthSegs:3 heightSegs:3 pos:[0, 0, -cbSz / 2]
    lttc = Lattice strut_Radius:1 strut_Segments:1 strut_Sides:3 joint_Radius:2
    addmodifier bx lttc
    bx2 = box length:cbSz width:(cbSz - b3S) height:(cbSz / 20) material:mtS \
        lengthSegs:1 widthSegs:1 heightSegs:1 pos:bxPs2
    bx3 = box length:(cbSz - b3S) width:b3S height:(cbSz / 20) material:mtS \
        lengthSegs:1 widthSegs:1 heightSegs:1 pos:bxPs3
    bx4 = box length:b3S width:b3S height:(cbSz / 20) material:mtS \
        lengthSegs:1 widthSegs:1 heightSegs:1 pos:bxPs4
    pOF[6].Pos = bx2.Pos; pOF[6].Width = bx2.Width; pOF[6].Height = bx2.Length
    pOF[7].Pos = bx3.Pos; pOF[7].Width = bx3.Width; pOF[7].Height = bx3.Length
    pOF[8].Pos = bx4.Pos; pOF[8].Width = bx4.Width; pOF[8].Height = bx4.Length
    bx4.Pivot = pOF[8].Pivot = [-cbSz2 + b3S, -cbSz2 + b3S, cbSz2]
    select (#(bx, bx2, bx3, bx4) + pOF)
    gr =group selection
    rotate gr -10 [0, 0, 1]
    clearSelection()
    ungroup gr
    hide pOF
    --
    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:0
    particleFlow.BeginEdit()
    opBth = birth emit_Start:0 emit_Stop:0 amount:300
    opPI = position_Icon location:4 subframe_Sampling:on
    opDP = displayParticles color:black type:6    -- Geometry
    opSI = shape_Instance shape_Object:sph
    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
    tstCS = collision collision_Nodes:pOF test_Type:0 speed_Option:0
    tstST = script_Test proceed_Script:"
    on channelsUsed pCont do (
        pCont.UseTime = true
        pCont.UsePosition = true
    )
    on proceed pCont do (
        nP = pCont.NumParticles()
        for k in 1 to nP do (
            pCont.ParticleIndex = k
            pPs = pCont.ParticlePosition
            if pPs[3] > cbSz2 then (
                pCont.ParticleTestStatus = true
                pCont.ParticleTestTime = pCont.ParticleTime
            )
        )
    )"
    apActFn pF #(opBth, opPI, opSI, opSpd, opSP, opDP, tstCS, tstST) false
    --
    -- Event 2
    particleFlow.BeginEdit()
    opDP2 = displayParticles type:6 color:red
    evn2 = apActFn pF #(opDP2) false
    tstST.SetNextActionList evn2 tstST
    --
    bx4T = bx4.Transform
    with redraw off (
        rotate bx4 (eulerAngles -30 45 0)
        bx4T2 = bx4.Transform
        aSt = aRng / 4
        animate on (
            at time 0 bx4.Transform = pOF[8].Transform = bx4T
            at time aSt bx4.Transform = pOF[8].Transform = bx4T2
            at time (2 * aSt) bx4.Transform = pOF[8].Transform = bx4T
            at time (2.5 * aSt) bx4.Transform = pOF[8].Transform = bx4T
            at time (3.5 * aSt) bx4.Transform = pOF[8].Transform = bx4T2
        )
    )
    playAnimation()
)
fPPstn2 1

1.13.5. ДОМОЙ

Частицы направляются назад в клетку (рис. 1.84, а). Однако часть частиц вновь выходит наружу (рис. 1.84, б).

        Домой

Рис. 1.84. Два кадра анимации: а - почти все дома; б - есть непослушные

function apActFn pF arrOps hasRP = (
-- Код функции приведен выше

)
fn fPPstn3 h = (
    delete $*
    sliderTime = 0f
    msg = "Particle Position: Go home"
    messageBox(msg)
    rt = quat 0.55 -0.2 -0.3 0.75
    max tool zoomExtents
    text text:msg size:20 wireColor:black rotation:rt pos:[5, 0, -90]
    aRng = 300
    animationRange = interval 0f aRng
    --
    spdVal = 40
    amtVal = 50
    pSz = 2
    sph = sphere radius:pSz
    hide #(sph)
    qt = eulerToQuat (eulerAngles 40 4 -140)
    dPY = 27
    dPZ = 30
    sF = 75
    cn = cone radius1:0 radius2:3 height:16 rotation:qt \
        pos:[-10, -7 + dpY, 15 + dpZ] wireColor:[135, 110, 8]
    global cbSz = 30, cbSz2
    cbSz2 = cbSz / 2
    --
    mtS = standard diffuse:gray showInViewport:true opacity:100
    opc2 = 100
    mtS2 = standard diffuse:gray showInViewport:true opacity:opc2
    pOF = #()
    append pOF (POmniFlect pos:[0, 0, 0] width:cbSz height:cbSz timeOff:aRng)
    for k = 1 to 6 do append pOF (copy pOF[1])
    rotate pOF[1] (eulerAngles 0 90 0); move pOF[1] [cbSz2, 0, 0]
    rotate pOF[2] (eulerAngles 0 -90 0); move pOF[2] [-cbSz2, 0, 0]
    rotate pOF[3] (eulerAngles 90 0 0); move pOF[3] [0, -cbSz2, 0]
    rotate pOF[4] (eulerAngles -90 0 0); move pOF[4] [0, cbSz2, 0]
    move pOF[5] [0, 0, -cbSz2]
    b3S = cbSz / 3
    b3S2 = b3S / 2
    bxPs2 = [b3S2, 0, cbSz / 2]
    x3 = cbSz2 - b3S2
    bxPs3 = [-x3, b3S2, cbSz / 2]
    bx = box length:cbSz width:cbSz height:cbSz material:mtS2 \
        lengthSegs:3 widthSegs:3 heightSegs:3 pos:[0, 0, -cbSz / 2]
    lttc = Lattice strut_Radius:1 strut_Segments:1 strut_Sides:3 joint_Radius:2
    addmodifier bx lttc
    bx2 = box length:cbSz width:(cbSz - b3S) height:(cbSz / 20) material:mtS \
        lengthSegs:1 widthSegs:1 heightSegs:1 pos:bxPs2
    bx3 = box length:(cbSz - b3S) width:b3S height:(cbSz / 20) material:mtS \
        lengthSegs:1 widthSegs:1 heightSegs:1 pos:bxPs3
    pOF[6].Pos = bx2.Pos; pOF[6].Width = bx2.Width; pOF[6].Height = bx2.Length
    pOF[7].Pos = bx3.Pos; pOF[7].Width = bx3.Width; pOF[7].Height = bx3.Length
    select (#(bx, bx2, bx3) + pOF)
    gr =group selection
    rotate gr -10 [0, 0, 1]
    clearSelection()
    ungroup gr
    hide pOF
    --
    pF = PF_Source enable_Particles:true emitter_Type:3 \
        pos:[0, 0, 0] quantity_Viewport:100 rotation:qt \
        show_Logo:off show_Emitter:off integration_for_Viewport:0
    particleFlow.BeginEdit()
    opBth = birth emit_Start:0 emit_Stop:(aRng * 160) amount:amtVal
    opDP = displayParticles color:black type:6    -- Geometry
    opPO = position_Object emitter_Objects:#(cn) location:0
    opSI = shape_Instance shape_Object:sph
    opSpd = speed direction:0 speed:spdVal
    opSP = scaleParticles type:0 X_Scale_Factor:sF \
        Y_Scale_Factor:sF Z_Scale_Factor:sF
    tstCS = collision collision_Nodes:pOF test_Type:0 speed_Option:0
    apActFn pF #(opBth, opPO, opSI, opSpd, opSP, opDP, tstCS) false
    --
    -- Event 2
    particleFlow.BeginEdit()
    opDP2 = displayParticles type:6 color:red
    tstCS2 = collision collision_Nodes:pOF test_Type:0 speed_Option:0
    tstST2 = script_Test proceed_Script:"
    on channelsUsed pCont do (
        pCont.UseTime = true
        pCont.UsePosition = true
    )
    on proceed pCont do (
        nP = pCont.NumParticles()
        for k in 1 to nP do (
            pCont.ParticleIndex = k
            pPs = pCont.ParticlePosition
            if pPs[3] > cbSz2 then (
                pCont.ParticleTestStatus = true
                pCont.ParticleTestTime = pCont.ParticleTime
            )
        )
    )"
    evn2 = apActFn pF #(opDP2, tstCS2, tstST2) false
    tstCS.SetNextActionList evn2 tstCS
    --
    -- Event 3
    particleFlow.BeginEdit()
    opDP3 = displayParticles type:6 color:green
    evn3 = apActFn pF #(opDP3) false
    tstST2.SetNextActionList evn3 tstST2
    playAnimation()
)
fPPstn3 1

1.13.6. КРАСНАЯ АРМИЯ ВСЕХ СИЛЬНЕЙ


В примере воздушные суда Красной Армии уничтожают наземные технические средства противника (рис. 1.85).

        Непобедимая

Рис. 1.85. Красная армия всех сильней

Воздушные суда Красной Армии создаются как частицы с формой звезды. Силы противника в первом событии второго потока частиц (потока частиц противника) – это box-частицы, а во втором перешедшая в него частица удаляется, а на ее место помещается примитив Box таких же размеров, как и частица. Эффект уничтожения этого примитива обеспечивается ассоциированной с ним mesh-бомбой Bomb.
Заметим, что такая бомба генерирует осколки из Mesh объекта, имеющие регулярную форму (в примере осколок состоит из прямоугольников). Для получения осколков нерегулярной формы следует использовать бомбу PBomb, которая связывается не с объектом, а с массивом частиц PArray и берет в качестве осколков частицы этого массива.

global clr2 = [0, 0, 115], sFct2, arrB
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 $*
max tool zoomExtents
timeConfiguration.PlaybackLoop = false
usbNm = "E:\\"
fNmF = usbNm + "white.bmp"    -- 525 * 336
viewport.DispBkgImage = true
setAsBackground (openBitmap fNmF)
sliderTime = 0f
aRng = 800
animationRange = interval 0f aRng
sMt = standard diffuseColor:[255, 100, 0] showInViewport:true
meditMaterials[1] = sMt
sMt2 = standard diffuseColor:clr2 showInViewport:true
meditMaterials[2] = sMt2
st = star radius1:12 radius2:30 fillet1:0 fillet2:0 numPoints:5 distort:0 material:sMt
convertToPoly st
polyop.BevelFaces st #{1} 7 0
update st
hide st
bx = box length:20 width:20 height:10 material:sMt2 \
    lengthSegs:5 widthSegs:5 heightSegs:5
hide bx
mB = bomb strength:0.05 gravity:-0.2 minFragmentSize:5 \
        maxFragmentSize:20 spin:50 chaos:10 seed:(random 0 1000)
hide mb
y = 150
z = 30
x = 0
eSX = 10
eSY = 200
eStp = (0.9 * aRng) * 160
pF = PF_Source enable_Particles:true emitter_Type:0 \
    pos:[x, y, z] quantity_Viewport:100 \
    emitter_Length:eSX emitter_Width:eSY emitter_Height:0 \
    show_Logo:off show_Emitter:off
rotate pF -90 [1, 0, 0]
particleFlow.BeginEdit()
amt = 30
opBth = birth emit_Start:0 emit_Stop:eStp amount:amt
opPI = position_Icon Location:4
opDP = displayParticles type:6    -- 9 - Asterisks (6 - Geometry)
opSI = shape_Instance shape_Object:st
opMtS = material_Static assigned_Material:sMt
opSpd = speed speed:25 variation:5 direction:0 divergence:5
opRP = renderParticles()
sFct = 50
opSP = scaleParticles type:0 X_Scale_Factor:sFct \
        Y_Scale_Factor:sFct Z_Scale_Factor:sFct
apActFn pF #(opBth, opPI, opDP, opSI, opMtS, opSpd, opSP, opRP) true
y2 = -100
z2 = -30
x2 = 0
pF2 = PF_Source enable_Particles:true emitter_Type:0 \
    pos:[x2, y2, z2] quantity_Viewport:100 \
    emitter_Length:eSX emitter_Width:eSY emitter_Height:0 \
    show_Logo:off show_Emitter:off
rotate pF2 90 [1, 0, 0]
particleFlow.BeginEdit()
amt2 = 25
eStp2 = (0.7 * aRng) * 160
opBth2 = birth emit_Start:0 emit_Stop:eStp2 amount:amt
opPI2 = position_Icon Location:4
opDP2 = displayParticles type:6
opSI2 = shape_Instance shape_Object:bx
opMtS2 = material_Static assigned_Material:sMt2
opSpd2 = speed speed:25 variation:10 direction:0 divergence:5
opRP2 = renderParticles()
sFct2 = 75
opSP2 = scaleParticles type:0 X_Scale_Factor:sFct2 \
        Y_Scale_Factor:sFct2 Z_Scale_Factor:sFct2
tstAT2 = age_Test test_Type:1 condition_Type:1 test_Value:(eStp / 4) variation:(100 * 160)
apActFn pF2 #(opBth2, opPI2, opDP2, opSI2, opMtS2, opSpd2, opSP2, tstAT2, opRP2) true
--
-- Event 2
particleFlow.BeginEdit()
opDP2_2 = displayParticles type:6
opMtS2_2 = material_Static assigned_Material:sMt2
opSpd2_2 = speed speed:0 variation:0 direction:0 divergence:0
arrB = #()
opSO2_2 = script_Operator proceed_Script:"
    on channelsUsed pCont do (
        pCont.UseAge = true
        pCont.UsePosition = true
    )
    on proceed pCont do (
        nP = pCont.NumParticles()
        pCont.ParticleIndex = 1
        dT = pCont.ParticleTime
        p = pCont.ParticlePosition
        if pCont.DeleteParticle 1 then (
            if (findItem arrB p) == 0 do (
                append arrB p
                bx2 = copy bx
                bx2.Pos = p
                mb2 = copy mb
                hide mb2
                mb2.Pos = p
                mB2.Detonation = dT
                bindSpaceWarp bx2 mB2
            )
        )
    )"
evn2 = apActFn pF2 #(opDP2_2, opMtS2_2, opSpd2_2, opSO2_2) false
tstAT2.SetNextActionList evn2 tstAT2
--backgroundColor = [200, 100, 100]
--playAnimation()
--fNm = "c:/rdRm.avi"
--render framerange:(interval 0f aRng) outputwidth:160 outputheight:120 \
--            outputfile:fNm vfb:on

1.13.7. ИСТИНА


В примере отступающая мишура бытия открывает Троицу в окружении мерцающих звезд (рис. 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.13.8. ВРЕМЕНА ГОДА. ЛЕТО


В примере дым лесных и торфяных пожаров накрывает часть территории РФ (рис. 1.87).

        Непогода

Рис. 1.87. Времена года. Лето

Для получения результата необходим следующий rss.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
)
fn yrTmsSmmr h = (
    viewport.ActiveViewport = 4
    aRng = 750
    animationRange = interval 0f aRng
    sliderTime = 0f
    delete $*
    aZ = 90
    wd = 200
    hg = 100
    x = 40
    y = 20
    yF = -0.1 * y
    z = 0
    k = -0.1
    omn = omnilight rgb:white multiplier:1 contrast:0 softenDiffuseEdge:0 \
        useNearAtten:off useFarAtten:off ambientOnly:on atmosShadows:off \
        decayRadius:0 pos:[-140, y, z]
    qtPOF = (eulerAngles 0 180 aZ) as quat
    qtPln = (eulerAngles 0 90 aZ) as quat
    qtW = eulerToQuat (eulerAngles 0 -25 0)
    qtF = eulerToQuat (eulerAngles 0 180 (aZ - 90))
    psPOF = [k * x, yF, 0.2 * hg]
    psPln = [x, y, z]
    psF = [k * x, yF, -0.75 * hg]
    pOF = POmniFlect rotation:qtPOF pos:psPOF width:wd height:hg timeOff:aRng
    fNm = "G:/rss.jpg"
    dMp = bitmapTexture fileName:fNm
    sMt = standard diffuseMap:dMp showInViewport:true diffuseMapEnable:true
    pln = plane length:hg width:wd rotation:qtPln pos:psPln material:sMt
    nS = 200
    while numAtmospherics > 0 do deleteAtmospheric 1
    while numEffects > 0 do deleteEffect 1
    spG = sphereGizmo radius:5 pos:psF
    scale spG [1, 1, 2]
    nFrc = 5
    nsM = noiseModifier fractal:true iterations:5 \
        strength:[nFrc, nFrc, nFrc] animate:true frequency:0.3 phase:15f
    addModifier spG nsM
    fE = fire_Effect inner_Color:(color 255 50 50) outer_Color:(color 75 75 75)
    appendGizmo fE spG
    addAtmospheric fE
    for k = 1 to nS do maxOps.CloneNodes spG
    global arrSpG = $SphereGizmo*    -- Array of SphereGizmo objects
    wnd = wind strength:0.002 turbulence:0.0 decay:0 \
        frequency:15 rotation:qtW pos:psF
    pF = PF_Source enable_Particles:true emitter_Type:0 \
        emitter_Length:wd emitter_Width:5 quantity_Viewport:100 \
        rotation:qtF pos:psF
    particleFlow.BeginEdit()
    opBth = birth emit_Start:0 emit_Stop:(aRng * 160) amount:nS
    opPI = position_Icon distinct_Points_Only:on total_Distinct_Points:4
    opSpd = speed speed:10 divergence:40
    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.075 * pAg + 5
            arrSpG[k].Transform = pCont.ParticleTM
            arrSpG[k].Radius = rSpG
        )
    )"
    tstCS = collision collision_Nodes:#(pOF) test_Type:0 speed_Option:2
    evn = apActFn pF #(opBth, opPI, opFc, opSpd, opSO, tstCS, opRP) true
    --max tool zoomExtents
    backgroundColor = white
    renderWidth = 160; renderHeight = 120
    sceneExposureControl.ExposureControl = automatic_Exposure_Control active:on
    aec = sceneExposureControl.exposurecontrol
    aec.ProcessBG = on
    aec.ColorDifferentiation = on
    aec.Brightness = 60
    aec.Contrast = 60
    aec.PhysicalScale = 1000
    aec.ChromaticAdaptation = on
    aec.WhiteColor = color 249 253 198
    fNm = "c:/rss.avi"
    render framerange:(interval 0f (1.0 * aRng)) outputwidth:640 outputheight:480 \
            outputfile:fNm vfb:on
--    playAnimation()
    -- To restore the scene
    --arrSpG.Pos = psF; arrSpG.Radius = 5
)
yrTmsSmmr 1

1.13.9. ПЕРО

В примере посредством пера создается простая надпись. Чернила остаются на бумаге при соприкосновении с ней пера (рис. 1.88).

        Перо

Рис. 1.88. Перо

fn fPn h = (
    delete $*
    sliderTime = 0f
    msg = "Speed By Icon with a pen"
    rt = quat 0.55 -0.2 -0.3 0.75
    frmCnt = 300
    kFrm = frmCnt / 100
    frmCntPt = frmCnt / 6
    tPs = 30
    pn2 = 210
    animationRange = interval 0f frmCnt
    sDst = 3
    lttrW = 40
    lttrH = 40
    psE = [-lttrW / 2, 0, 0]
    psE2 = [lttrW / 2, 0, 0]
    psE3 = [lttrW + lttrW / 2, 0, 0]
    psCn = [-lttrW, lttrH, 0]
    pth = line pos:[0, 0, 0] wireColor:black
    addNewSpline pth
    addKnot pth 1 #corner #line [-lttrW - lttrW / 2, 0, 0]
    addKnot pth 1 #corner #line psCn
    addKnot pth 1 #corner #line psE
    addKnot pth 1 #corner #line psE2
    addKnot pth 1 #corner #line [lttrW, -lttrH, 0]
    addKnot pth 1 #corner #line [lttrW + lttrW, lttrH, 0]
    updateShape pth
    pth2 = copy pth
    pth3 = copy pth
    pc = path_Constraint follow:on axis:1 path:pth
    pc2 = path_Constraint follow:on axis:1 path:pth2 percent:34
    pc3 = path_Constraint follow:on axis:1 path:pth3 percent:49
    --
    pF = PF_Source emitter_Type:2 quantity_Viewport:100 \
        emitter_Length:sDst rotation:(eulerToQuat (eulerAngles 90 0 0)) \
        show_Logo:off show_Emitter:off
    particleFlow.BeginEdit()
    opBth = birth emit_Start:0 emit_Stop:(160 * frmCnt) amount:1000
    opPI = position_Icon Location:4 Lock_On_Emitter:off \
        inherit_Emitter_Movement:off
    opDP = displayParticles type:1 color:red
    opSBI = speedByIcon use_Icon_Orientation:off \
        steer_Towards_Trajectory:on distance:sDst
    opSBI.Pos.Controller = pc
    cntrl = opSBI.Pos.Controller.Percent.Controller
    addNewKey cntrl frmCntPt
    addNewKey cntrl (frmCntPt + tPs)
    cntrl.Keys[3].Value = cntrl.Keys[2].Value
    addNewKey cntrl pn2
    addNewKey cntrl (pn2 + tPs)
    cntrl.Keys[5].Value = cntrl.Keys[4].Value
    pF.Pos = opSBI.Pos
    tstFT = find_Target speed_Type:2 test_Type:0 test_Distance:sDst pos:psE
    evn = apActFn pF #(opBth, opPI, opDP, opSBI, tstFT) false
    --
    -- Event 2
    particleFlow.BeginEdit()
    opSBI2 = speedByIcon steer_Towards_Trajectory:on distance:(sDst + 1)
    opSBI2.Pos.Controller = pc2
    cntrl2 = opSBI2.Pos.Controller.Percent.Controller
    addNewKey cntrl2 frmCntPt
    addNewKey cntrl2 (frmCntPt + tPs)
    cntrl2.Keys[3].Value = cntrl2.Keys[2].Value
    addNewKey cntrl2 pn2
    addNewKey cntrl2 (pn2 + tPs)
    cntrl2.Keys[5].Value = cntrl2.Keys[4].Value
    tstFT2 = find_Target speed_Type:2 test_Type:0 \
        test_Distance:(sDst + 1) pos:(psE2 - [sDst + 1, 0, 0])    -- (psE2 + [sDst, 0, 0])
    evn2 = apActFn pF #(opSBI2, tstFT2) false
    tstFT.SetNextActionList evn2 tstFT
    --
    -- Event 3
    particleFlow.BeginEdit()
    opSBI3 = speedByIcon steer_Towards_Trajectory:on distance:(sDst + 2)
    opSBI3.Pos.Controller = pc3
    cntrl3 = opSBI3.Pos.Controller.Percent.Controller
    addNewKey cntrl3 frmCntPt
    addNewKey cntrl3 (frmCntPt + tPs)
    cntrl3.Keys[3].Value = cntrl3.Keys[2].Value
    addNewKey cntrl3 pn2
    addNewKey cntrl3 (pn2 + tPs)
    cntrl3.Keys[5].Value = cntrl3.Keys[4].Value
    opDP3 = displayParticles type:1 color:blue
    tstFT3 = find_Target speed_Type:2 test_Type:0 test_Distance:(sDst + 3) pos:psE3
    evn3 = apActFn pF #(opSBI3, opDP3, tstFT3) false
    tstFT2.SetNextActionList evn3 tstFT2
    --
    -- Event 4
    particleFlow.BeginEdit()
    opDlt4 = deleteParticles type:0
    evn4 = apActFn pF #(opDlt4) false
    tstFT3.SetNextActionList evn4 tstFT3
    --
    pcCn = copy pc
    cnH = 40
    cn = cone radius1:2 radius2:1 height:cnH wireColor:green
    cn.Pivot = [0, 0, cnH - 1]
    rotate cn (eulerToQuat (eulerAngles 180 0 0))
    cn.Pos.Controller = position_List()
    cn.Pos.Controller.Available.Controller = pcCn
    zP = cn.Pos.Controller[1].Z_Position
    animate on (
        at time 0f (
            cn.Pos.Controller.Weight[1] = 0
            cn.Pos.Controller.Weight[2] = 100
        )
        at time frmCntPt (
            cn.Pos.Controller.Weight[1] = 0
            cn.Pos.Controller.Weight[2] = 100
        )
        at time (frmCntPt + tPs / 2) (
            cn.Pos.Controller.Weight[1] = 100
            cn.Pos.Controller.Weight[2] = 0
        )
        at time (frmCntPt + tPs) (
            cn.Pos.Controller.Weight[1] = 0
            cn.Pos.Controller.Weight[2] = 100
        )
        at time 125f (
            cn.Pos.Controller.Weight[1] = 0
            cn.Pos.Controller.Weight[2] = 100
        )
        at time 145f (
            cn.Pos.Controller.Weight[1] = 100
            cn.Pos.Controller.Weight[2] = 0
        )
        at time 165f (
            cn.Pos.Controller.Weight[1] = 0
            cn.Pos.Controller.Weight[2] = 100
        )
        at time pn2 (
            cn.Pos.Controller.Weight[1] = 0
            cn.Pos.Controller.Weight[2] = 100
        )
        at time (pn2 + tPs / 2) (
            cn.Pos.Controller.Weight[1] = 100
            cn.Pos.Controller.Weight[2] = 0
        )
        at time (pn2 + tPs) (
            cn.Pos.Controller.Weight[1] = 0
            cn.Pos.Controller.Weight[2] = 100
        )
        at time (frmCnt - tPs /2) (
            cn.Pos.Controller.Weight[1] = 0
            cn.Pos.Controller.Weight[2] = 100
        )
        at time (frmCnt - tPs /2 + 1) (
            cn.Pos.Controller.Weight[1] = 100
            cn.Pos.Controller.Weight[2] = 0
    )
        at time frmCnt (
            cn.Pos.Controller.Weight[1] = 100
            cn.Pos.Controller.Weight[2] = 0
        )
    )
    max tool zoomExtents
    text text:msg size:24 wireColor:black rotation:rt pos:[5, 0, -90]
    hide #(opSBI, opSBI2, opSBI3, tstFT, tstFT2, tstFT3, pth, pth2, pth3)
    playAnimation()
)

1.13.10. НАЖДАК

В примере иллюстрируется процесс шлифовки изделия на абразивном круге (рис. 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()
)

1.13.11. И ПИР ВЕСЕЛЫЙ ИМ НЕ В ПИР

В примере создаются системы частиц для двух следующих целей: одна совместно с объектом 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]

Итоговая сцена показана на рис. 1.90.

        И пир веселый им не в пир

Рис. 1.90. И пир веселый им не в пир

1.13.12. РЫБА

Рыба, работая хвостом, удаляется от начальной точки вдоль отрицательного направления оси Х, а затем возвращается в начальную точку (и так 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()

        Рыба

Рис. 1.91. Рыба

2. ИНЫЕ СИСТЕМЫ ЧАСТИЦ

2.1. МАССИВ ЧАСТИЦ PARRAY

2.1.1. PARRAY И PBOMB. РАЗРУШЕННЫЙ ЧАЙНИК

В примере создается массив частиц, использующий в качестве эмиттера стандартный примитив чайник 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) придает частицам дополнительный импульс, направление которого определяется положением бомбы и ее параметрами.

delete $*
clr2 = [135, 110, 8]
pA = -50
tp = teapot radius:10 smooth:on segs:6 wireColor:red \
    body:on handle:on spout:on lid:on mapCoords:off pos:[pA, 0.2 * pA, 0]
a = PArray name:"aNm" speed:0.05 emitter:tp emitter_Start:0f wireColor:clr2 \
    fragment_Thickness:1 fragChunkMinimum:200 life:100f pos:[pA, 0.2 * pA, 0]
a.Speed_Variation = 50            -- %
a.Spin_Time = 5f
a.Spin_Time_Variation = 50        -- %
a.ParticleType = 2                    -- Particle Type: Object fragments
a.ViewType = 2                    -- Viewport Display:Mesh
a.FragmentMethod = 1            -- Object Fragment Controls: Number of Chunks
a.IconHidden = true
pb = pBomb pos:[-20 + pA, 0.2 * pA, 0] strength:0.05 \
    chaos:80 icon_Size:10 start_Time:10f Lasts_For:1f
bindSpaceWarp a pb
hide #(tp, pb)
playAnimation()

На рис. 2.1 приведены два кадра анимации: первый в момент до взрыва бомбы и последний кадр.

        PArray и PBomb

Рис. 2.1. Разрушенный чайник

2.1.2. PARRAY И MESHER. ЧАСТИЦА С НАИБОЛЬШИМ ЧИСЛОМ ГРАНЕЙ

В примере чайник используется в качестве эмиттера массива частиц PArray. Решаемая задача – это демонстрация фрагмента, имеющего наибольшее число граней.
Порядок решения следующий:
  1. Создать, используя PArray, неподвижные частицы из фрагментов чайника.
  2. Создать, используя конструктор Mesher, объект класса Mesher.
  3. Создать Editable_Mesh-объект (totalMesh) и определить его Mesh, как Mesh объекта Mesher. Число элементов в полученном объекте будет равно числу частиц (фрагментов чайника), созданных на этапе 1.
  4. Последовательно удаляя элементы Editable_Mesh-объекта, запомнить в переменной nFMax максимальное число граней.
  5. Восстановить объект totalMesh и найти первый элемент с числом граней, равным nFMax. Результат сохранить в переменной fragmentMesh.
  6. Отобразить найденный элемент.
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.

        PArray и Mesher

Рис. 2.2. Элемент totalMesh с наибольшим числом граней

2.1.3. ВЗРЫВ ЧЕТЫРЕХ ОБЪЕКТОВ СРЕДСТВАМИ PARRAY И PBOMB

В примере на основе чайников в 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()

Два кадра анимации приведены на рис. 2.3.

        PArray и PBomb

Рис. 2.3. Сцена после первого и третьего взрывов

2.1.4. ПОТОК С ОДНОЙ ЧАСТИЦЕЙ

В примере отображается только первая частица потока 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-частиц.

        metaParticles-частицы

Рис. 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)
            )
        )

2.1.5. РАЗБИВАЕМ И СОБИРАЕМ ЧАЙНИК

2.1.5.1. ПРИЕМ ОБРАТНОГО ВОСПРОИЗВЕДЕНИЯ
В примере чайник используется в качестве эмиттера массива частиц PArray. Созданные частицы облают нулевой скоростью. Падение частиц, составляющих чайник, обеспечивается введенной в сцену силой гравитации Gravity. В момент столкновения с отражателем частицы подвергаются воздействию бомбы и разлетаются по плоскости (рис. 2.7), совмещенной с отражателем.

        Осколки

Рис. 2.7. Осколки чайника на плоскости отражателя

Сборка чайника из осколков наблюдается в видовом порте благодаря введенному в конец кода циклу

for k = 1 to 100 do sliderTime -= 1

возвращающему ползунок временной шкалы из ее конца в ее начало.

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

animationRange = interval 0f 100f
timeConfiguration.PlaybackLoop = false
sliderTime = 0f
viewport.ActiveViewport = 4
delete $*
pA = 20
tp = teapot radius:20 smooth:on segs:6 wireColor:[70, 150, 90] \
    body:on handle:on spout:on lid:on mapCoords:off pos:[0, 0, pA]
a = PArray name:"aNm" speed:0 emitter:tp wireColor:[70, 150, 90] \
    emitter_Start:0f emitter_Stop:0f \
    fragment_Thickness:2 fragChunkMinimum:200 \
    display_Until:101f life:101f pos:[0, 0, pA] iconHidden:on
a.Speed_Variation = 0
a.Spin_Time = 0f
--a.Spin_Time_Variation = 50
a.ParticleType = 2                    -- Particle Type: Object fragments
a.ViewType = 2                    -- Viewport Display:Mesh
a.FragmentMethod = 1            -- Object Fragment Controls: Number of Chunks
pb = pBomb pos:[pA, 0.2 * pA, 0] strength:-0.05 \
    chaos:80 icon_Size:10 start_Time:20f lasts_For:1 symmetry:1
bindSpaceWarp a pb
pS = [20, 20, 0]
pOF = POmniFlect timeOn:0f timeOff:100f affects:100 bounce:0 \
    chaos:100 friction:0 width:150 height:150 pos:pS
pln = plane length:150 width:170 pos:pS wireColor:[166, 229, 229]
bindSpaceWarp a pOF
grv = gravity pos:[100, 20, 0] iconSize:5 strength:0.05
rotate grv 180 [1, 0, 0]
bindSpaceWarp a grv
hide #(tp, pb, pOF)
playAnimation()
for k = 1 to 100 do sliderTime -= 1

Аналогичный результат можно получить при совместном употреблении PArray и PF_Source. Последовательность действий следующая:
  1. Создать чайник, PArray и Mesher; указать чайник в качестве эмиттера PArray и получить Mesh из сгенерированных PArray частиц, употребив Mesher.
  2. Создать отражатель и совместить с ним плоскость.
  3. Создать PF_Source, необходимые операторы и тест столкновения. В proceed-обработчике Script_Operator выполнить totalMesh.Mesh = mesher.Mesh и создать PF_Source-частицы – по одной частице на элемент totalMesh (этот элемент является частицей чайника, полученного PArray и Mesher). Определить надлежащим образом свойства новой частицы (см. код).
  4. Воспроизвести анимацию (см. рис. 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
2.1.5.2. РЕВЕРС КЛЮЧЕЙ АНИМАЦИИ
В примере создается чайник. Он падает на пол и разбивается (рис. 2.8).

        Реверс ключей анимации

Рис. 2.8. Чайник, упавший на пол

Затем полученные фрагменты возвращаются на исходные позиции, и чайник приобретает первоначальную форму (рис. 2.9).

        Чайник склеен из фрагментов

Рис. 2.9. Склеенный чайник

Если бы решалась задача разрушения чайника без последующей сборки, то подошел бы следующий код:

delete $*
pA = 20
tBm = 20
tp = teapot radius:20 smooth:on segs:6 body:on handle:on spout:on lid:on mapCoords:off
hide tp
pArr = PArray speed:0 emitter:tp emitter_Start:0f emitter_Stop:0f \
    fragment_Thickness:2 fragChunkMinimum:75 wireColor:[135, 110, 8] \
    display_Until:101f life:101f pos:[0, 0, pA] iconHidden:on \
    speed_Variation:0 spin_Time:0f seed:474
pArr.ParticleType = 2            -- Particle Type: Object fragments
pArr.ViewType = 2            -- Viewport Display:Mesh
pArr.FragmentMethod = 1        -- Object Fragment Controls: Number of Chunks
pb = pBomb pos:[pA, 0.2 * pA, 0] strength:-0.05 \
    chaos:80 icon_Size:10 start_Time:tBm lasts_For:1 symmetry:1
pOF = POmniFlect timeOn:0f timeOff:100f affects:100 bounce:0 \
    chaos:100 friction:0 width:150 height:150 pos:[20, 20, 0]
grv = gravity pos:[100, 20, 0] iconSize:5 strength:-0.05
bindSpaceWarp pArr pb
bindSpaceWarp pArr pOF
bindSpaceWarp pArr grv
animationRange = interval 0f 100f
playAnimation()

Код вводит в сцену чайник 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.

        Time Configuration

Рис. 2.10. Часть диалога Time Configuration

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

fn prps = (
    delete $*
    gc()
    viewport.SetLayout #layout_4
    viewport.ActiveViewport = 4
    max vpt persp user
    viewport.SetGridVisibility 4 false
    backgroundColor = white
    timeConfiguration.PlaybackLoop = false
)
fn mkMsh pArr pS sldTm sMt = (
    sliderTime = sldTm
    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
        frM.Material = sMt
        append arrFrMsh frM
    )
    delete #(mshr, totalMesh)
    return arrFrMsh
)
fn oneK frPC k k2 arrFrMsh tm pD = (
        local frM
        frM = arrFrMsh[k]
        addNewKey frPC tm
        ksX = frPC.X_Position.Controller.Keys[k2]
        ksY = frPC.Y_Position.Controller.Keys[k2]
        ksZ = frPC.Z_Position.Controller.Keys[k2]
        ksX.Value = frM.Pos[1] + pD[1]
        ksY.Value = frM.Pos[2] + pD[2]
        ksZ.Value = frM.Pos[3] + pD[3]
        ksX.InTangentType = ksY.InTangentType = ksZ.InTangentType = #linear
        ksX.OutTangentType = ksY.OutTangentType = ksZ.OutTangentType = #linear
        if k2 > 2 do delete frM
)
fn f0 h = (
    local mCnt, arrFrMsh = #()
    prps()
    aRng = 200
    with redraw off (
    pA = 20
    pT = [0, 0, pA]
    pS = [20, 20, 0]
    bH = 10
    pS2 = pS - [0, 0, pA + bH]
    tBm = 20
    sMt = standard diffuse:[135, 110, 8] showInViewport:true
    tp = teapot radius:20 smooth:on segs:6 \
        body:on handle:on spout:on lid:on mapCoords:off pos:pT
    pArr = PArray speed:0 emitter:tp emitter_Start:0f emitter_Stop:0f \
        fragment_Thickness:2 fragChunkMinimum:75 \
        display_Until:101f life:101f pos:pT iconHidden:on \
        speed_Variation:0 spin_Time:0f seed:474
    pArr.ParticleType = 2        -- Particle Type: Object fragments
    pArr.ViewType = 2            -- Viewport Display:Mesh
    pArr.FragmentMethod = 1    -- Object Fragment Controls: Number of Chunks
    bx = box length:135 width:160 height:bH wireColor:black pos:pS2
    pb = pBomb pos:[pA, 0.2 * pA, 0] strength:-0.05 \
        chaos:80 icon_Size:10 start_Time:tBm lasts_For:1 symmetry:1
    pOF = POmniFlect timeOn:0f timeOff:100f affects:100 bounce:0 \
        chaos:100 friction:0 width:150 height:150 pos:pS
    grv = gravity pos:[100, 20, 0] iconSize:5 strength:-0.05
    bindSpaceWarp pArr pb
    bindSpaceWarp pArr pOF
    bindSpaceWarp pArr grv
    arrFrMsh = mkMsh pArr pS 0 sMt
    arrFrMsh2 = mkMsh pArr pT 30 sMt
    arrFrMsh3 = mkMsh pArr pT 100 sMt
    mCnt = arrFrMsh.Count
    for k = 1 to mCnt do (
        frM = arrFrMsh[k]
        frPC = frM.Pos.Controller
        oneK frPC k 1 arrFrMsh 0 [0, 0, 0]
        oneK frPC k 2 arrFrMsh tBm [0, 0, -pA]
        oneK frPC k 3 arrFrMsh2 30 [0, 0, 0]
        oneK frPC k 4 arrFrMsh3 aRng [0, 0, 0]
    )
    hide #(tp, pb, pOF, pArr, grv)
    sliderTime = 0f
    )
    animationRange = interval 0f aRng
    playAnimation()
    for m = 1 to mCnt do (
        frM = arrFrMsh[m]
        frPC = frM.Pos.Controller
        supportsTimeOperations frPC
        reverseTime frPC animationRange #incLeft #incRight
    )
--    nK = 4
--     arrT = #(0, aRng - 30, aRng - tBm, aRng)
--     for m = 1 to mCnt do (
--         frM = arrFrMsh[m]
--         frPC = frM.Pos.Controller
--         arrV = #()
--         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]
--         )
--         for k = 1 to nK do (
--             k2 = nK - k + 1
--             if (k != k2) do (
--                 v = arrV[k2]
--                 frPC.X_Position.Controller.Keys[k].Time = arrT[k]
--                 frPC.Y_Position.Controller.Keys[k].Time = arrT[k]
--                 frPC.Z_Position.Controller.Keys[k].Time = arrT[k]
--                 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]
--             )
--         )
--     )
    playAnimation()
)
f0 1

2.1.6. МАТЕРИАЛЫ САЙТА 100BYTE.RU

2.1.6.1. ЭНТРОПИЯ

В примере из хаоса создается порядок (рис. 2.11).

        Энтропия с PArray и Bomb

Рис. 2.11. Хаос и порядок

Хаос создается из фрагментов сферы и конуса с использованием двух 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
2.1.6.2. ПУСТЬ СИЛЬНЕЕ ГРЯНЕТ КРИЗИС

В примере 100-долларовая банкнота разрывается на частицы (рис. 2.12); полученные частицы сжимаются в область исчезающего размера.

        PArray

Рис. 2.12. Начальная и конечная стадии разбиения купюры

Результат достигается после выполнения следующей последовательности действий:
  1. В сцену вводятся плоскость, массив частиц, а также объект Mesher и два объекта Editable_Mesh.
  2. Плоскость снабжается стандартным материалом, диффузионная карта которого построена на основе файла 100d.gif, содержащего образ 100-долларовой банкноты.
  3. Частицы создаются из фрагментов плоскости, а объект Mesher содержит полигональные модели (Mesh) этих частиц.
  4. Mesh объектов Editable_Mesh определяется как Mesh объекта Mesher.
  5. Формируется массив arrNF граней полученных Editable_Mesh объектов.
  6. На основе одного Editable_Mesh-объекта формируется массив arrMsh, каждый элемент которого содержит список граней соответствующего элемента анализируемого Editable_Mesh-объекта. Кроме того, создается и массив arrCntr, содержащий координаты центров элементов Editable_Mesh. (Напомним, что метод GetElementsUsingFace возвращает битовый массив граней, находимых в примере по параметру #{arrNF[1]}.) Элемент, грани которого включены в массив arrMsh, а координаты центра – в arrCntr, удаляется из анализируемой Editable_Mesh. Также грани, включенные в arrMsh, удаляются из массива arrNF.
  7. Используя элементы полученного массива 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.1.6.3. ОГОНЬ
В примере огонь (рис. 2.13) создается средствами массива частиц PArray и эффекта Lens Effects Glow, вводимого в сцену как событие Image Filter Event.

        PArray и Lens Effects Glow

Рис. 2.13. Огонь с PArray и Lens Effects Glow

Движение частиц обеспечивается ветром. Кроме того, для PArray задаются такие свойства, как Material, GbufferChannel, Motionblur, ImageMotionBlurMultiplier и MotionBlurOn. Указанные свойства (кроме Material) можно задать в окне свойств объекта, открываемом при выбранном объекте после нажатия на правую кнопку мыши выбора Object Properties.
После выполнения следующего кода

fn prps h = (
    delete $*
    sliderTime = 0f
    if h == 4 then
        max vpt persp user
    else (
        viewport.ActiveViewport = 2
        viewport.SetType #view_front
    )
    viewport.SetGridVisibility 4 false
    animationRange = interval 0f 100f
    max tool zoomExtents
)
function f3 h = (
    prps 4
    grdMp = gradient color1:(color 255 0 0) color2:(color 255 180 0) \
        color3:(color 255 180 180) color2Pos:0.08
    stn = standard shaderType:1 diffuseMap: grdMp opacity:90 \
        selfIllumAmount:55 opacityFallOffType:1
    meditMaterials[1] = stn
    sp =sphere radius:15
    pArr = PArray formation:0 numDistinctPoints:20 speed:0 speed_Variation:0 \
        divergence_Angle:10 birth_Rate:80 total_Number:100 \
        quantityMethod:0 viewPercent:100 \
        emitter_Start:0f emitter_Stop:100f display_Until:100f \
        life:20f life_Variation:0f subsampleEmitterTranslation:on \
        subsampleCreationTime:on subsampleEmitterRotation:off \
        size:4 size_Variation:0 growth_Time:20f fade_Time:50f \
        standardParticle:4 spin_Time:0f \
        spin_Time_Variation:0 spin_Phase:0 spin_Phase_Variation:0 \
        motionInfluence:100 motionMultiplier:1 \
        motionVariation:0 bubble_Amplitude:0 bubble_Amplitude_Variation:0 \
        bubble_Period:100000f bubble_Period_Variation:0 bubble_Phase:0 \
        bubble_Phase_Variation:0 iconSize:15 iconHidden:off fragEdgeMatID:2 \
        spawnType:0 fragBackMatID:3 interparticle_Collisions_On:0 \
        use_Selected_Subobjects:0 pos:[0, -30, 0]
    pArr.Emitter = sp
    pArr.Material = stn
    pArr.GbufferChannel = 1
    pArr.Motionblur = #image
    pArr.ImageMotionBlurMultiplier = 6
    pArr.MotionBlurOn = on
    wnd = wind pos:[0, 0, -40] strength:0.5 decay:0 \
        turbulence:2.2 frequency:1.3 scale:3
    bindSpaceWarp pArr wnd
    hide sp
    max videoPost
--     videoPostTracks.LG.Size = 5
--     videoPostTracks.LG.Intensity = 50
)

в окне Video Post создаются 3 события (рис. 2.14).

        Video Post

Рис. 2.14. События Video Post

Файл для результата задается как событие Image Output Event.
Свойства Size и Intensity события Lens Effects Glow определяются следующим кодом:

videoPostTracks.LG.Size = 5
videoPostTracks.LG.Intensity = 50

Для Perspective (событие Scene Event) устанавливается флажок Scene Motion Blur, а в качестве программы воспроизведения выбирается Default Scanline Renderer.
Сцена воспроизводится после нажатия в Video Post на кнопку Execute Execute.
Наилучший эффект достигается при работе с черным фоном.

2.2. БОЛЬШОЙ СПРЕЙ SUPERSPRAY

2.2.1. Хоровод. SuperSpray, BLob и Scatter Skip 2

В примере посредством конструктора BlobMesh (капля) создается Mesh из частиц большого спрея (BlobMeshOps.AddBlob ssp). Далее эта Mesh средствами объекта Scatter тиражируется (Duplicates = 10) и распределяется по сфере (рис. 2.15).

        SuperSpray, BLob и Scatter Skip 2

Рис. 2.15. Хоровод

delete $*
animationRange = interval 0f 100f
viewport.ActiveViewport = 4
sph = sphere radius:15 segs:32 wireColor:[6, 135, 113]
ssp = superSpray speed:10 viewType:2 viewPercent:100 \
    standardParticle:7 off_Axis:4 axis_Spread:12 birth_Rate:3 \
    emitter_Stop:100f display_Until:100f life:100f size:4 \
    growth_Time:30f fade_Time:0f speed:1.5 plane_Spread:180 \
    bubble_Amplitude:1.5 bubble_Amplitude_Variation:0 \
    bubble_Period:8f bubble_Period_Variation:40 \
    bubble_Phase:180 bubble_Phase_Variation:100 \
    spin_Time:60f spin_Time_Variation:33 \
    spin_Phase:180 spin_Phase_Variation:100
ssp.Rotation = (eulerAngles 0 -90 0) as quat
ssp.Pos = [-40, 0, 0]
bM = blobMesh pos:[0, -50, 0] wireColor:[135, 110, 8]
bM.BlobMeshOps.AddBlob ssp
select bM
max create mode

После выполнения приведенного кода создаются сфера sph, большой спрей ssp и составной объект BlobMesh (капля) bM; выбран объект bM. Далее интерактивно создается составной объект распределитель Scatter (рис. 2.16, а).

        Составной объект Scatter

Рис. 2.16. Scatter: а - ввод в сцену распределителя;
б - выбраны источник и цель распределителя; в - параметры цели

Его первым операндом будет bM. В качестве второго операнда после нажатия на кнопку Pick Distribution Object одноименной секции выбирается сфера sph и переключатель Distribution устанавливается в положение Use Distribution Object (рис. 2.16, б).
Затем переключатель Distribute Using устанавливается в Skip N 2 (рис. 2.16, в).
Прочие свойства Scatter не изменяются (берутся значения, заданные После чего выполняется следующий код:

max modify mode
bM.Duplicates = 10
bM.Base_Scale = 75
hide #(sph, ssp)
playAnimation()

2.2.2. ЛУЧИ. SUPERSPRAY, MESHER И SCATTER AREA

В примере посредством конструктора Mesher (создатель Mesh) создается Mesh из частиц большого спрея (mesher pick:ssp). Далее эта Mesh средствами объекта Scatter тиражируется (Duplicates = 9) и распределяется по поверхности сферы (рис. 2.17).

        SuperSpray, Mesher и Scatter Area

Рис. 2.17. Лучи

delete $*
animationRange = interval 0f 100f
viewport.ActiveViewport = 4
sph = sphere radius:15 segs:32
ssp = superSpray viewType:2 viewPercent:100 \
    standardParticle:7 off_Axis:4 axis_Spread:5 birth_Rate:3 \
    emitter_Stop:100f display_Until:100f life:100f size:4 \
    growth_Time:30f fade_Time:0f speed:1.0 off_Plane:0 plane_Spread:0 \
    bubble_Amplitude:1.5 bubble_Amplitude_Variation:0 \
    bubble_Period:8f bubble_Period_Variation:40 \
    bubble_Phase:180 bubble_Phase_Variation:100 \
    spin_Time:60f spin_Time_Variation:33 \
    spin_Phase:180 spin_Phase_Variation:100
ssp.Rotation = (eulerAngles 0 -90 0) as quat
ssp.Pos = [-40, 0, 0]
msh = mesher pick:ssp wireColor:[135, 110, 8] pos:[0, -50, 0]
select msh
max create mode

После выполнения приведенного кода создаются сфера sph, большой спрей ssp и составной объект Mesher msh; выбран объект msh. Далее интерактивно создается составной объект распределитель Scatter с переключателем Distribute Using, установленным в положение Area. После чего выполняется следующий код:

max modify mode
msh.Duplicates = 9
msh.Base_Scale = 75
hide #(sph, ssp)
playAnimation()

2.2.3. ЛУЖОК

В примере посредством большого спрея пяти Mesher имитируется рост похожего на кактус растения (рис. 2.18).

         SuperSpray и Mesher

Рис. 2.18. Лужок

В конечной стадии анимации взрослый кактус делает посредством модификатора Bend несколько колебательных движений.

delete $*
animationRange = interval 0f 100f
viewport.ActiveViewport = 4
ssp = superSpray viewType:2 viewPercent:100 \
    standardParticle:7 off_Axis:4 axis_Spread:5 birth_Rate:1 \
    emitter_Start:-20f emitter_Stop:90f display_Until:100f life:100f size:4 \
    growth_Time:30f fade_Time:0f speed:0.5 plane_Spread:90 \
    bubble_Amplitude:3.0 bubble_Amplitude_Variation:0 \
    bubble_Period:8f bubble_Period_Variation:40 \
    bubble_Phase:180 bubble_Phase_Variation:100 \
    spin_Time:60f spin_Time_Variation:33 \
    spin_Phase:180 spin_Phase_Variation:100
hide ssp
pSL = 80; pSW = 130; pSL2 = pSL / 2; pSW2 = pSW / 2
pZ = -10
pln = plane length:(pSL + 5) width:(pSW + 5) wireColor:[8, 110, 135] pos:[0, 0, pZ]
bnd = #(1, 2, 3, 4, 5)
for k = 1 to 5 do (
    msh = mesher pos:[0, 0, pZ] pick:ssp wireColor:[177, 88, 27]
    bnd[k] = bend()
    addModifier msh bnd[k]
    case k of (
        1 : msh.Pos = [-pSW2, -pSL2, pZ]
        2 : msh.Pos = [ pSW2, -pSL2, pZ]    
        3 : msh.Pos = [pSW2, pSL2, pZ]
        4 : msh.Pos = [-pSW2, pSL2, pZ]
    )
)
animate on (
    at time 0 bnd.Angle = 0
    at time 69 bnd.Angle = 0
    at time 70 bnd.Angle = 45
    at time 85 bnd.Angle = -45
    at time 100 bnd.Angle = 0
)
playAnimation()

2.2.4. НЛО

В примере из частиц четырех копий большого спрея и Mesher создаются летающие объекты. В качестве пути их перемещения берется эллипс (рис. 2.19).

        SuperSpray и Mesher

Рис. 2.19. НЛО

delete $*
viewport.ActiveViewport = 4
max tool zoomExtents
animationRange = interval 0f 100f
ssp = superSpray viewType:2 viewPercent:100 \
    standardParticle:5 off_Axis:0 quantityMethod:1 \
    total_Number:20 \
    emitter_Start:-100f emitter_Stop:100f display_Until:200f life:200f size:10 \
    growth_Time:0f fade_Time:0f speed:0.25 \
    bubble_Amplitude:10.0 bubble_Amplitude_Variation:0 \
    bubble_Period:30 bubble_Period_Variation:0 \
    bubble_Phase:30 bubble_Phase_Variation:0 spin_Time:30
msh = mesher pos:[0, 0, 0] pick:ssp wireColor:[177, 88, 27] \
    rotation:(eulerToQuat (eulerAngles 0 -90 0))
msh2 = copy msh; msh2.WireColor = [27, 88, 177]
msh3 = copy msh; msh3.WireColor = [88, 27, 177]
msh4 = copy msh; msh4.WireColor = [177, 27, 88]
ellps = ellipse length:180 width:120 pos:[0, 20, 0] wireColor:black
pc = path_Constraint follow:on axis:0
pc.Path = ellps
pc2 = copy pc; pc3 = copy pc; pc4 = copy pc
msh.Pos.Controller = pc; msh.Pos.Controller.Percent = 0
msh2.Pos.Controller = pc2; msh2.Pos.Controller.Percent = 25
msh3.Pos.Controller = pc3; msh3.Pos.Controller.Percent = 50
msh4.Pos.Controller = pc4; msh4.Pos.Controller.Percent = 75
hide #(ssp, ellps)
playAnimation()

2.2.5. АТАКА


В примере вертолет поражает цель. Роль оружия выполняет большой спрей (рис. 2.20).

        SuperSpray

Рис. 2.20. Атака

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

delete $*
viewport.ActiveViewport = 4
max tool zoomExtents
-- 1
animationRange = interval 0f 30f
sliderTime = 0f
timeConfiguration.PlaybackLoop = false
hClr = color 100 100 100
sR = 15
sph = sphere radius:sR segs:32 wireColor:hClr
playAnimation()
convertToPoly sph
move sph.Verts[#{120..124, 151..157, 182..190, 214..222, 246..254, \
    278..286, 310..318, 343..349, 376..380}] [2, 0, 0]
move sph.Verts[#{153..155, 184..188, 215..221, 247..253, 279..285, \
    312..316, 345..347}] [2., 0, 0]
move sph.Verts[#{186, 217..219, 248..252, 281..283, 314}] [2.5, 0, 0]
move sph.Verts [#{217..219, 249..251, 281..283}] [1.5, 0, 0]
move sph.Verts[#{249..251}] [0.5, 0, 0]
addModifier sph (turboSmooth ())
maxOps.CollapseNodeTo sph 1 true
playAnimation()
cl = cylinder radius:2 height:15 heightSegs:1 wireColor:hClr pos:[0, 0, sR]
cH = 45
cl2 = cylinder radius:4 height:cH heightSegs:15 wireColor:hClr
rotate cl2 90 [0, 1, 0]; move cl2 [-cH - sR, 0, 0]
bL = 32; bH = 2
bx = box length:bL width:4 height:2 wireColor:hClr \
    lengthSegs:16 widthSegs:2 pos:[-60, -30, -10]
convertToPoly bx
polyOp.ExtrudeFaces bx #{16, 18, 48, 50, 74..75} 14
polyOp.ExtrudeFaces bx #{15, 17, 47, 49, 92..93} 14
bx.Pos = cl.Pos + [0, 0, cl.Height + bH / 2]
playAnimation()
convertToPoly cl2
polyOp.ExtrudeFaces cl2 #{12..13} 14
polyOp.ExtrudeFaces cl2 #{12..13} 2
polyOp.ExtrudeFaces cl2 #{372} 5
bx2 = copy bx; bx2.WireColor = hClr
scale bx2 [0.75, 0.75, 0.5]; rotate bx2 90 [1, 0, 0]
vP = polyop.getVert cl2 397 in coordsys world
vP2 = polyop.getVert cl2 399 in coordsys world
dx = vP2[1] - vP[1]; dz = vP2[3] - vP[3]
bx2.Pos = vP + [0.5 * dx, bH / 2, 0.5 * dz]
playAnimation()
aRng = 200f
animationRange = interval 0f aRng
cl.Parent = sph; cl2.Parent = sph; bx.Parent = sph; bx2.Parent = sph
pth = arc radius:300 from:0 to:150 pie:off reverse:on pos:[0, 0, 0] wireColor:black
pc = path_Constraint follow:on axis:0
pc.Path = pth
sph.Position.Controller = pc
hide pth
-- 2
bxCntrl = bx.Rotation.Controller
bxCntrl2 = bx2.Rotation.Controller
bx3 = box length:bL width:bL height:(bL / 2) wireColor:red \
    lengthSegs:16 widthSegs:16 heightSegs:8 pos:[0, 0, 0]
bmT = 136f
eS = 120f
bm = bomb strength:0.05 gravity:-0.1 detonation:bmT \
    minFragmentSize:1 maxFragmentSize:10 \
    spin:15 pos:bx3.Pos chaos:5
bindSpaceWarp bx3 bm
ssp = superSpray viewType:2 viewPercent:100 standardParticle:7 \
    off_Axis:0 quantityMethod:1 total_Number:20 \
    emitter_Start:eS emitter_Stop:eS display_Until:bmT \
    life:100f size:7 size_Variation:0 speed:10 \
    speed_Variation:15 bubble_Amplitude:0.0 bubble_Phase:0 \
    iconSize:0 wireColor:green
animate on (
    at time 0f (bxCntrl.Z_Rotation = 0; bxCntrl2.Y_Rotation = 0)
    at time aRng (
        bxCntrl.Z_Rotation = (2 * 1440)
        bxCntrl2.Y_Rotation = (3 * 1440)
    )
)
with redraw off (
    sliderTime = es
    ssp.Pos = sph.Pos
    rotate ssp (eulerAngles 119 -1 -4)
    sliderTime = 0f
--    viewport.Zoom 2.1; viewport.Pan 130 0
    max tool zoomExtents
)
completeRedraw()
playAnimation()

2.3. ПЛАМЯ СРЕДСТВАМИ BLIZZARD

В примере пламя (рис. 2.21) имитируется средствами системы частиц Blizzard (вьюга, пурга).

        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)

fn fBlzFlm h = (
    animationRange = interval 0f 100f
    sliderTime = 0f
    delete $*
    messageBox("Blizzard: Flame")
--    pAMp = particle_Age color1:red age1:20 \
--        color2:[255, 255, 0] age2:50 color3:white age3:100
--    smkMp = smoke color1:[255, 0, 0] color2:[255, 128, 0]
    smkMp2 = smoke color1:black color2:white
--    mtS = standard diffuseColor:[255, 100, 0] diffuseMap:pAMp \
--        showInViewport:true diffuseMapEnable:true \
--        useSelfIllumColor:on selfillumColor:[255, 50, 0] selfillumMap:pAMp \
--        opacityMap:smkMp2 opacity:100
    mtS = standard diffuseColor:[255, 100, 0] \
        showInViewport:true diffuseMapEnable:true \
        useSelfIllumColor:on selfillumColor:[255, 50, 0] \
        opacityMap:smkMp2 opacity:100
    meditMaterials[1] = mtS
    sph = sphere radius:4 segs:16 pos:[-50, 0, 0] material:meditMaterials[1]
    ns = noiseModifier seed:0 scale:5 fractal:on roughness:0 iterations:4.5 \
            strength:[-3, 5.5, 12] frequency:0.25 phase:0f animate:on
    addModifier sph ns
    hide sph
    blz = blizzard rotation:(eulerToQuat (eulerAngles 0 -170 0)) pos:[0, 0, -40]
    blz.Tumble = 0
    blz.Tumble_Rate = 0
    blz.Emitter_Length = 45
    blz.Emitter_Width = 45
    blz.EmitterHidden = on
    blz.Speed = 2
    blz.Speed_Variation = 0.3
    blz.QuantityMethod = 0
    blz.Birth_Rate = 4
    blz.Total_Number = 100
    blz.Emitter_Start = -250f
    blz.Emitter_Stop = 250f
    blz.Display_Until = 100f
    blz.Life = 40f
    blz.Life_Variation = 35f
    blz.SubsampleEmitterTranslation = off
    blz.SubsampleCreationTime = on
    blz.SubsampleEmitterRotation = off
    blz.Size = 2.0
    blz.Size_Variation = 10        -- %
    blz.Growth_Time = 10f
    blz.Fade_Time = 30f
    blz.Seed = 12345
    blz.ViewType = 2                -- Mesh
    blz.ViewPercent = 100
    blz.ParticleType = 2            -- Instanced Geometry
    blz.MaterialSource = 1        -- Instanced Geometry
    blz.InstancingObject = sph
    blz.InstanceSubTree = off
    blz.InstanceKeyOffsetType = 0
    blz.InstanceFrameOffset = 0
    blz.Spin_Time = 30f
    blz.Spin_Time_Variation = 0
    blz.Spin_Phase = blz.Spin_Phase_Variation = 0
    blz.SpinAxisType = 0
    blz.SpawnType = 0
    blz.Interparticle_Collisions_On = 0
    max modify mode
    select blz
    messageBox("First press button Get Material From in rollout Particle Generation")
    backgroundColor = white
    max render scene
)
fBlzFlm 1

Список работ

Рейтинг@Mail.ru