Объекты 3D

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

Оглавление
  1. Простые трехмерные объекты
  2. HTML
  3. JavaScript

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

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

Демо

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
Перевел: Виктор Клим
Правила использования материалов сайта!