Используя средства 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;
Правдоподобное избражение сферы будет получено, если выполнить расчет нормалей к вершинам модели и задействовать освещенность.
Так же перед выводом граней следует выполнить их сортирровку по глубине, выводя прежде наиболее удаленные грани.
Кроме того, полезно использовать перспективное прооецирование взамен прямоугольного.