|
||||||||
![]() |
§23. Отталкивающиеся мячи. В предыдущем параграфе мы создали модель мяча, двигающегося в замкнутой области. В задачах для самостоятельного решения мы создали программу, в которой двигаются три различных мяча. В данном параграфе мы разовьём данную тему и заставим мячи отталкиваться друг от друга. Сделаем это для того, что бы профессионально вырасти, а так же данная программа будет использована в последующих параграфах. Для реализации данной задачи необходимо создать отдельную процедуру, которая будет определять, столкнулись мячи или нет, и если столкнулись, то она будет менять их координаты. Данной процедуре дадим следующее имя: IzmeneniyeKoordinatPriStolknovenii. Для её написания нам понадобиться разобрать ряд теоретических вопросов, которые мы рассмотрим отдельно друг от друга по порядку их использования в коде. Соответственно на каждый вопрос у нас должен получиться отдельный блок кода. Написание самого кода процедуры будет предложено сделать в задаче для самостоятельного решения. Попробуете написать его самостоятельно. Если что-то не получится, то можете посмотреть пример решения. Определение произошло ли столкновение. Во-первых, нам необходимо узнать столкнулись мячи или нет. Для этого будем сравнивать расстояние между центрами мячей, которое при столкновении будет меньше либо равно сумме их радиусов. Меньше оно может получиться в результате того, что процедура Koordinati, которая меняет координаты мяча, не проверяет, произошло ли столкновение с другим мячом. И мячи при этом могут как бы налететь друг на друга. Начертим рисунок и определим необходимые нам параметры: Расстояние между центрами можно найти из следующего соотношения:
где Угол
Получаем: На данном рисунке угол Здесь есть один нюанс – если
Уточнение положения мячей. Если столкновение произошло, то необходимо уточнить положение мячей во время их столкновения, т.к. как было уже сказано, они могут как бы налететь друг на друга, как показано на рисунке: Пунктирной линией обозначено положение мячей до уточнения координат, сплошной линией – после уточнения координат. Соответственно:
Аналитически найти центры мячей после уточнения достаточно трудно, поэтому предлагаю искать их методом подбора, как бы возвращая мячи обратно. Для этого разделим скорость перемещения мячей на некоторое количество долей, которое обозначим как
Далее будем вычислять новое положение мячей, вычитая из координат центров эти элементарные перемещения, как бы возвращая мячи обратно, пока расстояние между их центрами не станет больше, чем сумма их радиусов. Т.е. до тех пор пока они на перестанут быть наложенными друг на друга:
При каждом проходе цикла возвращения мячей мы будем увеличивать на единицу переменную count, считая при этом количество долей скорости, на которое мы вернули мячи. Этот параметр понадобиться нам при вычислении координат мячей уже после столкновения. Т.е. мы должны будем переместить мячи на то количество долей скорости, на которое мы вернули их обратно. Может получиться так, что мы вернём мячи слишком далеко, поэтому необходимо хранить значения предыдущих координат, предыдущее значение угла b и предыдущее значение переменной count. После завершения цикла возврата мячей необходимо будет выбрать либо новые координаты, либо предыдущие. Критерием выбора будет служить наименьший модуль разницы между суммой радиусов и расстоянием между центров. Каким образом находится расстояние между центрами мячей, было расписано в предыдущем подразделе. Определение составляющей скорости участвующей в столкновении. Если мячи двигались друг другу на встречу и столкнулись «лоб в лоб», то при расчётах можно использовать просто вектора скоростей этих мячей. Т.к. в таком случае траектории движения лежат на одной прямой, а следовательно и вектора скоростей. Однако если мячи двигаются по траекториям не лежащих на одной прямой, то в столкновении будут участвовать только составляющие этих скоростей, или другими словами проекции основных скоростей на прямую, которая проходит через центры мячей. Далее на рисунке это наглядно продемонстрировано: На рисунке приняты следующие обозначения: Далее не будем приводить индексы 1 и 2 для обозначений, т.к. методика расчётов для первого и второго мячей абсолютно одинакова, причём в коде программы можно создать одну процедуру, которая будет находить составляющие скорости. В качестве параметров этой процедуре необходимо будет передать проекции основной скорости на оси Х и Y. Из рисунка видно, что:
Осталось найти Модуль основной скорости находится из теоремы Пифагора:
Если считать, что вектор основной скорости выходит из начала координат, то минимальный угол между ним и осью Х будет равен: Поэтому для нахождения угла Определение скорости после столкновения. При рассмотрении данного вопроса, для того, что бы сосредоточится на сути, мы будем использовать следующие формулировки: скорости мячей до столкновения и скорости мячей после столкновения, подразумевая составляющие основных скоростей участвующих в столкновении. Скорости после столкновения можно найти из двух физических законов: закона сохранения импульса (верхнее уравнение) и закона сохранения энергии (нижнее уравнение):
где Здесь стоит заострить ваше внимание на том, что в уравнениях используются именно вектора скоростей, а не их модули. Т.е. необходимо выбрать какое-то положительное направление, например направление оси Х, и если скорость направлена в ту же сторону, что и эта ось, то скорость берётся со знаком плюс. Если скорость направлена противоположно, то – со знаком минус. Будем считать, что плотность обоих мячей одинакова, тогда масса будет пропорциональна радиусу, т.е. в место Так же в законе сохранения энергии двойку в числителе можно сократить. Получаем следующую систему уравнений:
После проведения преобразований получим следующие уравнения:
В нижнем уравнении мы имеем квадратное уравнение, в котором если принять следующие обозначения:
то где Если В нашем случае дискриминант будет получаться больше ноля, соответственно мы будем иметь два корня и два варианта решения уравнений. Один из вариантов будет равен скоростям до столкновения, т.е. получим: Получение дискриминанта равного нулю или меньшего нуля мало вероятно, однако, на мой взгляд, необходимо прописать код и для этих случаев. В случае если дискриминант равен нулю мы получим просто один вариант решения уравнений. А вот в случае если дискриминант меньше нуля, предлагаю просто выйти из процедуры, ничего при этом не меняя. Возврат к проекциям на оси Х и Y. После того как была определена составляющая скорости участвующая в столкновении нам необходимо перейти обратно к проекциям скорости на оси Х и Y. Рассмотрим следующий рисунок: Из рисунка можем записать следующие уравнения, с помощью которых можно вернуться к проекциям скорости на оси Х и Y:
Угол Стоит заметить, что Нахождение координат после столкновения. Мы нашли проекции скоростей на оси Х и 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>0) then 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, в которой находится цикл всей программы. Можно было поместить его в тело самой программы, однако я выделил его в отдельную процедуру. Дело в том, что данная программа будет переработана в следующем параграфе, и там необходимо будет, что бы этот цикл находился именно в отдельной процедуре.
|