Balls v5

Материал из Department of Theoretical and Applied Mechanics
Перейти к: навигация, поиск
Виртуальная лаборатория > Динамика взаимодействующих частиц > Balls - версии > Balls v5



Гравитация: mg = ⋅ m ⋅ g0

Сколько шаров помещается по вертикали:  

Конфигурация:

Количество частиц:


Скачать программу: Balls_v5_release.zip

Текст программы на языке JavaScript (разработчики Кривцов Антон, Цветков Денис):

Файл "Balls_v5_release.js"

  1 function MainBalls(canvas, slider_01, text_01) {
  2 
  3     canvas.onselectstart = function () {return false;};     // запрет выделения canvas
  4 
  5     // Предварительные установки
  6 
  7     var context = canvas.getContext("2d");                  // на context происходит рисование
  8     canvas.oncontextmenu = function (e) {return false;};    // блокировка контекстного меню
  9 
 10     var Pi = 3.1415926;                 // число "пи"
 11 
 12     var m0 = 1;                         // масштаб массы
 13     var T0 = 1;                         // масштаб времени (период колебаний исходной системы)
 14     var a0 = 1;                         // масштаб расстояния (диаметр шара)
 15 
 16     var g0 = a0 / T0 / T0;              // масштаб ускорения (ускорение, при котором за T0 будет пройдено расстояние a0)
 17     var k0 = 2 * Pi / T0;               // масштаб частоты
 18     var C0 = m0 * k0 * k0;              // масштаб жесткости
 19     var B0 = 2 * m0 * k0;               // масштаб вязкости
 20 
 21     // *** Задание физических параметров ***
 22 
 23     var Ny = 5;                         // число шаров, помещающихся по вертикали в окно (задает размер шара относительно размера окна)
 24     var m = 1 * m0;                     // масса
 25     var Cwall = 10 * C0;                // жесткость стен
 26     var Cball = 0.1 * Cwall;            // жесткость между частицами
 27     var B = 0.000 * B0;                 // вязкость среды
 28     var Bball = 0.01 * B0;              // вязкость между частицами
 29     var Bwall = 0.03 * B0;              // вязкость на стенках
 30     var mg = 0.25 * m * g0;             // сила тяжести
 31     var r = 0.5 * a0;                   // радиус частицы в расчетных координатах
 32     var K = 0.7;                        // все силы, зависящие от радиуса, ограничиваются значением, реализующимся при r/a = K
 33     var a = 2 * r;                      // равновесное расстояние между частицами
 34     var aCut = 2 * a;                   // радиус обрезания
 35 
 36     // *** Задание вычислительных параметров ***
 37 
 38     var fps = 50;                       // frames per second - число кадров в секунду (качечтво отображения)
 39     var spf = 100;                      // steps per frame   - число шагов интегрирования между кадрами (скорость расчета)
 40     var dt  = 0.045 * T0 / fps;         // шаг интегрирования (качество расчета)
 41 
 42     // Выполнение программы
 43 
 44     var r2 = r * r;                     // ___в целях оптимизации___
 45     var aCut2 = aCut * aCut;            // ___в целях оптимизации___
 46     var a2 = a * a;                     // ___в целях оптимизации___
 47     var D = a2 * Cball / 72;            // энергия связи между частицами
 48     var LJCoeff = 12 * D / a2;          // коэффициент для расчета потенциала Л-Дж
 49 
 50     var Ka = K * a;                     // ___в целях оптимизации___
 51     var K2a2 = K * K * a2;              // ___в целях оптимизации___
 52 
 53     var dNd = null;                     // ссылка на захваченный курсором шар (drag & drop)
 54     var grad;                           // должен ли работать градиент (регулируется в функции setNy())
 55 
 56     this.set_01 = function(c) {mg = c * m * g0;};
 57     this.setNy = function(ny) {
 58         Ny = ny;
 59         if (Ny > 8) {
 60             grad = false;                   // градиент не работает, если Ny > 8
 61             context.fillStyle = "#3070d0";  // цвет, шара
 62         } else
 63             grad = true;
 64     };
 65     this.setNy(Ny);                         // запускаем с уже присвоенным значением, чтобы обновились настройки градиента
 66 
 67     // Запуск новой системы
 68 
 69     // следующие переменные должны пересчитываться каждый раз, когда мы изменяем значение Ny
 70     var scale, w, h;
 71     var rScale13, rScaleShift;
 72     this.newSystem = function() {
 73         scale = canvas.height / Ny / a0;    // масштабный коэффициент для перехода от расчетных к экранным координатам
 74         w = canvas.width / scale;           // ширина окна в расчетных координатах
 75         h = canvas.height / scale;          // высота окна в расчетных координатах
 76 
 77         rScale13 = r * scale * 1.3;         // ___в целях оптимизации___
 78         rScaleShift = r * scale / 5;        // ___в целях оптимизации___
 79 
 80         this.setRandom();                   // задаем случайную конфигурацию
 81     };
 82 
 83     // настройка слайдеров и текстовых полей
 84     slider_01.min = 0;               slider_01.max = 5;
 85     slider_01.step = 0.05;
 86     slider_01.value = mg / m / g0;          // начальное значение ползунка должно задаваться после min и max
 87     text_01.value = mg / m / g0;
 88 
 89     // Работа с мышью
 90 
 91     var mx_, my_;                               // буфер позиции мыши (для расчета скорости при отпускании шара)
 92 
 93     canvas.onmousedown = function(e) {          // функция при нажатии клавиши мыши
 94         var m = mouseCoords(e);                 // получаем расчетные координаты курсора мыши
 95         // цикл в обратную сторону, чтобы захватывать шар, нарисованный "сверху"
 96         // (т.к. цикл рисования идет в обычном порядке)
 97         for (var i = balls.length - 1; i >= 0; i--) {
 98             var b = balls[i];
 99             var rx = b.x - m.x;
100             var ry = b.y - m.y;
101             var rLen2 = rx * rx + ry * ry;              // квадрат расстояния между курсором и центром шара
102             if (rLen2 <= r2) {                          // курсор нажал на шар
103                 if (e.which == 1) {                     // нажата левая клавиша мыши
104                     dNd = b;
105                     dNd.xPlus = dNd.x - m.x;            // сдвиг курсора относительно центра шара по x
106                     dNd.yPlus = dNd.y - m.y;            // сдвиг курсора относительно центра шара по y
107                     mx_ = m.x;    my_ = m.y;
108                     canvas.onmousemove = mouseMove;     // пока клавиша нажата - работает функция перемещения
109                 } else if (e.which == 3)                // нажата правая клавиша мыши
110                     balls.splice(i, 1);                 // удалить шар
111                 return;
112             }
113         }
114 
115         // если не вышли по return из цикла - нажатие было вне шара, добавляем новый
116         if (e.which == 1) {
117             dNd = addNewBall(m.x, m.y, true);   // добавляем шар и сразу захватываем его курсором
118             if (dNd == null) return;            // если шар не добавился (из за стен или других шаров) - возвращаемся
119             dNd.xPlus = 0;  dNd.yPlus = 0;      // держим шар по центру
120             mx_ = m.x;    my_ = m.y;
121             canvas.onmousemove = mouseMove;     // пока клавиша нажата - работает функция перемещения
122         }
123     };
124 
125     document.onmouseup = function(e) {          // функция при отпускании клавиши мыши
126         canvas.onmousemove = null;              // когда клавиша отпущена - функции перемещения нету
127         dNd = null;                             // когда клавиша отпущена - захваченного курсором шара нету
128     };
129 
130     function mouseMove(e) {                     // функция при перемещении мыши, работает только с зажатой ЛКМ
131         var m = mouseCoords(e);                 // получаем расчетные координаты курсора мыши
132         dNd.x = m.x + dNd.xPlus;
133         dNd.y = m.y + dNd.yPlus;
134         dNd.vx = 0.6 * (m.x - mx_) / dt / fps;   dNd.vy = 0.6 * (m.y - my_) / dt / fps;
135         mx_ = m.x;    my_ = m.y;
136     }
137 
138     function mouseCoords(e) {                   // функция возвращает расчетные координаты курсора мыши
139         var m = [];
140         var rect = canvas.getBoundingClientRect();
141         m.x = (e.clientX - rect.left) / scale;
142         m.y = (e.clientY - rect.top) / scale;
143         return m;
144     }
145 
146     // Работа с массивом
147 
148     var balls = [];                             // массив шаров
149     var addNewBall =  function(x, y, check) {
150         // проверка - не пересекается ли новый шар со стенами или уже существующими шарами
151         if (check) {
152             if (x - r < 0 || x + r > w || y - r < 0 || y + r > h) return null;
153             for (var i = 0; i < balls.length; i++) {
154                 var rx = balls[i].x - x;
155                 var ry = balls[i].y - y;
156                 var rLen2 = rx * rx + ry * ry;
157                 if (rLen2 < 4 * r2) return null;
158             }
159         }
160 
161         var b = [];
162 
163         b.x = x;                b.y = y;        // расчетные координаты шара
164         b.fx = 0;               b.fy = mg;      // сила, действующая на шар
165         b.vx = 0;               b.vy = 0;       // скорость
166 
167         balls[balls.length] = b;                // добавить элемент в конец массива
168         return b;
169     };
170 
171     this.setEmpty = function() {balls = [];};   // пустое поле
172 
173     this.setRandom = function() {               // случайная конфигурация
174         balls = [];
175         for (var i = 0; i < 1000; i++)
176             addNewBall(Math.random() * w, Math.random() * h, true);
177     };
178 
179     var sqrt3 = Math.sqrt(3);
180     this.setTriangularLattice = function() {            // задать на поле треугольную решетку
181         balls = [];
182         var center = (w - Math.floor(w / r) * r) / 2;   // сдвиг, решетка будет появляться по середине по горизонтали
183         for (var j = 0; j < Math.floor(h / (sqrt3 * r)); j++)
184             for (var i = 0; i < Math.floor(w / r) - 1; i++)
185                 if ((i + j) % 2 == 0) addNewBall(r * (i + 1) + center, h - r * (1 + sqrt3 * j), false);
186     };
187 
188     // Основной цикл программы
189 
190     function control() {
191         physics();
192         draw();
193     }
194 
195     // Расчетная часть программы
196 
197     function physics() {                        // то, что происходит каждый шаг времени
198         for (var s = 1; s <= spf; s++) {
199 
200             // пересчет сил идет отдельным массивом, т.к. далее будут добавляться силы взаимодействия между шарами
201             for (var i0 = 0; i0 < balls.length; i0++) {
202                 balls[i0].fx = - B * balls[i0].vx;
203                 balls[i0].fy = mg - B * balls[i0].vy;
204             }
205 
206             for (var i = 0; i < balls.length; i++) {
207                 // расчет взаимодействия производится со всеми следующими шарами в массиве,
208                 // чтобы не считать каждое взаимодействие дважды
209                 var b = balls[i];
210                 for (var j = i + 1; j < balls.length; j++) {
211                     var b2 = balls[j];
212                     var rx = b.x - b2.x;   var ry = b.y - b2.y;         // вектор смотрит на первый шар (b)
213                     var r2 = rx * rx + ry * ry;                         // квадрат расстояния между шарами
214                     if (r2 > aCut2) continue;                           // проверка на радиус обрезания
215                     var rLen = (Math.sqrt(r2));
216 
217                     // если расстояние между частицами мало, силы будут посчитаны для K * a
218                     if (r2 < K2a2) {
219                         if (rLen > 0.00001) {                           // проверка, чтобы избежать деления на 0
220                             rx = rx / rLen * Ka;
221                             ry = ry / rLen * Ka;
222                         }
223                         r2 = K2a2;
224                         rLen = Ka;                                      // корень K2a2
225                     }
226 
227                     // сила взаимодействия
228                     var s2 = a2 / r2;         var s4 = s2 * s2;         // ___в целях оптимизации___
229                     var F = LJCoeff * s4 * s4 * (s4 * s2 - 1);          // сила взаимодействия Леннарда-Джонса
230 
231                     // сила внутреннего трения между частицами
232                     if (r2 < a2) {
233                         var vx21 = b.vx - b2.vx;    var vy21 = b.vy - b2.vy;    // вектор смотрит на первый шар (b)
234                         var ex = rx / rLen;         var ey = ry / rLen;
235                         var v = vx21 * ex + vy21 * ey;
236                         F -= F * Bball / rLen * v;
237                     }
238 
239                     // суммируем силы
240                     var Fx = F * rx;        var Fy = F * ry;
241 
242                     b.fx += Fx;             b.fy += Fy;
243                     b2.fx -= Fx;            b2.fy -= Fy;
244                 }
245 
246                 if (b == dNd) continue;  // если шар схвачен курсором - его вз. со стенами и перемещение не считаем
247 
248                 if (b.y + r > h) { b.fy += -Cwall * (b.y + r - h) - Bwall * b.vy; }
249                 if (b.y - r < 0) { b.fy += -Cwall * (b.y - r) - Bwall * b.vy;}
250                 if (b.x + r > w) { b.fx += -Cwall * (b.x + r - w) - Bwall * b.vx; }
251                 if (b.x - r < 0) { b.fx += -Cwall * (b.x - r) - Bwall * b.vx; }
252 
253                 b.vx += b.fx / m * dt;        b.vy += b.fy / m * dt;
254                 b.x += b.vx * dt;             b.y += b.vy * dt;
255             }
256         }
257     }
258 
259     // Рисование
260     function draw() {
261         context.clearRect(0, 0, w * scale, h * scale);      // очистить экран
262         for (var i = 0; i < balls.length; i++){
263             var xS = balls[i].x * scale;           var yS = balls[i].y * scale;
264             if (grad) {
265                 // расчет градиента нужно проводить для каждого шара
266                 var gradient = context.createRadialGradient(xS, yS, rScale13, xS - rScaleShift, yS + rScaleShift, 0);
267                 gradient.addColorStop(0, "#0000bb");
268                 gradient.addColorStop(1, "#44ddff");
269                 context.fillStyle = gradient;
270             }
271 
272             context.beginPath();
273             context.arc(xS, yS, r * scale, 0, 2 * Math.PI, false);
274             context.closePath();
275             context.fill();
276         }
277     }
278 
279     // Запуск системы
280     this.newSystem();
281     setInterval(control, 1000 / fps);
282     // след. функция обновляет информацию о количестве частиц на поле
283     setInterval(function(){document.getElementById('ballsNum').innerHTML = balls.length;}, 1000 / 20);
284 }

Файл "Balls_v5_release.html"

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <meta charset="UTF-8" />
 5     <title>Balls</title>
 6     <script src="Balls_v5_release.js"></script>
 7 </head>
 8 <body>
 9     <canvas id="canvasBalls" width="800" height="600" style="border:1px solid #000000;"></canvas>
10     <br>
11     <div>Гравитация:
12         <input type="range" id="slider_01" style="width: 150px;" oninput="app.set_01(this.value); document.getElementById('text_01').value = this.value;">
13         mg =
14         <input id="text_01" style="width: 5ex;" required pattern="[-+]?([0-9]*\.[0-9]+|[0-9]+)" oninput="
15             // если введено не число - строка не пройдет валидацию по паттерну выше, и checkValidity() вернет false
16             if (!this.checkValidity()) return;
17             app.set_01(this.value);
18             document.getElementById('slider_01').value = this.value;
19         ">
20      ⋅ m ⋅ g0</div><br>
21 
22     <div>Сколько шаров помещается по вертикали:
23         <input type="button" style="width: 30px" name="" onclick="app.setNy(3); app.newSystem();return false;" value="3"/>
24         <input type="button" style="width: 30px" name="" onclick="app.setNy(4); app.newSystem();return false;" value="4"/>
25         <input type="button" style="width: 30px" name="" onclick="app.setNy(5); app.newSystem();return false;" value="5"/>
26         <input type="button" style="width: 30px" name="" onclick="app.setNy(7); app.newSystem();return false;" value="7"/>
27         &nbsp;  <!--знак пробела-->
28         <input type="button" style="width: 30px" name="" onclick="app.setNy(9); app.newSystem();return false;" value="9"/>
29         <input type="button" style="width: 30px" name="" onclick="app.setNy(12); app.newSystem();return false;" value="12"/>
30         <input type="button" style="width: 30px" name="" onclick="app.setNy(15); app.newSystem();return false;" value="15"/>
31     </div><br>
32 
33     <div>Конфигурация:
34         <input type="button" name="" onclick="app.setTriangularLattice(); return false;" value="Треугольная решетка"/>
35         <input type="button" name="" onclick="app.setRandom(); return false;" value="Как попало"/>
36         <input type="button" name="" onclick="app.setEmpty(); return false;" value="Пустое поле"/>
37     </div><br>
38 
39     <div>Количество частиц: <span id="ballsNum"></span></div>
40 
41     <script type="text/javascript">var app = new MainBalls(document.getElementById('canvasBalls'),
42             document.getElementById('slider_01'), document.getElementById('text_01'));</script>
43 </body>
44 </html>