Цель этой статьи — показать практическое использование некоторых тригонометрических функций в программировании.

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

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

На рисунке 1 показана окружность радиуса r = 1 с центром в точке O. Произвольная точка A заданная на окружности образует дугу AB и угол α, численно (в радианах) равных друг другу.
Проекция точки A на ось абсцисс (x) называется косинусом угла α или дуги AB.
Проекция точки A на ось ординат (y) называется синусом угла α или дуги AB.
Если рассматривать полученные треугольники AxOA или AyOA, то можно говорить о следующих отношениях.
Косинусом острого угла называется отношение прилежащего катета к гипотенузе.
Синусом острого угла называется отношение противолежащего катета к гипотенузе.
Углы в системе координат откладываются против часовой стрелки.
Так как длина всей окружности равна 2πr, а радиус нашей окружности равен единице, то длина окружности в нашем случае равна . То есть, угол в 180 градусов соответствует π радиан. Из этого мы можем вывести формулу: 1 радиан = 180 / π.
Более подробную информацию Вы можете получить, например, здесь.

Результатом нашего проекта должны стать часы, показанные на рисунке 2.

Итак, далее я приведу полный исходный текст программы, а затем прокомментирую непонятные моменты. Программа написана с использованием библиотеки Qt.

Файл Clock.h

#ifndef CLOCK_H
#define CLOCK_H

#include <QtGui/QWidget>
#include <QtCore/QPoint>

class CClock : public QWidget
{
Q_OBJECT
public:
explicit CClock(QWidget *parent = 0);

public slots:
void onTimer();

protected:
virtual void paintEvent(QPaintEvent * event);

private:
QPoint rotatePoint(const QPoint & point, int degree, double radius);
};

#endif // CLOCK_H

Файл Clock.cpp

#include "Clock.h"
#include <QtGui/QPainter>
#include <QtCore/QTime>
#include <QtCore/QTimer>
#include <cmath>
//===================================================================

CClock::CClock(QWidget *parent) :
QWidget(parent)
{
setMinimumSize(500, 500);
QTimer * timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(onTimer()));
timer->start(1000);
}
//===================================================================

void CClock::onTimer()
{
QWidget::update();
}
//===================================================================

void CClock::paintEvent(QPaintEvent * /*event*/)
{
//
// константы для отрисовки.
//

// цвет окружности.
static const QColor circle_color(88, 88, 88, 255);
// цвет засечек часов.
static const QColor stroke_hour_color(19, 66, 91, 255);
// цвет засечек минут.
static const QColor stroke_min_color(100, 120, 120, 255);
// цвет часовой стрелки.
static const QColor hour_hand_color(60, 65, 65, 255);
// цвет минутной стрелки.
static const QColor min_hand_color(90, 105, 105, 255);
// цвет секундной стрелки.
static const QColor sec_hand_color(125, 150, 150, 255);

//
// константы метрик и координат.
//

// ширина линии для отрисовки окружности.
static const int circle_line_width = 5;
// ширина линии штриха (деление).
static const int stroke_line_width = 3;
// длина штриха часов.
static const int stroke_hour_length = 10;
// длина штриха минут.
static const int stroke_min_length = 5;

// зазор меду окружностью и засечками.
static const int spacing = 10;
// абсцисса центра окружности в оконных координатах.
const double circle_center_x = width() / 2;
// ордината центра окружности в оконных координатах.
const double circle_center_y = height() / 2;
// радиус окружности.
const double circle_radius = (circle_center_x < circle_center_y ?
circle_center_x : circle_center_y) - spacing - circle_line_width;

// радиус, описываемый часовой стрелкой.
const double hour_hand_radius = circle_radius / 2;
// радиус, описываемый минутной стрелкой.
const double min_hand_radius = hour_hand_radius + circle_radius / 6;
// радиус, описываемый секундной стрелкой.
const double sec_hand_radius = hour_hand_radius + circle_radius / 4;

static const double hour_hand_tail = 20.0; // длина хвоста часовой стрелки.
static const double min_hand_tail = 30.0; // длина хвоста минутной стрелки.
static const double sec_hand_tail = 40.0; // длина хвоста секундной стрелки.

// половина основания часовой стрелки.
static const double hour_hand_half_found = 15.0;
// половина основания минутной стрелки.
static const double min_hand_half_found = 10.0;
// половина основания секундной стрелки.
static const double sec_hand_half_found = 5.0;

// радиус, описываемый крайними точками хвоста часовой стрелки.
const double hour_hand_tail_radius = ::sqrt(::pow(hour_hand_tail, 2) +
::pow(hour_hand_half_found, 2));
// радиус, описываемый крайними точками хвоста минутной стрелки.
const double min_hand_tail_radius = ::sqrt(::pow(min_hand_tail, 2) +
::pow(min_hand_half_found, 2));
// радиус, описываемый крайними точками хвоста секундной стрелки.
const double sec_hand_tail_radius = ::sqrt(::pow(sec_hand_tail, 2) +
::pow(sec_hand_half_found, 2));


// координаты часовой стрелки в начальном состоянии (указывает на 3).
//
// конец стрелки.
const QPoint hour_hand_a0(hour_hand_radius, 0);
// координаты первой точки основания часовой стрелки.
const QPoint hour_hand_b0(-hour_hand_tail, hour_hand_half_found);
// координаты второй точки основания часовой стрелки.
const QPoint hour_hand_c0(-hour_hand_tail, -hour_hand_half_found);

// координаты минутной стрелки в начальном состоянии (указывает на 3).
//
// конец стрелки.
const QPoint min_hand_a0(min_hand_radius, 0);
// координаты первой точки основания минутной стрелки.
const QPoint min_hand_b0(-min_hand_tail, min_hand_half_found);
// координаты второй точки основания минутной стрелки.
const QPoint min_hand_c0(-min_hand_tail, -min_hand_half_found);

// координаты секундной стрелки в начальном состоянии (указывает на 3).
//
// конец стрелки.
const QPoint sec_hand_a0(sec_hand_radius, 0);
// координаты первой точки основания секундной стрелки.
const QPoint sec_hand_b0(-sec_hand_tail, sec_hand_half_found);
// координаты второй точки основания секундной стрелки.
const QPoint sec_hand_c0(-sec_hand_tail, -sec_hand_half_found);


//
// рисуем.
//

QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);

// устанавливаем новое начало координат.
painter.translate(circle_center_x, circle_center_y);

// копируем перо из устройства рисования.
QPen pen = painter.pen();

// рисуем окружность.
//
// устанавливаем ширину и цвет линии пера.
pen.setWidth(circle_line_width);
pen.setColor(circle_color);
// устанавливаем перо в устройство рисования.
painter.setPen(pen);
// рисуем.
painter.drawEllipse(QPoint(0, 0), static_cast<int>(circle_radius),
static_cast<int>(circle_radius));

// рисуем 60 засечек.
//
// крайняя к окружности точка.
const QPoint p1(circle_radius - circle_line_width - spacing, 0);
// вторая точка для штрихов часов.
const QPoint p2(p1.x() - stroke_min_length, 0);
// вторая точка для штрихов минут.
const QPoint p3(p1.x() - stroke_hour_length, 0);
pen.setWidth(stroke_line_width);
pen.setColor(stroke_min_color);
painter.setPen(pen);
for(int i = 0; i < 60; i++)
{
if(i % 5 == 0)
{
pen.setColor(stroke_hour_color);
painter.setPen(pen);
painter.drawLine(p1, p3);
pen.setColor(stroke_min_color);
painter.setPen(pen);
}
else
{
painter.drawLine(p1, p2);
}
painter.rotate(6.0);
}

// рисуем стрелки.
//
QPoint points[3]; // точки для рисования.
// узнаём текущее время.
QTime cur_time = QTime::currentTime();
// часовая стрелка.
//
// угол часовой стрелки от начального состояния (3 часа).
const int hour_beta = 90 - (cur_time.hour() * 30
+ cur_time.minute() / 2);
points[0] = rotatePoint(hour_hand_a0, hour_beta, hour_hand_radius);
points[1] = rotatePoint(hour_hand_b0, hour_beta, -hour_hand_tail_radius);
points[2] = rotatePoint(hour_hand_c0, hour_beta, -hour_hand_tail_radius);
painter.setPen(Qt::NoPen);
painter.setBrush(hour_hand_color);
painter.drawConvexPolygon(points, 3);
// минутная стрелка.
//
// угол минутной стрелки от начального состояния (3 часа).
const int min_beta = 90 - cur_time.minute() * 6;
points[0] = rotatePoint(min_hand_a0, min_beta, min_hand_radius);
points[1] = rotatePoint(min_hand_b0, min_beta, -min_hand_tail_radius);
points[2] = rotatePoint(min_hand_c0, min_beta, -min_hand_tail_radius);
painter.setBrush(min_hand_color);
painter.drawConvexPolygon(points, 3);
// секундная стрелка.
//
// угол секундной стрелки от начального состояния (3 часа).
const int sec_beta = 90 - cur_time.second() * 6;
points[0] = rotatePoint(sec_hand_a0, sec_beta, sec_hand_radius);
points[1] = rotatePoint(sec_hand_b0, sec_beta, -sec_hand_tail_radius);
points[2] = rotatePoint(sec_hand_c0, sec_beta, -sec_hand_tail_radius);
painter.setBrush(sec_hand_color);
painter.drawConvexPolygon(points, 3);

// переварачиваем всё вверх ногами, так как на экране направление
// оси ординат сверху вниз.
painter.rotate(180);
}
//===================================================================

QPoint CClock::rotatePoint(const QPoint & point, int degree, double radius)
{
static const double pi = 3.14159265359; // число Пи.

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

// старый угол.
double old_degree_rad = ::asin(point.y() / radius);
// переводим угол в радианы.
double degree_rad = degree * pi / 180.0;
// новый угол.
double new_degree_rad = old_degree_rad - degree_rad;

return QPoint(::cos(new_degree_rad) * radius, ::sin(new_degree_rad) * radius);
}
//===================================================================

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

#include <QtGui/QApplication>
#include "Clock.h"

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
CClock w;
w.show();
return a.exec();
}

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

Мы определяем координаты для всех стрелок в начальном состоянии. За начальное состояние
я принял угол равный 0 градусов. На рисунке 3 показано начальное состояние стрелки, соответствующее треугольнику AoBoCo. Стрелку мы условно разделяем на две части: собственно стрелку и хвост. Хвост — это та часть, которая находится с обратной стороны точки вращения стрелки. Более подробно хвостовая часть показана на рисунке 4.

На рисунке 4 мы видим треугольники OBoDo и OCoDo. Из любого из них мы можем легко найти радиус окружности по теореме Пифагора.

Итак, координаты стрелки в начальном состоянии таковы:
Ao: x равен длине отрезка OAo, у равен нулю.
Bo: x равен длине отрезка ODo, взятой с отрицательным знаком, y равен длине отрезка DoBo, взятой с отрицательным знаком.
Co: x равен длине отрезка ODo, взятой с отрицательным знаком, y равен длине отрезка DoCo.

Так как нас больше всего интересуют стрелки, то засечки я нарисовал самым простым образом — поворачивая холст. Следует отметить, что не все библиотеки предоставляют удобные методы поворота холста.

Далее мы вычисляем угол, на который следует повернуть стрелку из положения по умолчанию и поворачиваем каждую из точек стрелки на заданный угол.