Переменная clock

POV-Ray позволяет создавать анимацию в виде последовательности кадров. Чтобы на этих кадрах что-то изменялось, нужна переменная, задающая время -- она называется clock. И еще нужно задать кое-какие настройки, например, количество кадров. О настройках поговорим чуть позже, а пока рассмотрим пример:

camera {
  location <0, 3, -6>
  look_at <0, 0, 0>
}

light_source { <20, 20, -40> color White }

plane {
  y, 0
  pigment { checker color White color Black }
}

box {
  <-1, 0, -1> , <1, 2, 1>
  pigment { color Green }
  translate <clock, 0, 0>
}

y -- это вектор нормали к поверхности и равен он <0,1,0>.

Ключевой момент здесь -- строка translate <clock, 0, 0>. Переменная clock, по умолчанию, равна 0 в начале анимации и 1 -- в конце. Благодаря этому бокс должен переместиться на одну клетку вправо.

Задаем число кадров

Теперь зададим число кадров. Номера первого и последнего кадров задаются параметрами Initial_Frame и Final_Frame. Initial_Frame по умолчанию равен 1, и менять его мы не собираемся, а Final_Frame зададим равным 20. Это делается в командной строке с помощью опции +KFF так: +KFF20. Второй вариант -- добавим в INI-файл строку Final_Frame=20. В результате, для каждого кадра будет создаваться свой графический файл с номером, равным номеру кадра.

Видео

Теперь полученную последовательность кадров можно преобразовать в видео. Для этого есть масса инструментов, например Virtualdub -- широко распространенный, свободный и кроссплатформенный.

Вот что у нас получилось в итоге.

translate_box.gif

Катится, вертится шар полосатый...

В прошлом примере изменялся всего параметр -- x-координата бокса. А что если изменяться будет сразу несколько параметров? Рассмотрим более сложное движение: пусть шар катится по плоскости...

#include "colors.inc"

camera {
  location <0, 3, -6>
  look_at <0, 0, 0>
}

light_source { <20, 20, -20> color White }

plane {
  y, 0
  pigment { checker color White color Black }
}

sphere {
  <0, 0, 0> , 1
  pigment {
    gradient x
    color_map {
      [0.0 Blue  ]
      [0.5 Blue  ]
      [0.5 White ]
      [1.0 White ]
    }
    scale .25
  }
  rotate <0, 0, -clock*360>
  translate <-pi, 1, 0>
  translate <2*pi*clock, 0, 0>
}

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

Начнем с вращения. Так как оно выполняется относительно начала координат, то его нужно сделать первым. Иначе шар будет вращаться по орбите вокруг начала координат:

translate_rotate_orbit.gif

А это совсем не то, что нам нужно. Тогда продолжим. Вращение выполняется первым и по часовой стрелке -- отсюда знак минус. При этом тело совершает полный оборот -- от 0 до 360 градусов (clock по-прежнему изменяется от 0 до 1). Первый translate помещает тело на исходную позицию: 1 по Y поднимает шар на поверхность, а -pi сдвигает его к левой границе кадра. Следующий translate задает перемещение шара вправо, которое нужно выполнить синхронно с вращением. Длина пути, который пройдет шар за один оборот, равна 2*pi*r, где r -- радиус шара (у нас он равен 1). Значит за один шаг шар переместиться на 2*pi*r*clock единиц. Если длину пути выбрать другой, то шар будет проскальзывать на месте или его вращение не будет поспевать за скольжением по поверхности.

translate_rotate.gif

Заметим, что на каждом кадре перемещения отсчитываются от начала координат. Т. е. шар поворачивается на все больший угол и перемешается все дальше вправо, но точкой отсчета служит не предыдущий кадр, а начало координат.

Мы сделали сферу полосатой, чтобы вращение было заметней. Заливка цветом происходит по градиенту X, а цветовая карта color_map указывает, как именно будут изменяться цвета. gradient x возвращает значения от 0.0 до 1.0. Пока значение x меньше, чем у первого цвета на карте (0.0 Blue), для заливки используется этот цвет, как только x станет принимать значения в диапазоне от 0.0 до 0.5, будет использоваться следующий цвет 0.5 Blue. В нашем случае это один и тот же синий цвет. Когда x станет равным 0.5, для заливки будет использоваться белый цвет. Затем шаблон заполнения повторяется.

Условный оператор. Переменные, зависящие от времени

Снова усложним задачу. Допустим, что шар, докатившись до правой границы кадра, меняет направление движения на 135 градусов и катится вглубь сцены. Получается, что наша будущая анимация делится на две части: уже известную по прошлому примеру и новую. Для того, чтобы ее реализовать, нам понадобится изменить переменную clock: в первой части она, как и раньше, будет изменяться от 0.0 до 1.0, а во второй -- от 1.0 до 2.0. Сделать это легко, но хотелось бы максимально сохранить имеющийся код, а он построен на том, что clock изменяется от 0.0 до 1.0. Кроме того, нам понадобится условный оператор: он будет следить за тем, чтобы шар изменил направление движения в зависимости от значения clock. Ну что ж, поехали...

#if ( clock <= 1 )
  sphere { <0, 0, 0> , 1
    pigment {
      gradient x
      color_map {
        [0.0 Blue  ]
        [0.5 Blue  ]
        [0.5 White ]
        [1.0 White ]
      }
      scale .25
    }
    rotate <0, 0, -clock*360>
    translate <-pi, 1, 0>
    translate <2*pi*clock, 0, 0>
  }
#else
  // если clock  > 1, то выполняется вторая часть анимации,
  // но мы по прежнему работаем с переменной, изменяющейся от 0 до 1.
  #declare ElseClock = clock - 1;
  sphere { <0, 0, 0> , 1
    pigment {
      gradient x
      color_map {
        [0.0 Blue  ]
        [0.5 Blue  ]
        [0.5 White ]
        [1.0 White ]
      }
      scale .25
    }
    rotate <0, 0, ElseClock*360>
    translate <-2*pi*ElseClock, 0, 0>
    rotate <0, 45, 0>
    translate <pi, 1, 0>
  }
#end

Если вас интересует, почему шар поворачивает именно на {\(135^\circ\)}, то отвечу: никакой магии в этом числе нет. Можете поставить любое другое значение, скорректировав соответственно угол поворота во втором rotate.

Условный оператор выглядит вполне традиционно:

#if ( логическое_условие )
  ...
#else
  ...
#end

Как только clock становится больше 1.0, начнут выполняться команды, стоящие после #else. Здесь мы объявляем новую переменную ElseClock, сделав ее счетчиком времени, уменьшенным по сравнению с clock на единицу. В итоге, когда clock будет принимать значения в диапазоне от 1.0 до 2.0, наша новая переменная ElseClock будет изменяться от 0.0 до 1.0 и мы можем применить к ней все приемы, которые раньше использовали для clock.

Теперь нужно настроить значения clock. Чтобы она изменялась от 0.0 до 2.0, допишем в INI-файл следующие строки:

Initial_Clock = 0.0
Final_Clock = 2.0

На сколько изменяется переменная clock за время одного кадра? Если вся анимация занимает 2 секунды и содержит 20 кадров, то длительность одного кадра составит 0.1 секунды. Это значит, что clock будет последовательно принимать значения: 0 -- для первого кадра, .1 -- для второго, .2 -- для третьего, и т. д. (ноль перед десятичной точкой ставить не обязательно). Длительность шага по времени (0.1) вычисляется автоматически и хранится во встроенной переменой clock_delta.

И, наконец, получим:

if_clock.gif

Если нужно проверить несколько условий, то их помещают в блоки #elseif условного оператора или используют оператор #switch.

Цикл while

Допустим, нам нужно выполнять анимацию вплоть до заданного момента времени. Тогда нам пригодится цикл с условием:

#while (логическое_условие)
  ...
#end

Он будет выполняться до тех пор, пока остается истинным логическое условие, стоящее в скобках.

Давайте нарисуем бокс, поместим на его вершину шар, столкнем его и нарисуем что из этого получится:

#include "colors.inc"

light_source { <0,50,0> color White }
light_source { <10,2.5,0> color White }
light_source { <0,5,-50> color White }

camera { location <5,3,-10> look_at <1,3,0> } 

#declare time = 0.0;
#declare bx = 0.0;
#declare by = 5.0;
#declare vx = 5;
#declare vy = 0.0;
#declare g = 9.8; 

box { <-5,by,2>, <0,-2,-2> pigment {Blue} }
plane { y, 0 pigment {Gray} } 

#while (time <= clock)
  #declare dt = clock_delta;
  #declare bx = bx+vx*dt;
  #declare by = by+vy*dt-0.5*dt*dt;
  #declare vy = vy-g*dt;

  sphere { <bx,by,0>, 0.2 pigment {Green} }

  #declare time = time + clock_delta;
#end

Координаты шара задаются переменными bx, by, а его скорость -- vx, vy. g -- ускорение свободного падения. Если это вам ни о чем не говорит, то просто примите формулы на веру и сосредоточьтесь на рисовании.

Здесь в цикле изменяется "пользовательское" время time, увеличиваясь на каждом шаге на clock_delta. Я назвал это время так, потому что его состоянием пользователь может управлять по своему желанию, тогда как для clock можно задавать только диапазон изменения. На каждом шаге цикла рисуется все больше и больше шаров, до тех пор пока время рисования не закончится (т. е. достигнет значения Final_Clock).

animate_while.gif

Чтобы не рисовать "след" из шаров, достаточно вынести команду sphere за пределы цикла и поместить ее после #end.

Обратите внимание, что сцена освещена тремя источниками света. Зачем так много? А вот зачем. Первый источник освещает сцену сверху и позволяет получить "дорожку" из теней шаров в горизонтальной плоскости. Второй светит сбоку и дает тени шаров на вертикальной стенке бокса. Теперь мы можем увидеть, что вдоль координаты X шар движется равномерно, а вдоль Y -- ускоряется. Наконец, третий источник дает нам "обычное" освещение сцены. Так что если вас не интересуют тени шаров, можете оставить только его.

Заметим, что POV-Ray -- не лучшее средство для моделирования движения, он все-таки только "рисователь". Обычно, движение моделируется с помощью какого-то из языков программирования (например, С), результаты сохраняются в виде команд POV-Ray, а уж тот рисует как двигалось тело. Но, как видим, если нужно, то простое моделирование можно выполнить и в самом POV-Ray.

Кроме цикла с условием #while, в POV-Ray есть и цикл со счетчиком -- #for.



Комментарии

comments powered by Disqus