Список работ

Вывод тора средствами html-canvas

Содержание

Подготовка модели

Используя средства html-canvas, выводится полигональная модель тора из трапеций (рис. 1).

Тор из трапеций

Рис. 1. Тор из трапеций

При генерации тора его центр находится в начале системы координат. Ось тора совпадает с осью Y.
При отображении модели выполняется ее сдвиг в центр окна вывода:

cw = canvas.width;
ch = canvas.height;
shiftX = 0.5 * cw; // Смещение по X
shiftY = 0.5 * ch; // Смещение по Y
ctx.translate(shiftX, shiftY);

Вдобавок тор поворачивается относительно осей X и Y.

rotateX3D(45, obj.verts)
rotateY3D(-15, obj.verts)

Функции rotateX3D и rotateY3D см. ниже в коде файла torusMesh.js.
Система координат, в которой задаются вершины тора, является левосторонней (рис. 2).

Левосторонняя система координат

Рис. 2. Левосторонняя система координат

В торе, показанном на рис. 3, грани при ее создании задается красный цвет, если средняя Z-координата его вершин больше нуля, и зеленый - в противном случае.

Тор в левосторонней системе координат

Рис. 3. Тор в левосторонней системе координат

Код задания цвета грани:

fc = obj.faces[i]; // Грань тора
// Средняя Z-координата вершин грани
zAvg = (obj.verts[fc[0]][2] + obj.verts[fc[1]][2] + obj.verts[fc[2]][2] + obj.verts[fc[3]][2]) / 4.0;
if (zAvg > 0)
 obj.colors[i] = 'rgb(255, 0, 0)';
else
 obj.colors[i] = 'rgb(0, 255, 0)';

Сортировка по глубине

Порядок вывода граней определяется Z-координатами их вершин.
В приводимой ниже программе для каждой грани вычисляется средняя значение Z-координат вершин грани. Затем грани сортируются по убыванию этой величиины.
Таким образом, чем больше усредненная Z-координата вершин грани, тем раньше будет выполнен ее вывод.
Массив отсортированных граней формиируется следующим кодом:

// arrSortedFaces - массив граней, отсортированных по глубине
// Элемент массива - это структура с двумя полями: ind и z_avg // ind - это номер грани в массиве obj.faces[]
// z_avg - это средняя Z-координата вершин грани
var arrSortedFaces = new Array();
for(i = 0; i < obj.faces.length; i++) {
 fc = obj.faces[i];
 zAvg = (obj.verts[fc[0]][2] + obj.verts[fc[1]][2] + obj.verts[fc[2]][2] + obj.verts[fc[3]][2]) / 4.0;
 arrSortedFaces[i] = {"ind":i, "z_avg":zAvg};
}
// Сортировка граней по глубине в левосторонней системе координат
arrSortedFaces.sort(function (a, b) { return b.z_avg - a.z_avg });

Анимация вращения тора

Первоначально выполняется поворот тора на 45° относительно оси X и на -15° относительно Y.
Далее с интервалом в 100 миллисекунд тор поворачивается на rotAng градусов относительно оси X.
После полного поворота суммарный угол поворота обнуляется, а координатам вершин тора возвращаются начальные значения.
Они хранятся свойством points созданного объекта.

rotateX3D(rotAng, obj.verts);
rotAngSum += rotAng;
if (rotAngSum > 360) {
 for (i = 0; i < obj.verts.length; i++) {
  obj.verts[i][0] = obj.points[i][0];
  obj.verts[i][1] = obj.points[i][1];
  obj.verts[i][2] = obj.points[i][2];
 }
 rotateX3D(rotXStart, obj.verts);
 rotateY3D(rotYStart, obj.verts);
 rotAngSum = 0;
}

Тор из трапеций

Выводится следующим кодом:

<html>
 <head>
  <script type = "text/javascript" src="torusMesh.js"></script>
  <script type = "text/javascript"> obj = new createTorus(30, 8, 120, 16); </script>
  <script type = "text/javascript" src = "torusMain.js"></script>
 </head>
 <body>
  <canvas id = "scene" height = "400" width = "600"></canvas>
 </body>
</html>

// Файл torusMesh.js
pi = Math.PI;

// Цвета граней
function facesColors(obj) {
 obj.colors = new Array();
 for (i = 0; i < obj.faces.length; i++) {
  r = Math.round(Math.random() * 255);
  g = Math.round(Math.random() * 255);
  b = Math.round(Math.random() * 255);
  obj.colors[i] = 'rgb(' + r + ',' + g + ',' + b + ')';
  showZDir = true;
  if (showZDir) {
   fc = obj.faces[i];
   // Средняя Z-координата вершин грани
   zAvg = (obj.points[fc[0]][2] + obj.points[fc[1]][2] + obj.points[fc[2]][2] + obj.points[fc[3]][2]) / 4.0;
   if (zAvg > 0)
   obj.colors[i] = 'rgb(255, 0, 0)';
   else
   obj.colors[i] = 'rgb(0, 255, 0)';
  }
 }
}
function createTorus(r1, n1, r2, n2) {
 // Центр тора в начале координат - точка (0, 0, 0)
 // Ось тора совпадает с осью Y
 // r1 - радиус сечения тора
 // n1 - число разбиений сечения тора
 // r2 - радиус тора
 // n2 - число сечений тора
 theta = 2 * pi / n1;
 theta2 = 2 * pi / n2;
 torusVerts = [];
 for (j = 0; j < n2; j++) {
  tj = theta2 * j;
  cx = Math.cos(tj);
  sn = Math.sin(tj);
  c = [r2 * cx, 0, r2 * sn];
  for (i = 0; i < n1; i++) {
   ti = theta * i;
   dx = r1 * Math.cos(ti);
   dy = r1 * Math.sin(ti);
   torusVerts.push([c[0] + dx * cx, c[1] + dy, c[2] + dx * sn]);
  }
 }
 faces = [];
 for (j = 0; j < n2 - 1; j++) {
  s = j * n1;
  for (i = 0; i < n1 - 1; i++) {
   si = s + i;
   faces.push([si, si + 1, si + n1 + 1, si + n1]);
  }
  faces.push([s, s + n1 - 1, s + 2 * n1 - 1, s + n1]);
 }
 s = (n2 - 1) * n1;
 for (i = 0; i < n1 - 1; i++) {
  si = s + i;
  faces.push([si, si + 1, i + 1, i]);
 }
 fc = faces[faces.length - 1];
 faces.push([fc[1], fc[2], 0, s]);
 this.points = torusVerts;
 this.verts = torusVerts;
 this.faces = faces;
 facesColors(this);
}
function rotateY3D(theta, verts) {
 ang = pi * theta / 180;
 ct = Math.cos(ang);
 st = Math.sin(ang);
 for (i = 0; i < verts.length; i++) {
  x = verts[i][0];
  y = verts[i][1];
  z = verts[i][2];
  verts[i] = [ct * x + st * z, y, -st * x + ct * z];
 }
}
function rotateX3D(theta, verts) {
 ang = pi * theta / 180;
 ct = Math.cos(ang);
 st = Math.sin(ang);
 for (i = 0; i < verts.length; i++) {
  x = verts[i][0];
  y = verts[i][1];
  z = verts[i][2];
  verts[i] = [x, ct * y - st * z, st * y + ct * z];
 }
}

// Файл torusMain.js
var ctx, cw, ch, shiftX, shiftY;
var rotAng = 1, rotAngSum = 0;
var rotXStart = 45;
var rotYStart = -15;

// Инициализация
function sceneInit() {
 // Подготовка сцены
 canvas = document.getElementById('scene');
 ctx = canvas.getContext('2d');
 ctx.strokeStyle = 'rgb(0,0,0)'; // Цвет линии (черный)
 ctx.lineWidth = 0.5; // Толщина линии
 // Начльное положение тора
 rotateX3D(rotXStart, obj.verts);
 rotateY3D(rotYStart, obj.verts);
 cw = canvas.width;
 ch = canvas.height;
 shiftX = 0.5 * cw; // Смещение по X
 shiftY = 0.5 * ch; // Смещение по Y
 ctx.translate(shiftX, shiftY);
 // 100 миллисекунд - частота обновлений сцены
 setInterval(drawScene, 100);
}
// Вывод сцены
function drawScene() {
 rotateX3D(rotAng, obj.verts);
 rotAngSum += rotAng;
 if (rotAngSum > 360) {
  for (i = 0; i < obj.verts.length; i++) {
   obj.verts[i][0] = obj.points[i][0];
   obj.verts[i][1] = obj.points[i][1];
   obj.verts[i][2] = obj.points[i][2];
  }
  rotateX3D(rotXStart, obj.verts);
  rotateY3D(rotYStart, obj.verts);
  rotAngSum = 0;
 }
 // Очистка области вывода
 ctx.translate(-shiftX, -shiftY);
 ctx.clearRect(0, 0, cw, ch); // Очистка области вывода
 ctx.translate(shiftX, shiftY);
 // arrSortedFaces - массив граней, отсортированных по глубине
 // Элемент массива - это структура с двумя полями: ind и z_avg
 // ind - это номер грани в массиве obj.faces[]
 // z_avg - это средняя Z-координата вершин грани
 var arrSortedFaces = new Array();
 for(i = 0; i < obj.faces.length; i++) {
  fc = obj.faces[i];
  zAvg = (obj.verts[fc[0]][2] + obj.verts[fc[1]][2] + obj.verts[fc[2]][2] + obj.verts[fc[3]][2]) / 4.0;
  arrSortedFaces[i] = {"ind":i, "z_avg":zAvg};
 }
 // Сортировка граней по глубине в левосторонней системе координат
 arrSortedFaces.sort(function (a, b) { return b.z_avg - a.z_avg });
 // Вывод объекта
 for (i = 0; i < obj.faces.length; i++) {
  // Начало вывода
  ctx.beginPath();
  // Выводимая грань
  fc = obj.faces[arrSortedFaces[i].ind];
  // Вывод грани (трапеции)
  for (j = 0; j < fc.length; j++) {
   vert = fc[j]; // Вершина грани
   ctx.lineTo(obj.verts[vert][0], obj.verts[vert][1]);
  }
  ctx.closePath();
  ctx.fillStyle = obj.colors[i];
  ctx.stroke(); // Вывод линий
  ctx.fill(); // Заливка граней
 }
}
window.onload = sceneInit;

Список работ

Рейтинг@Mail.ru