Зачем мы проходили тригонометрию в школе
medium

Зачем мы проходили тригонометрию в школе

Чтобы делать классные 3D-игры

Помните, когда мы в школе проходили все эти синусы, косинусы и углы, у всех был вопрос — а как нам это пригодится в жизни? Тогда казалось, что это нужно только учёным и математикам, но на самом деле всё трёхмерное моделирование и 3D-игры — это та самая школьная тригонометрия.

При чём тут 3D

В одной из статей мы рассказывали про 3D-игру Doom. Одной из особенностей этой игры было то, что у неё не было настоящей трёхмерной графики — движок оперировал двумерными моделями. Но на экране казалось, что это настоящая трёхмерность: 

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

Всё дело — в угле зрения. От него зависит, каким будет казаться объект, маленьким или большим, и как он изменит свой размер на разных расстояниях.

Угол зрения

Чтобы понять, что такое угол зрения, нам нужно знать всего две величины — высоту объекта и расстояние до него:

Зачем мы проходили тригонометрию в школе

Сейчас наблюдателю наш красный объект кажется далёким, потому что угол зрения — маленький. Но если мы подвинем этот объект поближе к наблюдателю, то угол зрения станет больше:

Зачем мы проходили тригонометрию в школе

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

И вот тут нам пригождается школьная тригонометрия — все эти синусы, косинусы и тангенсы. Благодаря им мы сможем рассчитывать нужный размер предметов на экране в зависимости от того, какой угол получится между нашей виртуальной камерой и разницей в высоте предмета. Это позволит нам смоделировать разное расстояние до предметов, как будто у двумерного плоского экрана появляется третье измерение — глубина.

Ненастоящее 3D в DOOM

Из школьной программы мы помним формулу тангенса:

tg α = высота / расстояние, где α — наш угол зрения.

Единственное, что отличает на экране далёкие предметы от близких, — это высота, поэтому мы можем регулировать её так:

высота = расстояние × tg α.

Если расстояние будет равно единице, то высота объекта — это просто будет тангенс угла зрения альфа. 

А раз так, то мы можем это использовать для эффекта 3D:

  1. Берём любой объект
  2. Выясняем, какой будет угол зрения для этого объекта, если подойти к нему вплотную, насколько позволяет игровой движок.
  3. Теперь если нам нужно показать, что мы отходим от объекта, то мы просто уменьшаем угол зрения. С ним уменьшится и тангенс, и высота объекта на экране.
  4. То же самое и с приближением — чтобы показать на экране, что мы как будто подходим к объекту, мы просто увеличиваем угол зрения, а с ним увеличивается и высота. Кажется, что мы подошли поближе.

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

Настоящее 3D 

В настоящем 3D синусы и косинусы нужны, чтобы посчитать новые координаты всех сторон движущегося объекта. Штука в том, что нам нужно перенести объёмный трёхмерный объект на плоский двумерный экран — сделать проекцию.

Зачем мы проходили тригонометрию в школе

На плоской поверхности у нас есть только две координаты — X и Y, поэтому нам нужны формулы, которые помогут учесть третью координату Z и нарисовать объект так, чтобы он выглядел объёмным:

x':=x*sin(угол между плоскостью XOY и отрезком OZ) ;

y':=y*cos(угол между плоскостью XOY и отрезком OZ) ;

Чтобы трёхмерные объекты на экране можно было двигать, тоже используют тригонометрию. Например, если у нас есть трёхмерный кубик, у вершин которого есть координаты x, y и z, то, чтобы его повернуть на угол L по оси X, нужно сделать такое для каждой вершины:

x'=x;

y':=y*cos(L)+z*sin(L) ;

z':=-y*sin(L)+z*cos(L) ;

Здесь x', y' и z' — новые координаты вершины. Если мы нарисуем кубик с такими новыми координатами каждой вершины, то будет казаться, что мы его немножко повернули.

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

Чтобы показать, как это работает, давайте сделаем HTML-страницу, которая нарисует нам вращающийся кубик. Мы прокомментировали каждую строку кода, чтобы вы тоже смогли понять, что там происходит. Это настоящее 3D, для которого тоже нужна школьная тригонометрия :-)

Сохраните себе этот код как HTML-документ и откройте в браузере, чтобы посмотреть на вращение кубика. Если не знаете, как это сделать, — вот подробный гайд в помощь: 

Спасательный круг для тех, кто начинает писать на JavaScript

<!DOCTYPE html>
<html>
<head>
  <title>3D-кубик</title>
</head>
<body>

  <!-- пусть кубик вращается по центру страницы-->
  <div align="center">
    <!-- готовим область для рисования — 200 на 200 пикселей  -->
    <canvas id="cubeCanvas" width="200" height="200"></canvas>
  </div>

  <!-- скрипт, который нарисует нам кубик -->
  <script type="text/javascript">

  // весь скрипт — одна большая функция
  (function () {

   // переменная, через которую будем работать с областью для рисования 
   var canvas = document.getElementById("cubeCanvas");
   // размер кубика — это минимальное значение высоты или ширины холста
   var size = Math.min (canvas.width,canvas.height);
   // холст для рисования — двухмерный
   var g = canvas.getContext("2d");

   // массив с координатами вершин кубика по осям X, Y и Z
   var nodes = 
    [[-1, -1, -1], [-1, -1, 1], [-1, 1, -1], [-1, 1, 1],
     [1, -1, -1], [1, -1, 1], [1, 1, -1], [1, 1, 1]];
   // а эта переменная отвечает за грани — какие вершины нужно соединить между собой по номерам, чтобы в итоге получился кубик. [0,1] означает, что будет линия между нулевой и первой вершиной, [1,3] — линия между первой и третьей вершиной и так далее
   var edges = 
    [[0, 1], [1, 3], [3, 2], [2, 0], [4, 5], [5, 7], [7, 6],
     [6, 4], [0, 4], [1, 5], [2, 6], [3, 7]];
   
   // если нужно сделать кубик больше или меньше — используем функцию масштабирования 
   function scale (factor0, factor1, factor2) { 
    // берём каждую грань
    nodes.forEach(function (node) {
      // и умножаем каждую её координату на размер масштаба
     node[0] *= factor0; node[1] *= factor1; node[2] *= factor2;
    });
   }
   
   // вращаем кубик и получаем новые координаты для каждой вершины, а в функцию передаём углы вращения по осям X и Y
   function rotateCuboid (angleX, angleY) { 

    // запоминаем значения синусов и косинусов для каждого угла вращения
    var sinX = Math.sin(angleX);
    var cosX = Math.cos(angleX);
    var sinY = Math.sin(angleY);
    var cosY = Math.cos(angleY);

    // для каждой вершины — пересчитываем координаты после поворота
    nodes.forEach(function (node) {

     // помещаем значения координат вершины в отдельные переменные
     var x = node[0]; var y = node[1]; var z = node[2];

     // а вот тут происходит сама магия вращения — мы с помощью синусов и косинусов получаем новые координаты для каждой вершины куба
     node[0] = x * cosX - z * sinX;
     node[2] = z * cosX + x * sinX;
     z = node[2];
     node[1] = y * cosY - z * sinY;
     node[2] = z * cosY + y * sinY;
    });
   }
   
   // эта функция отрисовывает кубик по текущим координатам вершин
   function drawCuboid () {

    // берём двухмерный холст, который мы заводили раньше
    g.save();

    // очищаем его
    g.clearRect(0, 0, canvas.width, canvas.height); 

    // помещаем наш будущий кубик в центр координат
    g.translate(canvas.width / 2, canvas.height / 2); 

    // рисовать будем чёрным
    g.strokeStyle = "#000000"; 

    // начинаем рисовать по линиям
    g.beginPath();

    // берём каждую грань
    edges.forEach(function (edge) {
     // запоминаем координаты, которые нужно отрисовать
     var p1 = nodes[edge[0]];
     var p2 = nodes[edge[1]];

     // идём на начальную точку
     g.moveTo(p1[0], p1[1]);
     // и виртуально соединяем её линией со второй точкой и так делаем для каждой грани
     g.lineTo(p2[0], p2[1]);
    });

    // нарисовали — выключаем режим рисования линий
    g.closePath();

    // отрисовываем полностью сразу весь кубик, который у нас получился с помощью виртуальных линий
    g.stroke();

    // восстанавливаем холст до начального состояния — убираем с него всё, чтобы подготовиться к рисованию следующего кадра
    g.restore();
   }
   
   // выбираем масштаб — уменьшим кубик в 4 раза
   scale (size/4, size/4, size/4); 

   // здесь задаём начальные углы наклона кубика по осям X и Y. Попробуйте их поменять и посмотреть, что получится
   rotateCuboid (Math.PI / 3, Math.atan(Math.sqrt(10))); 

   // основной цикл, который отвечает за анимацию вращения
   setInterval( function() {

    // поворачиваем наш кубик на выбранные углы
    rotateCuboid (0.02, 0.02); 
    // отрисовываем кубик
    drawCuboid ();
    // интервал между кадрами — 10 миллисекунд
   }, 10);

   // закончилась главная функция
  })(); 

  </script>

</body>
</html>

Зачем мы проходили тригонометрию в школе
Настоящий трёхмерный кубик на JavaScript

Что дальше

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

Обложка:

Алексей Сухов

Корректор:

Ирина Михеева

Вёрстка:

Мария Дронова

Соцсети:

Юлия Зубарева

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Вам может быть интересно
medium