Объекты 3D

В демонстрации показан мост между двухмерной и трехмерной графикой в HTML5 с помощью WebGL. Цель статьи показать, как нарисовать трехмерные объекты с помощью polygonal mesh. Polygonal mesh представляет собой набор вершин, ребер и граней, которые определяет форму многогранных объектов в 3D компьютерной графики и твердотельного моделирования.

Простые трехмерные объекты

Фасад состоит из треугольников, четырехугольников или других простых многоугольников. Чтобы продемонстрировать это, мы подготовили простые трехмерные объекты — куб и многогранные сферы (с переменным числом граней).

Демо

к меню ↑

HTML

Как обычно (для всех canvas на основе демо) у нас есть очень простая разметка HTML (с одним объектом внутри, canvas):

<html lang="ru" >
    <head>
        <meta charset="utf-8" />
        <title>Каркас 3D объектов в HTML5</title>

        <!-- стили -->
        <link href="css/main.css" rel="stylesheet" type="text/css" />

        <!-- скрипты -->
        <script src="js/meshes.js"></script>
        <script src="js/transform.js"></script>
        <script>
            //var obj = new cube();
            //var obj = new sphere(6);
            var obj = new sphere(16);
        </script>
        <script src="js/main.js"></script>
    </head>
    <body>
        <div class="container">
            <canvas id="scene" height="500" width="700" tabindex="1"></canvas>
            <div class="hint">Используйте кнопки Вверх / Вниз, чтобы управлять прозрачностью объекта</div>
        </div>
    </body>
</html>

Извлечение инициализации объекта, смотрите:

<script>
    //var obj = new cube();
    //var obj = new sphere(6);
    var obj = new sphere(16);
</script>

Это означает, что если нам нужно вывести куб — вы должны раскомментировать первую строку, если вы хотите отобразить сферу с 6 граней — нужно выбрать второй вариант.

к меню ↑

JavaScript

Есть три файла JavaScript (main.js, meshes.js и transform.js), мы публикуем два из них, третий (transform.js) содержит только функций вычислений, связанных с вращением, масштабированием и объектов проекта. Итак, давайте рассмотрим код первого JavaScript:
Файл js/meshes.js

// Получить случайный цвет
  function getRandomColor() {
  var letters = '0123456789ABCDEF'.split('');
  var color = '#';
  for (var i = 0; i < 6; i++ ) {
  color += letters[Math.round(Math.random() * 15)];
  }
  return color;
  }

  // Подготовка объекта
  function prepareObject(o) {
  o.colors = new Array();

  // Составим норм
  o.normals = new Array();
  for (var i = 0; i < o.faces.length; i++) {
  o.normals[i] = [0, 0, 0];

  o.colors[i] = getRandomColor();
  }

  // Составим центр: рассчитать максимальные позиции
  o.center = [0, 0, 0];
  for (var i = 0; i < o.points.length; i++) {
  o.center[0] += o.points[i][0];
  o.center[1] += o.points[i][1];
  o.center[2] += o.points[i][2];
  }

  // Составим расстояния
  o.distances = new Array();
  for (var i = 1; i < o.points.length; i++) {
  o.distances[i] = 0;
  }

  // Вычисляем среднее положение центра
  o.points_number = o.points.length;
  o.center[0] = o.center[0] / (o.points_number - 1);
  o.center[1] = o.center[1] / (o.points_number - 1);
  o.center[2] = o.center[2] / (o.points_number - 1);

  o.faces_number = o.faces.length;
  o.axis_x = [1, 0, 0];
  o.axis_y = [0, 1, 0];
  o.axis_z = [0, 0, 1];
  }

  // Объект куб
  function cube() {

  // Подготовим точки и граней куба
  this.points=[
  [0,0,0],
  [100,0,0],
  [100,100,0],
  [0,100,0],
  [0,0,100],
  [100,0,100],
  [100,100,100],
  [0,100,100],
  [50,50,100],
  [50,50,0],
  ];

  this.faces=[
  [0,4,5],
  [0,5,1],
  [1,5,6],
  [1,6,2],
  [2,6,7],
  [2,7,3],
  [3,7,4],
  [3,4,0],
  [8,5,4],
  [8,6,5],
  [8,7,6],
  [8,4,7],
  [9,5,4],
  [9,6,5],
  [9,7,6],
  [9,4,7],
  ];

  prepareObject(this);
  }

  //  Объект сфера
  function sphere(n) {
  var delta_angle = 2 * Math.PI / n;

  // Составим вершины (точек) сферы
  var vertices = [];
  for (var j = 0; j < n / 2 - 1; j++) {
  for (var i = 0; i < n; i++) {
  vertices[j * n + i] = [];
  vertices[j * n + i][0] = 100 * Math.sin((j + 1) * delta_angle) * Math.cos(i * delta_angle);
  vertices[j * n + i][1] = 100 * Math.cos((j + 1) * delta_angle);
  vertices[j * n + i][2] = 100 * Math.sin((j + 1) * delta_angle) * Math.sin(i * delta_angle);
  }
  }
  vertices[(n / 2 - 1) * n] = [];
  vertices[(n / 2 - 1) * n + 1] = [];

  vertices[(n / 2 - 1) * n][0] = 0;
  vertices[(n / 2 - 1) * n][1] =  100;
  vertices[(n / 2 - 1) * n][2] =  0;

  vertices[(n / 2 - 1) * n + 1][0] = 0;
  vertices[(n / 2 - 1) * n + 1][1] = -100;
  vertices[(n / 2 - 1) * n + 1][2] = 0;

  this.points = vertices;

  // Составим первый слой
  var faces = [];
  for (var j = 0; j < n / 2 - 2; j++) {
  for (var i = 0; i < n - 1; i++) {
  faces[j * 2 * n + i] = [];
  faces[j * 2 * n + i + n] = [];

  faces[j * 2 * n + i][0] = j * n + i;
  faces[j * 2 * n + i][1] = j * n + i + 1;
  faces[j * 2 * n + i][2] = (j + 1) * n + i + 1;
  faces[j * 2 * n + i + n][0] = j * n + i;
  faces[j * 2 * n + i + n][1] = (j + 1) * n + i + 1;
  faces[j * 2 * n + i + n][2] = (j + 1) * n + i;
  }

  faces[j * 2 * n + n - 1] = [];
  faces[2 * n * (j + 1) - 1] = [];

  faces[j * 2 * n + n - 1  ][0] = (j + 1) * n - 1;
  faces[j * 2 * n + n - 1  ][1] = (j + 1) * n;
  faces[j * 2 * n + n - 1  ][2] = j * n;
  faces[2 * n * (j + 1) - 1][0] = (j + 1) * n - 1;
  faces[2 * n * (j + 1) - 1][1] = j * n + n;
  faces[2 * n * (j + 1) - 1][2] = (j + 2) * n - 1;
  }
  for (var i = 0; i < n - 1; i++) {
  faces[n * (n - 4) + i] = [];
  faces[n * (n - 3) + i] = [];

  faces[n * (n - 4) + i][0] = (n / 2 - 1) * n;
  faces[n * (n - 4) + i][1] = i;
  faces[n * (n - 4) + i][2] = i + 1;
  faces[n * (n - 3) + i][0] = (n / 2 - 1) * n + 1;
  faces[n * (n - 3) + i][1] = (n / 2 - 2) * n + i + 1;
  faces[n * (n - 3) + i][2] = (n / 2 - 2) * n + i;
  }

  faces[n * (n - 3) - 1] = [];
  faces[n * (n - 2) - 1] = [];

  faces[n * (n - 3) - 1][0] = (n / 2 - 1) * n;
  faces[n * (n - 3) - 1][1] = n - 1;
  faces[n * (n - 3) - 1][2] = 0;
  faces[n * (n - 2) - 1][0] = (n / 2 - 1) * n + 1;
  faces[n * (n - 2) - 1][1] = (n / 2 - 2) * n;
  faces[n * (n - 2) - 1][2] = (n / 2 - 2) * n + n - 1;

  this.faces=faces;

  prepareObject(this);
  }

В самом начале, мы должны подготовить все точки и фасад наших объектов. Есть 2-ве функции: куб (который генерирует начальные массивы для простого объекта куба) и сферы (для генерации сферы). Гораздо сложнее вычислить все точки и фасады для многогранной сферы. Как только мы получим все эти точки и поверхности, мы должны вычислить другие параметры (например, расстояние, абсолютный центр и три оси).

// Внутренние переменные
  var canvas, ctx;
  var vAlpha = 0.5;
  var vShiftX = vShiftY = 0;
  var distance = -700;
  var vMouseSens = 0.05;
  var iHalfX, iHalfY;

  // Инициализация
  function sceneInit() {
  // Подготовка холста и контекст объектов
  canvas = document.getElementById('scene');
  ctx = canvas.getContext('2d');

  iHalfX = canvas.width / 2;
  iHalfY = canvas.height / 2;

  // Начальный масштаб и перевод
  scaleObj([3, 3, 3], obj);
  translateObj([-obj.center[0], -obj.center[1], -obj.center[2]],obj);
  translateObj([0, 0, -1000], obj);

  // Подключить обработчику событий
  document.onkeydown = handleKeydown;
  canvas.onmousemove = handleMousemove;

  // Основной цикл
  setInterval(drawScene, 25);
  }

  // Обработчик событий OnKeyDown
  function handleKeydown(e) {
  kCode = ((e.which) || (e.keyCode));
  switch (kCode) {
  case 38: vAlpha = (vAlpha <= 0.9) ? (vAlpha + 0.1) : vAlpha; break; // Up key
  case 40: vAlpha = (vAlpha >= 0.2) ? (vAlpha - 0.1) : vAlpha; break; // Down key
  }
  }

  // Обработчик события OnMouseMove
  function handleMousemove(e) {
  var x = e.pageX - canvas.offsetLeft;
  var y = e.pageY - canvas.offsetTop;

  if ((x > 0) && (x < canvas.width) && (y > 0) && (y < canvas.height)) {
  vShiftY = vMouseSens * (x - iHalfX) / iHalfX;
  vShiftX = vMouseSens * (y - iHalfY) / iHalfY;
  }
  }

  // Рисуем основную функцию
  function drawScene() {
  // Очистить холст
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

  // Установить цвет заливки, цвет обводки, толщину линии и глобальной альфа-
  ctx.strokeStyle = 'rgb(0,0,0)';
  ctx.lineWidth = 0.5;
  ctx.globalAlpha= vAlpha;

  // Вертикальный и горизонтальный поворот
  var vP1x = getRotationPar([0, 0, -1000], [1, 0, 0], vShiftX);
  var vP2x = getRotationPar([0, 0, 0], [1, 0, 0], vShiftX);
  var vP1y = getRotationPar([0, 0, -1000], [0, 1, 0], vShiftY);
  var vP2y = getRotationPar([0, 0, 0], [0, 1, 0], vShiftY);
  rotateObj(vP1x, vP2x, obj);
  rotateObj(vP1y, vP2y, obj);

  // Пересчитываем расстояния
  for (var i = 0; i < obj.points_number; i++) {
  obj.distances[i] = Math.pow(obj.points[i][0],2) + Math.pow(obj.points[i][1],2) + Math.pow(obj.points[i][2], 2);
  }

  // Подготовить массив с фасадом треугольников (с расчетом максимальное расстояние для каждой грани)
  var iCnt = 0;
  var aFaceTriangles = new Array();
  for (var i = 0; i < obj.faces_number; i++) {
  var max = obj.distances[obj.faces[i][0]];
  for (var f = 1; f < obj.faces[i].length; f++) {
  if (obj.distances[obj.faces[i][f]] > max)
  max = obj.distances[obj.faces[i][f]];
  }
  aFaceTriangles[iCnt++] = {faceVertex:obj.faces[i], faceColor:obj.colors[i], distance:max};
  }
  aFaceTriangles.sort(sortByDistance);

  // Подготовка массива с прогнозируемым пунктов
  var aPrjPoints = new Array();
  for (var i = 0; i < obj.points.length; i++) {
  aPrjPoints[i] = project(distance, obj.points[i], iHalfX, iHalfY);
  }

  // Нарисовать объект (поверхность)
  for (var i = 0; i < iCnt; i++) {

  ctx.fillStyle = aFaceTriangles[i].faceColor;

  // Начало пути
  ctx.beginPath();

  // Фасад индекс вершины
  var iFaceVertex = aFaceTriangles[i].faceVertex;

  // Переместить в исходное положение
  ctx.moveTo(aPrjPoints[iFaceVertex[0]][0], aPrjPoints[iFaceVertex[0]][1]);

  // И нарисовать три линии (построить треугольник)
  for (var z = 1; z < aFaceTriangles[i].faceVertex.length; z++) {
  ctx.lineTo(aPrjPoints[iFaceVertex[z]][0], aPrjPoints[iFaceVertex[z]][1]);
  }

  // Закрыть путь, и заполнить треугольник
  ctx.closePath();
  ctx.stroke();
  ctx.fill();
  }
  }

  // Функция сортировки
  function sortByDistance(x, y) {
  return (y.distance - x.distance);
  }

  // Инициализация
  if (window.attachEvent) {
  window.attachEvent('onload', sceneInit);
  } else {
  if (window.onload) {
  var curronload = window.onload;
  var newonload = function() {
  curronload();
  sceneInit();
  };
  window.onload = newonload;
  } else {
  window.onload = sceneInit;
  }
  }

Как только страница загружена, мы делаем основную инициализацию (функция sceneInit). Мы создаем canvas и контекст объектов, значит мы выполняем начальный масштаб и переводим наши объекты, которые создали в самом начале (куб или шар). Затем мы придаем обработчикам событий OnKeyDown и OnMouseMove и установим таймер, чтобы сделать нашу основную сцену (функция DrawScene). Не забывайте, что мы можем изменить globalAlpha параметры с нажатием клавиш Вверх/Вниз.

Статья подготовлена для вас коллективом сайта htmlhook.ru
Оригинал статьи: http://www.script-tutorials.com/triangle-mesh-for-3d-objects-in-html5
Перевел: Виктор Клим
Правила использования материалов сайта!