3D Engineering

...Лучшее из общего.

  • Увеличить размер шрифта
  • Размер шрифта по умолчанию
  • Уменьшить размер шрифта

Введение

Введение

В уроке из частиц создаются снеговик и другие фигуры. Для получения результата использованы система частиц PF Source (поток частиц) и язык программирования MAXSсript.
Системы частиц позволяют моделировать явления реального мира и воплощать разного рода вымышленные образы.
Так, многие физики полагают, что в основе мироздания положены частицы. Ученые ставят опыты, ускоряя и сталкивая частицы, надеясь подтвердить свои теории. Системы частиц, в том числе и 3ds Max, вполне пригодны для отображения наблюдаемых физиками процессов (рис.1).

Рис. 1. Бозон – частица Бога? (источник Яндекс. Картинки: бозон)

Весьма эффективно могут быть представлены частицами и работы из бисера (рис. 2).

Рис. 2. Мы в ответе за тех, кого приручаем (источник Яндекс. Картинки: бисероплетение)

MAXSсript не только предоставляет дополнительные возможности при работе с системами частиц, но и укоряет процесс получения результата. Кроме того, код компактно описывает моделируемую сцену, что весьма часто позволяет оценить генерируемые им образы, не имея под рукой 3ds Max.
Эти и иные преимущества будут ощутимы при наличии определенных навыков и достаточно продолжительной тренировки.
В качестве иллюстрации тезиса о компактности представления модели приведем код, обеспечивающей решение задачи, разобранной в размещенном на 3dgo.ru уроке "Текст из частиц" от Shoohrat Yuldasheva.
Задача решается в следующей формулировке:

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

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

-- Block 1
delete $*
animationRange = interval 0f 100f
ng = 10.0
t = text text:"Some T" size:100 rotation:((eulerAngles -ng ng 0) as quat)
addModifier t (extrude amount:0.5)
cl = cylinder radius:1 height:40
hide #(t, cl)
-- Block 2
pF = PF_Source enable_Particles:true quantity_Viewport:100 \
 rotation:((eulerAngles -ng (180 - ng) 0) as quat) isSelected:on
particleFlow.BeginEdit()
opBth = birth emit_Start:0 emit_Stop:0 type:0 amount:600
opPO = position_Object emitter_Objects:#(t) delete:on location:2 \
 apart_Placement:on apart_Distance:8
opSI = shape_Instance shape_Object:cl
opSP = scaleParticles type:2 constrain_Scale:off
opRt = rotation direction:2 euler_X:ng euler_Y:-ng
opRP = renderParticles type:2
opDP = displayParticles color:red type:6
evn = event()
particleFlow.EndEdit()
evn.AppendAction opBth
evn.AppendAction opPO
evn.AppendAction opSI
evn.AppendAction opSP
evn.AppendAction opRt
evn.AppendAction opDP
pF.AppendAction opRP
pF.AppendInitialActionList evn
-- Block 3
with redraw off (
 max tool animmode; set animate on
 sliderTime = 0f; opSP.Z_Scale_Factor = 0
 sliderTime = 50f; opSP.Z_Scale_Factor = 100
 sliderTime = 100f; opSP.Z_Scale_Factor = 50
 max tool animmode; set animate off
 sliderTime = 0f
)
max modify mode
max tool zoomExtents
playAnimation()

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

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

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

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

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

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

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

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

Снежинка

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

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

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

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

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

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

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

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

Снеговик

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

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

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

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

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

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

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

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

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

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

Рис. 8. 335-й кадр анимации

Рис. 9. 635-й кадр анимации

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

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

Заключение

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

Источники

  1. Autodesk® 3ds Max® 2009 MAXScript Reference.
  2. Бартеньев О. В. Программирование модификаторов 3ds Max. Учебно-справочное пособие. – М.:Физматкнига, 2009. – 341 с.
  3. Яндекс. Картинки.
  4. http://100byte.ru/
 

Архив статей

 июл   Август 2020   сен

ВПВСЧПС
   1
  2  3  4  5  6  7  8
  9101112131415
16171819202122
23242526272829
3031 
Julianna Walker Willis Technology

Случайная новость

vladirr

Приветствую всех снова... Сегодня мы с вами продолжим работу по улучшению интерфейса макса и приведению его в соответствие нашим пожеланиям. Исходная посылка - та же, что и в прошлом уроке - мы подготавливаем удобные инструменты для lp-моделирования. На примере сегодняшнего урока я хочу показать, как можно упростить себе жизнь, не заморачиваясь на серьезное программирование.
Итак, вводная - мы моделируем, требования к моделингу жесткие, мы практически постоянно должны контролировать количество вершин, фейсов, направления ребер... По умолчанию в максе отображаются только видимые ребра (Vizible Edges), но для нашей ситуации это не подходит. Конечно, мы можем кликнуть правой кнопкой по объекту, выбрать его свойства, снять флажок "Edges Only" и нажать OK. Итого - 4 операции, для обратного действия - столько же. Много! Нам желательно - один клик, либо нажатие одной клавиши.
Перво-наперво идем Customize->Customize User Interface вкладка Keyboard...

и убеждаемся, что такого параметра для назначения горячей клавиши нам не предоставили :( Смотрим то же на владке Toolbars - может быть, есть возможность вытащить на панель закладок кнопку с данной функцией? Опять неудача. Ищем пункт Properties в списке Action, в надежде посмотреть на макроскрипт и "выдрать" оттуда необходимый нам кусок...

М-да... Редактирование макроса для свойств объекта нам недоступно... Приблизительно к таким же по существу результатам приводит попытка найти информацию в справке по максскрипту. Тупик? Не совсем. Открываем окно MaxScript Listener и активируем MacroRecorder

- этот инструмент позволяет фиксировать большую часть событий, происходящих в пределах макса. Создаем бокс, кликаем по нему правой кнопкой мыши и выбираем пункт - Properties, снимаем галку Edges Only и жмем OK. смотрим в окно MaxScript Listener:

Ура, у нас есть параметр! Вводим для проверки в окне MaxScript Listener:

$.alledges = not $.alledges

и жмем ввод. Мы задали выражение на смену значения булевской переменной на противоположное. В окне проекции можно видеть, что вкрытые ребра исчесзли. Выполняем эту команду еще раз - появились, и так - по циклу. Замечательно. Следующий этап нам уже знаком - открываем пустой файл скрипта и пешем "рыбу" - шапку будущего макроса и закрытые круглые скобки в качестве тела макроса:

--------------------------------
macroScript ShowEdges
category:"MAX Script Tools"
internalcategory:"MAX Script Tools"
buttontext:"ShowHiddenEdges"
toolTip:"Show hidden edges"
(
)
------------------------------------

Сохраняем в каталог //root_max/ui/macroscript под именем Macro_AllEdges и заменяем разрешение на *.mcr. Перезапускаем макс и по аналогии с прошлым уроком размещаем кнопку нашего макроса на панели Main Toolbar. Теперь поработаем над красивостями :) а потому открываем фотошоп или аналогичный редактор, и рисуем иконки. В соответствии со справкой, нам надо нарисовать 4 *.bmp файла с общим названием и разными префиксами, я их обозвал MacroScriptTools*.bmp :
1. MacroScriptTools_16a.bmp - разрешение 16x15px, оттенки серого (определяем прозрачность иконки)
2. MacroScriptTools_16i.bmp - разрешение 16x15px, собственно сама иконка (маленькая)
3. MacroScriptTools_24a.bmp - разрешение 24x24px, оттенки серого (определяем прозрачность иконки)
4. MacroScriptTools_24i.bmp - разрешение 24x24px, собственно сама иконка (большая)

Над дизайном я особо не заморачивался, использовав уже существующую иконку и слегка ее изменив. Вы можете нарисовать свои иконки сами или скачать уже готовые мои :) Чтобы скачать архив иконок нажмите на изображение:

Замечательно, размещаем полученные изображения в папку
\\root_max\UI\Icons\

Идем обратно в макс и кликаем правой кнопкой мыши по созданной нами иконке макроса. Выбираем в контекстном меню пункт Edit Button Appearance и переключаемся в положение Images Button.

В разделе Group выбираем нашу группу: MacroScriptTools , в соответствии с названием, данным иконкам. В окне ниже - только одна иконка, и это правильно, поскольку данную группу создали мы и в ней всего одна иконка. Выбираем ее и жмем OK. Получили аккуратную иконку в интерфейсе :)


Щелкаем по иконку правой кнопкой мыши, выбираем пункт редактирования макроса и дорабатываем тело скрипта:
-----------------------------------
macroScript ShowEdges
category:"MAX Script Tools"
internalcategory:"MAX Script Tools"
buttontext:"ShowHiddenEdges"
toolTip:"Show hidden edges"

(
on execute do
(
if $selection.count == undefined then
(
)
else
try( $.alledges = not $.alledges ) catch ()

)


)
--------------------------------

Что мы написали?

Собственно говоря, только то, что если ничего не выделено (if $selection.count == undefined ) - ничего и не делать, в противном же случае (то есть когда что-то выделено) - изменить режим показа ребер выделения на противоположный.
В тело макроса так же добавлена конструкция

try( ...) catch ()

позволяющая избежать ошибки при попытке выполнить поставленную задачу над объектом, не имеющим ребер как таковых. Как и в прошлом уроке, по существу работы данной функции я вас отсылаю к справке :)
Ну что ж, сохраняем макрос (CTRL+S) и "компилируем" его (CTRL+E), создаем в сцене несколько различных объектов и, поочередно выделяя их по одному пытаемся нажать на нашу кнопку... результат очевиден :)

В качестве послесловия. В данном примере, в отличие от предыдущего, мы не стали задавать изменение состояния кнопки макроса, поскольку функция у нас циклична, и кнопка работает как классический триггер - "перебрасывает" объект из одного состояния в другое и обратно. Главная идея урока - помимо создания нужного мне инструмента :) - показать еще один способ поиска решения поставленной задачи, а так же показать "облегченный" алгоритм, подходящий для описания всех действий, изменяющих состояние или свойства объекта, которые можно описать с помощью булевских переменных. Напомню еще раз, что для уже созданного макроса всегда можно назначить горячую клавишу вызова, тогда вам не придется даже нажимать на кнопку, на создание которой потрачено столько трудов :)

Ну вот вроде и все на сегодня - пользуйтесь, экспериментируйте, творите.

Всегда Ваш - VladiRR

далее