Lecaw

Учимся создавать Web-сайты

Создаем 3D объекты из полигонов

October 29, 2012 335hits

Сегодняшний урок - мост между двумерной графикой в html5 и трехмерной (использование WebGL). Сегодня я покажу, как создать трехмерные объекты, используя полигоны. Полигоны или неструктурированная сетка - набор вершин, ребер и граней, которые формируют многомерный 3D объект в компьютерной графике и моделировании. Грани обычно состоят из треугольников, четырехугольников или других простых выпуклых многоугольников, так как это упрощает рендеринг, но также могут быть использованы вогнутые многоугольники или многоугольники с дырами.

Исходные файлы ДЕМО

 

 

 

Чтобы получить более подробную информацию, почитайте статью на Википедии.

Для демонстрации мы подготовили простые трехмерные объекты – куб и сфера.

Шаг 1. HTML

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

<html lang="en" >
    <head>
        <meta charset="utf-8" />
        <meta name="author" content="Script Tutorials" />
        <title>Triangle mesh for 3D objects in HTML5 | Script Tutorials</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">Please use Up / Down keys to change opacity</div>
</div>
</body>
</html>

Инициализация объектов:

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

Это означает, что если мы хотим вывести на экран куб – то нужно закомментировать вторую строчку, если нужно вывести сферу с 6 поверхностями – то первую.

Шаг 2. JS

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

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 функции: куб (который генерирует начальные массивы для простого объекта куба), и сфера (чтобы сгенерировать сферу). Как видите – довольно трудно вычислить все точки и грани для многомерной сферы.

js/main.js

// внутренние переменные
var canvas, ctx;
var vAlpha = 0.5;
var vShiftX = vShiftY = 0;
var distance = -700;
var vMouseSens = 0.05;
var iHalfX, iHalfY;
// инициализация
function sceneInit() {
    // подготовка canvas 
    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.2) ? (vAlpha - 0.1) : vAlpha; break; // Кнопка вниз
    }
}
// обработчик 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() {
    // очистка canvas
    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 и устанавливаем таймер. Не забывайте, что мы можем изменить параметр globalAlpha при помощи нажатия кнопок Вверх/Вниз.

Исходные файлы ДЕМО

 

Дополнительная информация

RATTING:
(0 голосов)

Медиа