Про восьмой флеш и физику. Часть 3

July 3, 2007

Flash 8 & Физика. Взаимодействие сложных объектов



Эта статья завершает собой серию материалов посвященных интеграции flash8 и физики. Сегодня мы перейдем от рассмотрения взаимодействия идеальных тел, таких как точка и линия, к более сложным и приближенным к реальной жизни. Серия заканчивается не потому, что закончились интересные законы физики|математики, а из-за грядущего выхода flash cs3 с поддержкой actionscript3. Работа с которым и будет рассматриваться в других сериях статей.

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

Но сначала надо разобраться с одним вопросом по материалам прошлого материала. В письме меня спросили: “зачем для определения того произошло ли столкновение и где именно оно произошло между мячом и стеной применять эти сложные формулы, ведь я же писал, что во flash есть стандартный метод hitTest позволяющий определить пересекаются ли два объекта клипа, а значит можно при наступлении очередного кадра вызвать этот метод и узнать ударился ли мяч об стену”. Что же, я виноват в том, что не акцентировал на это внимание. Итак, не забывайте что flash, как и любое П.О. оперирует временем как дискретной величиной. В реальности это не так. Например, если мяч движется достаточно быстро, то возможно, что в один такт времени (длина такта определяется через частоту кадров в минуту) мячик будет находиться до стены, а в следующем кадре, уже за стеной. Следовательно вы никогда не узнаете о том, что произошло столкновение мяча и стены. Наращивать частоту кадров – это также не выход и я уже ранее писал, почему это опасно.

Для того чтобы разобраться с дальнейшим материалом нам придется вспомнить пару основных законов физики.

Если задан некоторый объект то, прежде чем говорить о законе изменения его положения, необходимо определиться с используемой системой координат. Наиболее часто в механике используют прямоугольную декартову систему координат (далее СК), цилиндрическую и сферическую. Существуют формулы перехода из одной системы в другую, я их не привожу, т.к. эта статья не учебник математики. Пока все что вам следует запомнить, то, что для ряда задач имеет смысл перейти при поиске решения в иную СК. Что-то там посчитать и вернуться назад в исходную СК.

Когда мы говорим о движении объекта, то фактически мы говорим о том, что изменятся вектор, проложенный от центра СК к объекту (это вектор еще называют радиус-вектором). Скорость движения объекта это первая производная по времени от радиус-вектора v(t) = s’(t). Скорость направлена по касательной к траектории движения. Если объект движется в двумерном пространстве, то можно получить проекции вектора скорости на оси OX и OY, и для каждой из проекций будет соблюдаться правило vx(t) = sx’(t). Проще говоря, скорость это показатель того, как быстро происходит изменения координат объекта. В свою очередь скорость не обязана быть всегда постоянной, и может изменяться во времени. Итак, ускорение это векторная величина, характеризующая как быстро изменяется скорость движущейся точки. И в свою очередь, равна производной от скорости, т.е. a(t) = v’(t) = s’’(t). Разумеется, что эти формулы могут быть рассмотрены и в обратном порядке, так зная ускорение можно с помощью интегрирования вычислить (с точностью до некоторой постоянной величины-добавки) скорость, а на основании скорости вычислить и закон изменения радиус-вектора движущегося объекта. Кроме того, движение может быть не только поступательным, но и вращательным, со своими особыми формулами. Теперь будучи наделенными, столь академическими знаниями мы можем рассмотреть и классические законы движения Ньютона. Я мог бы написать пару конечных формул, позволивших бы нам непосредственно сымитировать столкновение сложных объектов, но полагаю лучшим рассказать основные идеи.

Первый закон гласит: “Всякая материальная точка сохраняет состояние покоя или равномерного и прямолинейного движения до тех пор, пока к ней не будет приложена внешняя сила, которая и выведет ее из этого состояния”. Проще говоря, если мы возьмем космонавта летящего в космосе, то, как бы он не “дрыгал” ногами и руками, но изменить свой полет, до тех пор, пока кто-то извне не применит к нему силу, не удастся.

Сила, которая действует на космонавта, изменяет ускорение, с которым он движется, т.е. космонавт выходит из состояния равномерного движения. Второй закон определяет формулу F=a*m (где m-масса космонавта). Очевидно, что изменение ускорения будет прямо пропорционально этой внешней силе, и обратно пропорционально его массе. Чем более упитанным будет космонавт, тем больше нужно приложить силу, чтобы он начал двигаться. Третий закон гласит что когда мы применяем к чему то силу, то и это что то действует на нас с силой равной той которую мы применили, но в обратном направлении. Так если космонавт ударит кулаком об стену, то стена оттолкнет его с равной противосилой. На земле мы бы не заметили этого из за силы тяжести, которая нивелирует противодействие, но в космосе наш приключенец отлетит от стены.

Следующий фактор который надо учитывать это гравитация. Любые два объекта даже маленькие как песчинки притягиваются друг к другу, с силой определяемой формулой: F=G*(M1*M2)/(R^2). Здесь M1 и M2 – это массы двух объектов, R - расстояние между ними, и G – гравитационная постоянная. Правда весь фокус в том, что G = 6.67*10E-11 (десять в минус одиннадцатой степени!). Если эти два объект весят меньше пары миллионов тонн, то сила притяжения очень-очень-очень мала и не заметна. Поэтому часто в играх, использующих гравитацию, этот коэффициент полагают равным 1 или даже еще большему числу.

Еще момент в том, что эта формула очень сложна (раз сложна, то значит и ресурсоемка). Так, что для flash ролика исполняющегося с достаточно большой частотой кадров (обычно рекомендуется цифра 24 к/с) применять эту формулу для достаточно большого количества объектов не стоит.

Вообще я хочу, чтобы вы понимали, если вы разрабатываете в flash игру, то никто не ждет от нее точного физического подобия, самое главное, чтобы все работало быстро и было “забавно”. Поэтому есть альтернативный подход к гравитации. Просто придумайте, какое то число для гравитации, например 1, и отнимайте это число от векторной скорости по OY в каждом кадре. Теперь два мяча не будут притягиваться друг к другу, а только вниз, к земле. Для следующего пример в Flash создайте символ с именем “mc_circle”. Путь это будет круг без заливки. Обязательно разрешите обращение к этому символу из ActionScript (контекстное меню символа в окне библиотеке -> пункт Linkage). Затем разместите на первом кадре ролика, два объекта этого символа и дайте им имена ball_a_1 и ball_b_1. Следующий код введите в обработчик первого кадра слоя Layer1.
  1. // не забывайте что во flash ось OY перевернута
  2. vy = -10; // скорость брошенного вверх мяча
  3. g = 1; // гравитация действующая вниз
  4. fr_num = 1; // номер фрейма
  5.  
  6. // функция вычисляющая квадрат аргумента
  7. function SQR (x: Number){ return x*x;}
  8.  
  9. _root.onEnterFrame = function (){
  10.  ball = _root.attachMovie( "mc_ball", "ball_real_" + fr_num ,_root.getNextHighestDepth() );
  11. ball._x = 40;
  12.  // координата X всегда остается постоянной. т.к. гравитация направлена внизу по оси OY
  13.  ball._y = 100 + vy*fr_num + g*fr_num*fr_num / 2;
  14.  // правильный расчет положения объекта -- чем ближе к земле тем больше скорость падения мяча
  15.  ball = _root.attachMovie( "mc_ball", "ball_dummy_" + fr_num ,_root.getNextHighestDepth() );
  16.  ball._x = 120;
  17.  ball._y = 100 + g*fr_num * 30;
  18.  // поправка 30 была взята "от балды" так чтобы падение смотрелось эффектнее
  19.  // неправильный простой расчет положения объекта -- скорость падения всегда одинакова
  20.  
  21.  // вычисляем ссылки на два шарика для которых идет расчет 
  22.  var ball_a = _root ["ball_a_" + fr_num];
  23.  var ball_b = _root ["ball_b_" + fr_num]; 
  24.  // расчет растояния между мячами
  25.  var R = Math.sqrt (SQR(ball_a._x - ball_b._x) + SQR(ball_a._y - ball_b._y));
  26.  var Gkoff = 8000;
  27.  var M1 = 10;// масса первого шарика
  28.  var M2 = 300;// масса второго шарика
  29.  var F = Gkoff * M1*M2/(R*R);// сила притяжения между двумя мячами
  30.  var A1 = F / M1; // ускорение действующее на первый мяч
  31.  var A2 = F / M2; // ускорение действующее на второй мяч
  32.  // т.к. расчет силы выполняется на основании предыдущего положения мячиков
  33.  // то прошедшее время полагается равным 1
  34.  var S1 = (A1*1*1/2);
  35.  var S2 = (A1*1*1/2);
  36.  // находим угол вектора соединяющего два мяча и выполняем 
  37. проецирование расстояния на него
  38.  var angle_1 = Math.atan2(ball_a._y - ball_b._y, ball_a._x - ball_b._x)
  39.  var angle_2 = Math.atan2(ball_b._y - ball_a._y, ball_b._x - ball_a._x) 
  40.  ball_a_nova = _root.attachMovie( "mc_ball", "ball_a_" + (fr_num+1) ,_root.getNextHighestDepth() );
  41.  ball_a_nova._x = ball_a._x + S1*Math.cos(angle_2);
  42.  ball_a_nova._y = ball_a._y + S1*Math.sin(angle_2);
  43.  ball_b_nova = _root.attachMovie( "mc_ball", "ball_b_" + (fr_num+1) ,_root.getNextHighestDepth() );
  44.  ball_b_nova._x = ball_b._x + S2*Math.cos(angle_1);
  45.  ball_b_nova._y = ball_b._y + S2*Math.sin(angle_1);
  46.  // увеличиваем номер кадра, который используется для 
  47. конструирования динамически создаваемых объектов клипов - показывающих движение мячей в динамике
  48.  fr_num++; 
  49. }
Результат работы показан на рис. 1.



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

Последняя сила, которую мы рассмотрим – это сила трения. Всякий раз, когда некоторый предмет, например ящик, толкнули с силой F, то возникающая сила трения действует с силой F=k*m*g и направленной противоположно. В формуле m – масса объекта, g – ускорение свободного падения. И последнее, множитель k – это коэффициент трения (в случае примера с ящиком, более правильно говорить о коэффициенте трения скольжения) и который зависит от свойств материала. Для того чтобы написать сценарий, который моделирует эту силу, необходимо вычислить значение ускорения противодействующего нашей попытке перемещать ящик. Из второго закона Ньютона получается F = a*m = k*m*g. Значит, a = k*g. Получив эту величину, мы будем ее отнимать от векторной скорости ящика до тех пор, пока она не станет равной нулю. Также как и для гравитации можно использовать упрощенный подход к трению. Например, вы будете каждый кадр умножать скорость на некоторое число от 0 до 1 (это коэффициент ослабления), до тех пор, пока скорость не станет почти равной нулю, в этот момент вы остановите перемещение ящика вообще.

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

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

Точка-мячик обладает некоторой массой и движется с некоторой скоростью, произведение этих двух величин является также вектором и называется импульс. Также скорость и масса объекта определяют его кинетическую энергию по формуле E=mv^2/2. Кинетическая энергия может переходить в другие формы. Так если бить молотком по шляпке гвоздя, то он будет нагреваться. Поэтому часто задачу моделирования столкновения еще более упрощают, когда говорят именно об упругом столкновении. Для которого характерно, что и энергия и импульс остается неизменной. Итак, если Va1 и Va2 – это векторы, задающие скорость движения двух тел до столкновения, Vb1 и Vb2 – это скорости после столкновения, массы объектов соответственно M1 и M2, то мы можем заявить, что сумма импульса и сумма кинетической энергии остается неизменной, т.е:

Эту систему уравнений я загнал в mathcad и получил конечные формулы представленные на рисунке 2. Честно говоря, эти формулы надо было еще оптимизировать, чтобы сократить повторяющиеся вычисления, но эти выкладки я здесь не привожу.



Еще шаг, столкновение зависит от формы объектов. Я привожу ситуацию столкновения двух шаров на рис. 3.



Здесь va1 – векторная скорость первого шара и va2 – векторная скорость второго шара. При столкновении мы проводим линию взаимодействия, между центрами масс обоих шаров. На эту линию надо спроецировать исходные вектора движения. Зачем это надо делать интуитивно понятно из следующего рассуждения: если два шара толкнуть друг на встречу другу с силой F, то сила соударения будет больше чем в случае касательного удара с такой же силой F. Это значит, не вся энергия шара участвует в столкновении, значит еще нужно выполнить проекцию скоростей шаров на линию перпендикулярную оси взаимодействия. В расчете новых скоростей по правилу сохранения импульса и энергии будет участвовать только первые проекции. И на последнем шаге, мы проецируем оба вектора скорости на оси OX и OY.

Для следующего примера создайте в библиотеке символ mc_circle – это будет круг радиусом 25, и поместите два объекта в слой Layer1 под именами ball_obj_1 и ball_obj_2.
  1. // короткие ссылки на два объекта мячика
  2. ball_1 = _root.ball_obj_1;
  3. ball_2 = _root.ball_obj_2;
  4. ball_1.r = ball_2.r = 25;//радиус шара
  5.  
  6. ball_1.vel = {x: 1, y: 1};// скорости объектов
  7. ball_2.vel = {x: -3, y: 1};
  8. ball_1.m = 60;// массы объектов
  9. ball_2.m = 20;
  10.  
  11. function bounce() {
  12.  var alpha = Math.atan2(ball_1._y-ball_2._y, ball_1._x-ball_2._x);
  13.  var co = Math.cos(alpha);
  14.  var si = Math.sin(alpha);
  15.  //Проецируем вектора скоростей на линию взаимодействия
  16.  var pL1 = ball_1.vel.x*co+ball_1.vel.y*si;
  17.  var pL2 = ball_2.vel.x*co+ball_2.vel.y*si;
  18.  // и на перпендикуляр линии взаимодействия
  19.  var pA1 = ball_1.vel.y*co-ball_1.vel.x*si;
  20.  var pA2 = ball_2.vel.y*co-ball_2.vel.x*si;
  21.  // Используем закон сохранения импульса и энергии - формулы 
  22. были упрощены по сравнению с тем что я получил в MathCad
  23.  var P = (ball_1.m*pL1+ball_2.m*pL2);
  24.  var V = (pL1-pL2);
  25.  var v2f = (P+ball_1.m*V)/(ball_1.m+ball_2.m);
  26.  var v1f = v2f-pL1+pL2;
  27.  pL1 = v1f;
  28.  pL2 = v2f;
  29.  
  30.  //Высчитав вектор скорости после соударения проецируем его на оси OX, OY
  31.  ball_1.vel.x = pL1*co-pA1*si;
  32.  ball_2.vel.x = pL2*co-pA2*si;
  33.  ball_1.vel.y = pA1*co+pL1*si;
  34.  ball_2.vel.y = pA2*co+pL2*si;
  35. }
  36.  
  37. // функция вычисляющая квадрат величины
  38. function SQR (x){return x*x;}
  39.  
  40. _root.onEnterFrame = function() {
  41. // выполняем перемещение мячиков, полагая, что за один кадр прошла 
  42. ровно одна единица времени
  43.  ball_1._x += ball_1.vel.x * 1;
  44.  ball_1._y += ball_1.vel.y * 1;
  45.  ball_2._x += ball_2.vel.x * 1;
  46.  ball_2._y += ball_2.vel.y * 1;
  47.  var R = Math.sqrt (SQR(ball_1._x - ball_2._x)+SQR(ball_1._y - ball_2._y));
  48.  if (R <= ball_1.r + ball_2.r){
  49.  // было соударение
  50.  bounce (); }};


Откровенно говоря, условие if (R <= ball_1.r + ball_2.r) просто ужасно. Т.к. из-за того самого дискретного времени есть малая доля вероятности, что шарики после удара не отлетят а “слипнутся”. Подумайте сами, почему это происходит и как этого избежать.