Учебник по программированию.

Первые шаги. Язык программирования PascalABC.

 

Предыдущий параграф Назад в содержание Следующий параграф


§23. Отталкивающиеся мячи.

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

Для реализации данной задачи необходимо создать отдельную процедуру, которая будет определять, столкнулись мячи или нет, и если столкнулись, то она будет менять их координаты. Данной процедуре дадим следующее имя: IzmeneniyeKoordinatPriStolknovenii. Для её написания нам понадобиться разобрать ряд теоретических вопросов, которые мы рассмотрим отдельно друг от друга по порядку их использования в коде. Соответственно на каждый вопрос у нас должен получиться отдельный блок кода.

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

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

Начертим рисунок и определим необходимые нам параметры:



Расстояние между центрами можно найти из следующего соотношения:

,

где угол между осью и прямой, проходящей через центры мячей.

Угол найдём из следующих соображений: уравнение прямой на плоскости в общем виде выглядит следующим образом: . Здесь коэффициент   есть не что иное, как тангенс наименьшего угла этой прямой к оси . Если угол положительный, то прямая возрастает, если отрицательный убывает. Соответственно, для нахождения угла , нам необходимо найти коэффициент прямой, проходящей через центры мячей. Для этой прямой можем составить систему уравнений:

.

Получаем: , .

На данном рисунке угол отрицательный, т.к. прямая убывает.

Здесь есть один нюанс если , то в знаменателе получится ноль, на который делить нельзя. Однако в этом случае расстояние будет равно:

.

Уточнение положения мячей. Если столкновение произошло, то необходимо уточнить положение мячей во время их столкновения, т.к. как было уже сказано, они могут как бы налететь друг на друга, как показано на рисунке:



Пунктирной линией обозначено положение мячей до уточнения координат, сплошной линией после уточнения координат. Соответственно: координаты мячей до уточнения; координаты мячей после уточнения (буква r обозначает, что это реальные координаты, т.к. тип этих переменных должен быть Real).

вектора скоростей первого и второго мячей соответственно.

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


.


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


.


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

Может получиться так, что мы вернём мячи слишком далеко, поэтому необходимо хранить значения предыдущих координат, предыдущее значение угла b и предыдущее значение переменной count. После завершения цикла возврата мячей необходимо будет выбрать либо новые координаты, либо предыдущие. Критерием выбора будет служить наименьший модуль разницы между суммой радиусов и расстоянием между центров.

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


Определение составляющей скорости участвующей в столкновении. Если мячи двигались друг другу на встречу и столкнулись «лоб в лоб», то при расчётах можно использовать просто вектора скоростей этих мячей. Т.к. в таком случае траектории движения лежат на одной прямой, а следовательно и вектора скоростей.

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



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

Далее не будем приводить индексы 1 и 2 для обозначений, т.к. методика расчётов для первого и второго мячей абсолютно одинакова, причём в коде программы можно создать одну процедуру, которая будет находить составляющие скорости. В качестве параметров этой процедуре необходимо будет передать проекции основной скорости на оси Х и Y.

Из рисунка видно, что:

; ; .

Осталось найти и .

Модуль основной скорости находится из теоремы Пифагора:

.

Если считать, что вектор основной скорости выходит из начала координат, то минимальный угол между ним и осью Х будет равен: . Однако этот минимальный угол не несёт в себе информации о направлении вектора основной скорости. Например, если , , то   даст результат равный . А на самом деле угол . Смотри следующий рисунок:


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

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

Скорости после столкновения можно найти из двух физических законов: закона сохранения импульса (верхнее уравнение) и закона сохранения энергии (нижнее уравнение):

.

где , массы первого и второго мячей;

      , вектора скоростей первого и второго мячей до столкновения;

      , вектора скоростей первого и второго мячей после столкновения.

Здесь стоит заострить ваше внимание на том, что в уравнениях используются именно вектора скоростей, а не их модули. Т.е. необходимо выбрать какое-то положительное направление, например направление оси Х, и если скорость направлена в ту же сторону, что и эта ось, то скорость берётся со знаком плюс. Если скорость направлена противоположно, то со знаком минус.

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

Так же в законе сохранения энергии двойку в числителе можно сократить. Получаем следующую систему уравнений:

.

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

.

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

, , , ,

то , ,

где дискриминант.

Если , то уравнение имеет два корня и ,, то . Если , то действительных корней нет.

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

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

Возврат к проекциям на оси Х и Y. После того как была определена составляющая скорости участвующая в столкновении нам необходимо перейти обратно к проекциям скорости на оси Х и Y.

Рассмотрим следующий рисунок:


Из рисунка можем записать следующие уравнения, с помощью которых можно вернуться к проекциям скорости на оси Х и Y:

;   .

Угол можем найти с помощью функции Ugol, в качестве параметров необходимо передать и .

Стоит заметить, что необходимо сохранить до этого момента, а так же то, что в результате столкновения он не меняется.

Нахождение координат после столкновения. Мы нашли проекции скоростей на оси Х и Y после столкновения. Теперь нам необходимо изменить координаты мячей. Для этого из координаты, например, необходимо вычесть . Однако тут необходимо сделать оговорку. В самом начале процедуры мы уточняли положение мячей, как бы возвращая их обратно. Так вот, как уже было сказано, теперь мы должны изменить положение центров ровно на столько долей от основной скорости на сколько мы их вернули обратно в самом начале. Поэтому получим:

.

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

Код процедуры  IzmeneniyeKoordinatPriStolknovenii.

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

Так же, здесь стоит сказать, что перед началом вычислений нам необходимо перевести все параметры из целого типа в тип Real, т.к. при этом мы получим более точный результат, и не надо будет  каждый раз вызывать функцию Round. Так же переводя в реальные значения необходимо учесть знак проекций скоростей.

Ещё одно примечание: в данной процедуре можно не предусматривать следующий аспект: если какой-либо мяч находится слишком близко к границе замкнутой области, то он может на неё налететь, и даже перелететь. Однако при небольших скоростях человеческому глазу это не будет заметно, так как процедура Koordinati без проблем изменит положение мяча в следующем проходе цикла движения мячей и вернёт мяч обратно в замкнутую область. При этом часть границы, где побывал мяч, будет стёрта. Для устранения этого «дефекта», в цикл движения мячей необходимо добавить процедуру перерисовывающую границу замкнутой области.

И последнее примечание: для проверки работы получившейся процедуры можно использовать два модуля M1 и M2. Они были написаны при решении последней задачи предыдущего параграфа.


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


Задача.

На основе, приведённых в данном параграфе, теоретических вопросов написать процедуру IzmeneniyeKoordinatPriStolknovenii. Написать программу демонстрирующую работу данной процедуры.


Решение.

program DvaOttalkivayushihsyaMyacha;

uses M1,M2,GraphABC;


//Процедура изменяет координаты мячей при их столкновении

procedure IzmeneniyeKoordinatPriStolknovenii(r1,r2:integer;

                           var x1,y1,x2,y2,Vx1,Vx2,Vy1,Vy2:integer;

                           var NaprX1,NaprX2,NaprY1,NaprY2:byte);

var S:real;//Расстояние между центрами мячей

    Vst1,Vn1,Vst2,Vn2,b:real;//Составляющие скоростей и угол бетта

    x1r,x2r,y1r,y2r,//Реальные координаты центров мячей

    Vx1r,Vy1r,Vx2r,Vy2r:real;//Реальные проекции скоростей после

                             //уточнения


function Ugol(Vx,Vy:real):real;

begin

  if (Vx>0) and (Vy>=0) then Ugol:=ArcTan(Vy/Vx);

  if (Vx=0) and (Vy>0then Ugol:=Pi/2;

  if ((Vx<0) and (Vy>=0)) or ((Vx<0) and (Vy<0)) then Ugol:=Pi+ArcTan(Vy/Vx);

  if (Vx=0) and (Vy<0) then Ugol:=3*Pi/2;

  if (Vx>0) and (Vy<0) then Ugol:=2*Pi+ArcTan(Vy/Vx);

end;


//Процедура определяет составляющую скорости участвующую в столкновении,

//А так же нормальную составляющую

procedure VStolknoveniya(Vx,Vy:real;var Vst,Vn:real);

var V,a,fi:real;//Углы альфа и фи

begin

  V:=Sqrt(Vx*Vx+Vy*Vy);

  a:=Ugol(Vx,Vy);

  fi:=a-b;

  Vn:=V*Sin(fi);

  Vst:=V*Cos(fi);

end;


//Процедура определяет проекции скорости на оси Х и Y

procedure VozvratKProekciyam(Vst,Vn:real;var Vx,Vy:real);

var V,a,g:real;//Основная скорость, углы альфа и гамма

begin

  V:=Sqrt(Vst*Vst+Vn*Vn);

  g:=Ugol(Vst,Vn);

  a:=g+b;

  Vx:=V*Cos(a);

  Vy:=V*Sin(a);

end;


//Тело процедуры IzmeneniyeKoordinatPriStolknovenii

begin

//Определяем произошло ли столкновение

  if x1<>x2 then

    begin

      b:=ArcTan((y1-y2)/(x1-x2));

      S:=abs(x1-x2)/Cos(Abs(b));

    end

   else

    begin

      b:=Pi/2;

      S:=Abs(y2-y1);

    end

  if S >(r1+r2) then exit;

//  pStolknoveniye;


//Переводим параметры в реальные

  if NaprX1=P then Vx1r:=Vx1 else Vx1r:=-Vx1;

  if NaprX2=P then Vx2r:=Vx2 else Vx2r:=-Vx2;

  if NaprY1=N then Vy1r:=Vy1 else Vy1r:=-Vy1;

  if NaprY2=N then Vy2r:=Vy2 else Vy2r:=-Vy2;

  x1r:=x1; x2r:=x2;

  y1r:=y1; y2r:=y2;


//Уточнение координат 

  var cDoley:=30;

  var dVx1:=Vx1r/cDoley;

  var dVx2:=Vx2r/cDoley;

  var dVy1:=Vy1r/cDoley;

  var dVy2:=Vy2r/cDoley;

  var count:integer;//счётчик проходов

  var x1rPred,x2rPred,y1rPred,y2rPred,bPred,SPred:real;//Предыдущие значения

  count:=0;

//Возвращаем мячи обратно

  repeat

    Inc(count);

    x1rPred:=x1r;  x2rPred:=x2r;  y1rPred:=y1r;  y2rPred:=y2r;

    x1r:=x1r-dVx1; x2r:=x2r-dVx2; y1r:=y1r-dVy1; y2r:=y2r-dVy2;

    bPred:=b;      SPred:=S;

    if x1r<>x2r then b:=ArcTan((y1r-y2r)/(x1r-x2r)) else b:=Pi/2;

    if b<>Pi/2 then S:=Abs(x1r-x2r)/Abs(Cos(b)) else S:=Abs(y1r-y2r);

  until (r1+r2)<=S;

//Если мячи вернули слишком, то берём предыдущие значения

  if Abs(S-(r1+r2))>Abs(SPred-(r1+r2)) then

    begin  

      x1r:=x1rPred; x2r:=x2rPred; b:=bPred;

      y1r:=y1rPred; y2r:=y2rPred; Dec(count);

    end;


//Находим составляющую скорости участвующую в столкновении

  if (b<>Pi/2) then

    begin

      VStolknoveniya(Vx1r,Vy1r,Vst1,Vn1);

      VStolknoveniya(Vx2r,Vy2r,Vst2,Vn2)

    end 

   else

    begin

      Vst1:=Vy1r; Vst2:=Vy2r

    end

 

//Изменение составляющей скорости, участвующей в столкновении

  var KA,KB,KC,D,//Коэффициенты для решения квадратного уравнения и дискриминант

      KorenVst11,KorenVst12,//Два корня решения уравнений

      Vst12:real;//Составляющая первой скорости после столкновения

  KA:=r1+r1*r1/r2;

  KB:=-2*r1*(r1*Vst1+r2*Vst2)/r2;

  KC:=(r1*Vst1+r2*Vst2)*(r1*Vst1+r2*Vst2)/r2-(r1*Vst1*Vst1+r2*Vst2*Vst2);

  D:=KB*KB-4*KA*KC;

  if D>0 then

    begin

      KorenVst11:=(-KB+Sqrt(D))/(2*KA);

      KorenVst12:=(-KB-Sqrt(D))/(2*KA);

      if abs(KorenVst11-Vst1)<0.001 then

         Vst12:=KorenVst12

        else

         Vst12:=KorenVst11;

    end

  if D=0 then

    begin

      Vst12:=-KB/2*KA;

    end;

  if D<0 then exit;//Т.к. в таком случае неизвестно как менять координаты


  Vst2:=(r1*Vst1+r2*Vst2-r1*Vst12)/r2;

  Vst1:=Vst12;


//Возвращаемся к проекциям на оси Х и Y

  if (b<>Pi/2) then

    begin

       VozvratKProekciyam(Vst1,Vn1,Vx1r,Vy1r);

       VozvratKProekciyam(Vst2,Vn2,Vx2r,Vy2r);

    end 

   else

    begin

       Vy1r:=Vst1; Vy2r:=Vst2

    end;


//Находим координаты мячей после столкновения

  x1:=Round(x1r+Vx1r*count/cDoley); 

  x2:=Round(x2r+Vx2r*count/cDoley); 

  y1:=Round(y1r+Vy1r*count/cDoley); 

  y2:=Round(y2r+Vy2r*count/cDoley); 

//Возвращаем реальные значения в целые

  Vx1:=Abs(Round(Vx1r));

  Vx2:=Abs(Round(Vx2r));

  Vy1:=Abs(Round(Vy1r));

  Vy2:=Abs(Round(Vy2r));

  if Vx1r<0 then NaprX1:=L else NaprX1:=P;

  if Vy1r<0 then NaprY1:=V else NaprY1:=N;

  if Vx2r<0 then NaprX2:=L else NaprX2:=P;

  if Vy2r<0 then NaprY2:=V else NaprY2:=N;

end;


procedure CiklDvijeniyaMyachey;//Цикл в котором двигаются мячи

begin

  while true do

    begin

      M1.Draw;        M2.Draw;

      DrawRectangle(9,19,631,471);

      Redraw;

      Sleep(30);

      M1.clear;       M2.Clear;

      M1.koordinati;  M2.koordinati; 

      IzmeneniyeKoordinatPriStolknovenii(M1.r,M2.r,M1.x,M1.y,M2.x,M2.y,

            M1.Vx,M2.Vx,M1.Vy,M2.Vy,M1.naprX,M2.naprX,M1.naprY,M2.naprY);

    end;

end;


begin

//Устанавливаем координаты замкнутой области

  M1.X1:=10;M1.X2:=630;M1.Y1:=20;M1.Y2:=470;

  M2.X1:=10;M2.X2:=630;M2.Y1:=20;M2.Y2:=470;

//Устанавливаем начальные параметры движения

  M1.y:=Random(M1.Y1+M1.R,M1.Y2-M1.R);

  M1.x:=Random(M1.X1+M1.R,M1.X2-M1.r);

  M1.naprY:=Random(3,4); M1.naprX:=Random(1,2);

  M1.Vy:=Random(4,20); M1.Vx:=Random(2,10);

 

  M2.y:=Random(M2.Y1+M2.R,M2.Y2-M2.R);

  M2.x:=Random(M2.X1+M2.R,M2.X2-M2.r);

  M2.naprY:=Random(3,4); M2.naprX:=Random(1,2);

  M2.Vy:=Random(4,20); M2.Vx:=Random(2,10);


//Рисуем замкнутую область

  Rectangle(9,19,631,471);

//Отключаем рисование непосредственно на экране

  LockDrawing;

  CiklDvijeniyaMyachey;

end.


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




Предыдущий параграф Назад в содержание Следующий параграф