Движение тела только под действием силы тяжести называется свободным падением. Свободно падающее тело движется равноускоренно с постоянным ускорением g, называемым ускорением свободного падения. Величина ускорения g зависит от географической широты местности и от высоты над уровнем моря, например:
В технических расчетах величину ускорения свободного падения обычно принимают равной g = 9.81 м / с2.
Известны разные методы определения g. В данной работе имитируется баллистический гравиметр, принцип действия которого основан на измерении времени прохождения свободно падающего тела через несколько точек, расстояния между которыми известны (рис. 1).
Рис. 1. Принцип устройства баллистического гравиметра
Зная начальную высоту hS, высот расположения датчика и время, которое прошло с начала падения до высоты расположения датчика, несложно вычислить ускорение g.
Число рассчитываемых значений ускорения g равно числу датчиков. Для получения ускорения g полученные по данным датчиков ускорения усредняются.
В работе разрабатывается приложение, имитирующее баллистический гравиметр.
Язык программирования Visual C#, проект Window Forms Application. Графика реализована с помощью библиотек Tao.OpenGL.dll и Tao.Platform.Windows.dll. Эти библиотеки нетрудно найти в интернете.
Для подключения библиотек в Solution Explorer (рис. 2) добавляется ссылка (References - правая кнопка мыши Add Reference).
Рис. 2. Добавление ссылки
Выбираемые ссылки показаны на рис. 3.
Рис. 3. Выбор ссылок Tao.OpenGL.dll и Tao.Platform.Windows.dll
Область графического вывода добавляется в форму после выбора Tollbox - General - simpleOpenGlControl (рис. 4).
Рис. 4. Добавление в форму области графического вывода
В рассматриваемом проекте эта область имеет имя GM (рис. 5)
Рис. 5. Имя области графического вывода
Позволяет задать начальную высоту, число датчиков и высоту их расположения (рис. 6).
Рис. 6. Интерфейс пользователя
Образ падающего тела (зеленый прямоугольник) устанавливается на начальной высоте. После задания числа датчиков и указания высоты их расположения можно нажать на кнопку "Пуск". Тело начинает падать, время прохождения датчика фиксируется в форме в строке задания высоты датчика (рис. 7).
Рис. 7. Можно рассчитать ускорение g
Датчик фиксирует время, когда нижняя граница тела достигает высоты расположения датчика.
По полученным данным рассчитывается ускорение g, значение вводится в поле "Ускорение" и нажимается кнопка "Проверить". В случае удачи выводится сообщение "Верно".
Если установлен флажок "Тест", то приложение само рассчитает ускорение g и отобразит его в поле "Ускорение".
Если нажать на кнопку "О программе", то появится следующая информация:
Проведение эксперимента.Анимация падения тела обеспечивается объектом timer, который в программе срабатывает каждые 10 мс. При срабатывании таймера вызываются процедуры Fall и Draw. Первая моделирует падение тела, а вторая отображает текущую сцену.
Объект Timer добавляется в проект в результате выполнения цепочки ToolBox - Components - Timer. Меню ToolBox.
Если оно отсутствует, можно показать посредством меню VIEW - ToolBox, или нажав Ctrl+Alt+X.
Таймер отображается под формой, рис. 8, хотя и перетаскивается в форму проекта.
Рис. 8. Добавление объекта Timer
Полный код формы приложения:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Collections;
// OpenGL
using Tao.OpenGl;
using Tao.Platform.Windows;
namespace openGL
{
public partial class Form1 : Form
{
float hStrt = 0; // Начальная высота
float hCrrnt = 0; // Текущая высота
float sec = 0; // Время падения
float g = 9.81f; // Ускорение свободного падения, м/с^2
bool click = false;
float W = 50, H = 100; // Параметры матрицы проецирования
public Form1()
{
InitializeComponent();
GM.InitializeContexts();
}
private void GM_Load(object sender, EventArgs e)
{
Gl.glMatrixMode(Gl.GL_PROJECTION); // Текущей стала матрица проецирования
Gl.glLoadIdentity();
Gl.glOrtho(-W, W, 0, H, -1, 1);
Gl.glMatrixMode(Gl.GL_MODELVIEW);
Gl.glLoadIdentity();
Gl.glShadeModel(Gl.GL_FLAT); // Отказ от интерполяции цветов (GL_FLAT)
Gl.glPolygonMode(Gl.GL_FRONT_AND_BACK, Gl.GL_FILL); // Заливка (GL_FILL) полигонов
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
Fall(ref hCrrnt); // Падение тела
Draw(hCrrnt); // Отображение текущей сцены
}
private void Fall(ref float hCrrnt)
{
if (click)
{
int nsV = (int)nSensors.Value; // Число датчиков
float cf = 1.004f; // Поправочный коэффициент
float shV1 = (float)sh1.Value; // Высота установки первого датчика
float shV2 = (float)sh2.Value;
float shV3 = (float)sh3.Value;
float shV4 = (float)sh4.Value;
hCrrnt = hStrt - 0.5f * g * sec * sec;
if (hCrrnt > 0f)
{
sec += timer1.Interval / 1000f; // Время, прошедшее с начала падения тела
// Время падения, замеряемое датчиками
string t = Convert.ToString(Math.Round(sec, 2));
if (hCrrnt > cf * shV1) st1.Text = t;
if (hCrrnt > cf * shV2) st2.Text = t;
if (nsV > 2)
{
if (hCrrnt > cf * shV3) st3.Text = t;
}
else
st3.Text = "";
if (nsV > 3)
{
if (hCrrnt > cf * shV4) st4.Text = t;
}
else
st4.Text = "";
// Обновляем информацию о высоте, скорости и времени падения
crrntH.Text = Convert.ToString(Math.Round(hCrrnt, 2)) + " м";
label1.Text = Convert.ToString(Math.Round(g * sec, 2)) + " м/c";
label2.Text = Convert.ToString(Math.Round(sec, 2)) + " с";
}
else
{
crrntH.Text = "0 м";
sec = 0;
hCrrnt = 0;
click = false;
nlbd(true);
if (checkBoxTest.Checked)
{
float t1 = float.Parse(st1.Text);
float t2 = float.Parse(st2.Text);
float g1 = 2.0f * (hStrt - shV1) / (t1 * t1);
float g2 = 2.0f * (hStrt - shV2) / (t2 * t2);
float gS = g1 + g2;
if (nsV > 2)
{
float t3 = float.Parse(st3.Text);
float g3 = 2.0f * (hStrt - shV3) / (t3 * t3);
gS += g3;
}
if (nsV > 3)
{
float t4 = float.Parse(st4.Text);
float g4 = 2.0f * (hStrt - shV4) / (t4 * t4);
gS += g4;
}
gS = gS / nsV;
textBox3.Text = Convert.ToString(Math.Round(gS, 2));
}
}
}
}
private void Draw(float y2)
{
float d = 5; // Высота прямоугольника-образа падающего тела
// Массивы координат вершин прямоугольника (образа падающего тела)
float[] v21 = { 0, y2 }, v22 = { d, y2 }, v23 = { d, y2 + d }, v24 = { 0, y2 + d };
// Вершины линии нулевой высоты
float[] v112 = { -W, 0 }, v122 = { W, 0 };
Gl.glClearColor(1, 1, 1, 1); // Цвет фона
Gl.glClear(Gl.GL_COLOR_BUFFER_BIT); // Очистка буфера цвета цветом фона
Gl.glColor3f(0, 0, 1); // Текущий цвет синий
// Линия нулевой высоты
Gl.glLineWidth(6);
Gl.glBegin(Gl.GL_LINES);
Gl.glVertex2fv(v112);
Gl.glVertex2fv(v122);
Gl.glEnd();
// Насечки по высоте
int m = (int)(0.9 * H), r = 4;
Gl.glLineWidth(1);
Gl.glColor3f(1, 0, 0); // Текущий цвет красный
Gl.glBegin(Gl.GL_LINES);
while (m > 0)
{
if (r == 4)
{
// Длинная насечка
Gl.glVertex2f(-2, m);
Gl.glVertex2f(2, m);
r = 0;
}
else
{
// Короткая насечка
Gl.glVertex2f(-1, m);
Gl.glVertex2f(1, m);
r++;
}
m -= 1;
}
Gl.glEnd();
Gl.glColor3f(0, 1, 0); // Текущий цвет
Gl.glBegin(Gl.GL_QUADS); // Вывод прямоуольника - образа падающего тела
Gl.glVertex2fv(v21);
Gl.glVertex2fv(v24);
Gl.glVertex2fv(v23);
Gl.glVertex2fv(v22);
Gl.glEnd();
Gl.glFlush(); // Вывод в буфер кадра
GM.Invalidate();
}
private void nlbd(bool tf)
{
textBox3.Enabled = tf;
button1.Enabled = tf;
button3.Enabled = tf;
button4.Enabled = tf;
button6.Enabled = tf;
}
// Кнопка "Проверить"
private void button3_Click(object sender, EventArgs e)
{
float gPol = 0f; // Рассчитанное по результатам измерения ускорение свободного падения
try
{
gPol = float.Parse(textBox3.Text);
}
catch
{
MessageBox.Show("Недопустимо");
return;
}
float eps = 0.011f;
if (Math.Abs(gPol - g) < eps)
MessageBox.Show("Верно");
else
MessageBox.Show("Плохо");
}
// Кнопка "Пуск"
private void button4_Click(object sender, EventArgs e)
{
click = true;
textBox3.Text = "";
nlbd(false);
}
// Кнопка "О программе"
private void button6_Click(object sender, EventArgs e)
{
GM.Visible = !GM.Visible;
label12.Visible = !label12.Visible;
button1.Enabled = !button1.Enabled;
button4.Enabled = !button4.Enabled;
if (!textBox3.Enabled)
button3.Enabled = false;
else if (!GM.Visible)
button3.Enabled = false;
else
button3.Enabled = true;
}
// Обработчик изменения числа датчиков
private void nSensors_ValueChanged(object sender, EventArgs e)
{
int numberCheck = (int)nSensors.Value;
sh4.Enabled = numberCheck > 3;
sh3.Enabled = numberCheck > 2;
}
// Установка диапазонов изменения высоты датчиков. hSV - начальная высота
private void setSensors(float hSV)
{
sh1.Maximum = Convert.ToDecimal(0.9 * hSV);
sh1.Minimum = Convert.ToDecimal(0.7 * hSV);
sh1.Value = sh1.Minimum;
sh2.Maximum = Convert.ToDecimal(0.6 * hSV);
sh2.Minimum = Convert.ToDecimal(0.5 * hSV);
sh2.Value = sh2.Minimum;
sh3.Maximum = Convert.ToDecimal(0.4 * hSV);
sh3.Minimum = Convert.ToDecimal(0.3 * hSV);
sh3.Value = sh3.Minimum;
sh4.Maximum = Convert.ToDecimal(0.2 * hSV);
sh4.Minimum = Convert.ToDecimal(0.1 * hSV);
sh4.Value = sh4.Minimum;
}
// Начальные установки
private void dflt(float hSV)
{
hStrt = hCrrnt = hSV;
textBox3.Enabled = false;
textBox3.Text = "";
button3.Enabled = false;
click = false;
crrntH.Text = Convert.ToString(hSV) + " м";
label1.Text = "0 м/c";
label2.Text = "0 с";
st1.Text = st2.Text = st3.Text = st4.Text = "";
sec = 0;
}
// Обработчик изменения значения начальной высоты
private void hS_ValueChanged(object sender, EventArgs e)
{
float hSV = (float)hS.Value;
setSensors(hSV);
dflt(hSV);
}
private void Form1_Load(object sender, EventArgs e)
{
float hSV = (float)hS.Value;
setSensors(hSV);
dflt(hSV);
}
// Кнопка "Закрыть"
private void button1_Click(object sender, EventArgs e)
{
this.Close();
}
На точность измерений отрицательно влияют дискретность изменения времени и округление. Для устранения этих влияний в процедуре Fall введен поправочный cf. Такой способ корректировки результата весьма груб и в перспективе должен быть замещен более тонкой настройкой.