Список работ

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

Содержание

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

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

Сферы с треугольными гранями и из трапеций

Рис. 1. Сферы с треугольными гранями и из трапеций

При генерации сферы ее центр находится в начале системы координат canvas – в его верхнем левом углу.
При отображении модели выполняется ее сдвиг в центр окна вывода:

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

В случае сферы из треугольников выводятся только видимые грани.
Вывод граней выполняется с задержкой. Интервал между двумя последовательно выводимыми гранями задается функцией setInterval:

setInterval(drawScene, 100);

После вывода всех видимых граней сцена очищается:

ctx.clearRect(0, 0, cw, ch); // Очистка области вывода

При создании сферы ее граням случайным образом назначается цвет:

obj.colors[i] = 'rgb(' + r + ',' + g + ',' + b + ')';

где r, g и b – это случайно выбираемые числа из отрезка [0, 255].

В случае сферы из трапеций выводятся все грани. Перед выводом сфера поворачивается вокруг осей Y и X на 15°.
Для этого в код добавляются обеспечивающие поворот функции rotateY3D и rotateX3D:

function rotateY3D(theta, vertices) {
 ang = Math.PI * theta / 180;
 cs = Math.cos(ang);
 sn = Math.sin(ang);
 for (i = 0; i < vertices.length; i++) {
  x = vertices[i][0];
  y = vertices[i][1];
  z = vertices[i][2];
  vertices[i] = [cs * x + sn * z, y, -sn * x + cs * z];
 }
}

function rotateX3D(theta, vertices) {
 ang = Math.PI * theta / 180;
 cs = Math.cos(ang);
 sn = Math.sin(ang);
 for (i = 0; i < vertices.length; i++) {
  x = vertices[i][0];
  y = vertices[i][1];
  z = vertices[i][2];
  vertices[i] = [x, cs * y - sn * z, sn * y + cs * z];
 }
}

Сфера с треугольными гранями

Приводимый ниже код обеспечивает вывод следующей сцены:

<html
 <head>
  <script type = "text/javascript" src="meshSph.js"></script>
  <script type = "text/javascript"> obj = new sphere(16); </script>
  <script type = "text/javascript" src="mainSph.js"></script>
 </head>
 <body>
  <canvas id = "scene" height = "400" width = "600"></canvas>
 </body>
</html>

// Файл meshSph.js
// Цвета граней
function facesColors(o) {
 obj.colors = new Array();
 for (i = 0; i < o.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 + ')';
 }
}
// Сфера
function sphere(n) {
 delta_angle = 2 * Math.PI / n;
 R = 100; // Радиус сферы
 // Вершины сферы
 vertices = [];
 for (j = 0; j < n / 2 - 1; j++) {
  j1 = (j + 1) * delta_angle;
  sj1 = R * Math.sin(j1);
  cj1 = R * Math.cos(j1);
  jn = j * n;
  for (i = 0; i < n; i++) {
   i1 = i * delta_angle;
   jni = jn + i
   vertices[jni] = [];
   vertices[jni][0] = sj1 * Math.cos(i1); // X
   vertices[jni][1] = cj1; // Y
   vertices[jni][2] = sj1 * Math.sin(i1); // Z
  }
 }
 n2 = (n / 2 - 1) * n;
 // Дно сферы (начало координат в верхнем левом углу canvas; ось Y направлена вниз)
 vertices[n2] = [];
 vertices[n2][0] = 0;
 vertices[n2][1] = R;
 vertices[n2][2] = 0;
 // Верхушка сферы
 n21 = n2 + 1;
 vertices[n21] = [];
 vertices[n21][0] = 0;
 vertices[n21][1] = -R;
 vertices[n21][2] = 0;
 this.points = vertices;
 // Грани сферы
 faces = [];
 for (j = 0; j < n / 2 - 2; j++) {
  jn = j * n;
  jn2 = 2 * jn;
  jn1 = (j + 1) * n;
  for (i = 0; i < n - 1; i++) {
   j2 = jn2 + i;
   j2n = j2 + n;
   jni = jn + i;
   j1ni = jn1 + i;
   faces[j2] = [];
   faces[j2n] = [];
   faces[j2][0] = jni;
   faces[j2][1] = jni + 1;
   faces[j2][2] = j1ni + 1;
   faces[j2n][0] = jni;
   faces[j2n][1] = j1ni + 1;
   faces[j2n][2] = j1ni;
  }
  j2nn = j * 2 * n + n - 1;
  faces[j2nn] = [];
  faces[j2nn][0] = (j + 1) * n - 1;
  faces[j2nn][1] = (j + 1) * n;
  faces[j2nn][2] = j * n;
  j12n = (j + 1) * 2 * n - 1;
  faces[j12n] = [];
  faces[j12n][0] = (j + 1) * n - 1;
  faces[j12n][1] = j * n + n;
  faces[j12n][2] = (j + 2) * n - 1;
 }
 for (i = 0; i < n - 1; i++) {
  // Дно сферы
  n2 = (n / 2 - 1) * n;
  nn4 = n * (n - 4) + i;
  faces[nn4] = [];
  faces[nn4][0] = n2;
  faces[nn4][1] = i;
  faces[nn4][2] = i + 1;
  // Верхушка сферы
  n22 = (n / 2 - 2) * n + i;
  nn3 = n * (n - 3) + i;
  faces[nn3] = [];
  faces[nn3][0] = n2 + 1;
  faces[nn3][1] = n22 + 1;
  faces[nn3][2] = n22;
 }
 this.faces = faces;
 this.n = n;
 facesColors(this);
}

// Файл mainSph.js
var i = 0;
var n2 = obj.n / 2;
var ctx, cw, ch;
// Инициализация
function sceneInit() {
 // Подготовка сцены
 canvas = document.getElementById('scene');
 ctx = canvas.getContext('2d');
 ctx.strokeStyle = 'rgb(0,0,0)'; // Цвет линии (черный)
 ctx.lineWidth = 0.5; // Толщина линии
 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() {
 if (i == 0) {
  ctx.translate(-shiftX, -shiftY);
  ctx.clearRect(0, 0, cw, ch); // Очистка области вывода
  ctx.translate(shiftX, shiftY);
 }
 // Вывод одной грани объекта
 ctx.beginPath(); // Начало вывода
 // Вершины i-й грани
 faceVerts = obj.faces[i];
 // Вывод грани (треугольника)
 for (j = 0; j < faceVerts.length; j++) { // Число вершин в грани (faceVerts.length) равно 3
  // j-я вершина грани
  vj = faceVerts[j];
  ctx.lineTo(obj.points[vj][0], obj.points[vj][1]); // Параметры lineTo - это X и Y-координаты вершины vj
 }
 ctx.closePath();
 ctx.stroke(); // Вывод линий
 ctx.fillStyle = obj.colors[i];
 ctx.fill(); // Заливка грани
 i++;
 if ((i - n2) % obj.n == 0) i += n2; // Выводим только передние (видимые) грани
 if (i >= obj.faces.length) i = 0;
}
window.onload = sceneInit

Сфера из трапеций

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

<html>
 <head>
  <script type = "text/javascript" src="sph4Mesh.js"></script>
  <script type = "text/javascript"> obj = new sphere(); </script>
  <script type = "text/javascript" src = "sph4.js"></script>
 </head>
 <body>
  <canvas id = "scene" height = "400" width = "600"></canvas>
 </body>
</html>

// Файл sph4Mesh.js
r = 100;
nx = 16;
ny = 16;
pi = Math.PI;
pix = pi / nx;
piy = pi / ny;
// Цвета граней
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 + ')';
 }
}
// Сфера
function sphere() {
 // Вершины и грани сферы
 vertices = [];
 faces = [];
 nv = -1; // Счетчик вершин
 nf = -1; // Счетчик граней
 rsy0 = 0;
 y0 = r;
 ay1 = 0;
 for (iy = 0; iy < ny; iy++) {
  ay1 = ay1 + piy;
  rsy1 = r * Math.sin(ay1);
  y1 = r * Math.cos(ay1);
  ax = 0;
  for (ix = 0; ix <= nx; ix++) {
   sx0 = Math.sin(ax);
   cx0 = Math.cos(ax);
   ax = ax + 2 * pix;
   sx1 = Math.sin(ax);
   cx1 = Math.cos(ax);
   // Вершины очередной трапеции (y0 и y1 задаются выше)
   // Лево низ
   x0 = rsy0 * cx0;
   z0 = rsy0 * sx0;
   nv++;
   vertices[nv] = [x0, y0, z0];
   // Право низ
   x1 = rsy0 * cx1;
   z1 = rsy0 * sx1;
   nv++;
   vertices[nv] = [x1, y0, z1];
   // Право верх
   x2 = rsy1 * cx1;
   z2 = rsy1 * sx1;
   nv++;
   vertices[nv] = [x2, y1, z2];
   // Лево верх
   x3 = rsy1 * cx0;
   z3 = rsy1 * sx0;
   nv++;
   vertices[nv] = [x3, y1, z3];
   nf++;
   faces[nf] = [nv - 3, nv - 2, nv - 1, nv];
  }
  rsy0 = rsy1;
  y0 = y1;
 }
 this.vertices = vertices;
 this.faces = faces;
 facesColors(this);
}
function rotateY3D(theta, vertices) {
 ang = pi * theta / 180;
 ct = Math.cos(ang);
 st = Math.sin(ang);
 for (i = 0; i < vertices.length; i++) {
  x = vertices[i][0];
  y = vertices[i][1];
  z = vertices[i][2];
  vertices[i] = [ct * x + st * z, y, -st * x + ct * z];
 }
}
function rotateX3D(theta, vertices) {
 ang = pi * theta / 180;
 ct = Math.cos(theta);
 st = Math.sin(theta);
 for (i = 0; i < vertices.length; i++) {
  x = vertices[i][0];
  y = vertices[i][1];
  z = vertices[i][2];
  vertices[i] = [x, ct * y - st * z, st * y + ct * z];
 }
}

// Файл sph4.js
var ctx;
var shiftX, shiftY;
// Инициализация сцены
function sceneInit() {
 // Подготовка сцены
 canvas = document.getElementById('scene');
 ctx = canvas.getContext('2d');
 ctx.strokeStyle = 'rgb(0,0,0)'; // Цвет линии (черный)
 ctx.lineWidth = 0.5; // Толщина линии
 cw = canvas.width;
 ch = canvas.height;
 shiftX = 0.5 * cw; // Смещение по X
 shiftY = 0.5 * ch; // Смещение по Y
 ctx.translate(shiftX, shiftY);
 rotateY3D(15, obj.vertices); // Поворот вокруг оси Y на 15° против часовой стрелки
 rotateX3D(15, obj.vertices); // Поворот вокруг оси X на 15° против часовой стрелки
 // 100 миллисекунд – частота обновлений сцены
 setInterval(drawScene, 100);
}
// Вывод сцены
function drawScene() {
 ctx.translate(-shiftX, -shiftY);
 ctx.clearRect(0, 0, cw, ch); // Очистка области вывода
 ctx.translate(shiftX, shiftY);
 for (i = 0; i < obj.faces.length; i++) {
  faceVerts = obj.faces[i];
  // Вывод одной грани объекта
  ctx.beginPath(); // Начало вывода
  for (j = 0; j < 4; j++) {
   vj = faceVerts[j];
   ctx.lineTo(obj.vertices[vj][0], obj.vertices[vj][1]); // Используем X и Y-координаты вершины
  }
  ctx.closePath();
  ctx.stroke();
  ctx.fillStyle = obj.colors[i];
  ctx.fill(); // Заливка грани
 }
}
window.onload = sceneInit;

Заключение

Правдоподобное избражение сферы будет получено, если выполнить расчет нормалей к вершинам модели и задействовать освещенность.
Так же перед выводом граней следует выполнить их сортирровку по глубине, выводя прежде наиболее удаленные грани.
Кроме того, полезно использовать перспективное прооецирование взамен прямоугольного.

Список работ

Рейтинг@Mail.ru