Можно долго и вкусно описывать преимущества канваса, но статья не про это; не менее интересно
посмотреть, чем же канвас плох.
Так чем же?
- Он ни в какую не работает в Обозревателе Интернета одной большой известной корпорации: эмулируя канвас через VML мы отрезаем самые вкусные пиксельные манипуцляции, а эмуяция пиксельных вкусняшек на flash безбожно тормозит из-за того, что где-то в недрах ExternalInterface разработчики из Adobe вставили пару функций sleep :)
- Только Google Chrome (aka Chromium) может обеспечить достаточную скорость исполнения джаваскрипта. Во всех остальных браузерах вкусный и сексуальный эффект рискует превратиться в слайд-шоу.
- И, наконец, самое главное — это неспортивно! Я уверен, что пока есть люди пишущие сапёров на bat файлах, тетрисы на sed и боярские диалекты C++, программирование ради самого процесса программирования будет интересовать массы :)
- раз в две недели я буду писать о каком-либо новом эффекте;
- раз в два месяца я буду рассказывать делать playable demo какой-нибудь игры (как водится, не использующую canvas);
- и, наконец, раз в полгода я буду делать по игре (ну, по крайней мере, я буду очень стараться не сорвать сроки)
Небольшая еретическая статья на затравку — как сделать 3D + z-buffer + subpixel + gouraud shading используя канвас
Шаг 0
Для начала, необходимо позаботится о пользователях IE, предложив им поддержку тэга canvas в виде Chrome Frame: прописываем meta, подключаем гуглоскрипт, создаём блок no-canvas и правим стили для гугловского iframe (по умолчанию он появляется в центре страницы)<head> <meta http-equiv="X-UA-Compatible" content="chrome=1" /> <style type="text/css" media="screen"> .chrome-install { position: relative; margin: 0; padding: 0; top: 0; left: 0; } </style> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script> </head> <body onload="main()"> <div id="no-canvas" style="display:none;"> <h2><canvas> support required.</h2> <div id="chrome-install-placeholder"></div> </div> <div id="canvas-enabled"> <canvas id="canvas" width="384" height="384"></canvas> </div> </body>Функция main (init вызывается по таймауту, потому что иначе хром (dev версия) иногда не прорисовывает background у body до конца):
function main() { canvas = document.getElementById('canvas'); if (typeof(canvas.getContext) == 'function') { ctx = canvas.getContext('2d'); setTimeout(init, 100); } else { document.getElementById('canvas-enabled').style.display = 'none'; document.getElementById('no-canvas').style.display = ''; CFInstall.check({ node: 'chrome-install-placeholder', className: 'chrome-install', destination: window.location.href }); } }
Шаг 1
Всю отрисовку будем вести в буфере 128x128, а потом его выводить с 3-х кратным увеличением (используя putImageData).var scr = []; var zbuf = []; var WDT = 128; var HGT = 128; function blit() { var cd = ctx.createImageData(canvas.width, canvas.height); var data = cd.data; var ind = 0; for (var y = 0; y < HGT; y++) { var line = scr[y]; for (var x = 0; x < WDT; x++) { data[ind++] = line[x][0]; data[ind++] = line[x][1]; data[ind++] = line[x][2]; data[ind++] = 255; .... } ctx.putImageData(cd, 0, 0); }
Шаг 2
Представим фигуру в виде объекта и напишем читерскую функцию box, которая будет создавать кубы сразу с нормалями:{ points: [ {x: point_x, y: point_y, z: point_z, n: { x: point_normal_x, y: point_normal_y, z: point_normal_z } }, .... ], faces: [ [point_1, point_2, point_3, { x: face_normal_x, y: face_normal_y, z: face_normal_z }], .... ], color: object_color, rot: [rot_x, rot_y, rot_z] } function box(size, cl, rot) { var norm = 1 / Math.sqrt(3); return { points: [ { x: -size, y: size, z: -size, n: { x: -norm, y: norm, z: -norm } }, .... ], faces: [ [ 0, 1, 2, { x: 0, y: 1, z: 0 } ], .... ], color: cl, rot: rot }; }
Шаг 3
Выполним всю чёрную работу — повернём объект (вместе с нормалями — это быстрее, чем считать нормали заново) и спроецируем его в 2D (мне было лень нормально считать уровень освещённости, по-этому я использовал метод научного тыкаtm для нахождения магической формулы и магических коеффициентов 0.7 и 3)function project(pt, rm) { var rot = { x: (pt.x*rm.oxx + pt.y*rm.oxy + pt.z*rm.oxz), y: (pt.x*rm.oyx + pt.y*rm.oyy + pt.z*rm.oyz), z: (pt.x*rm.ozx + pt.y*rm.ozy + pt.z*rm.ozz + ZPOS) }; var l = 1 - (Math.cos(pt.n.x*rm.ozx + pt.n.y*rm.ozy + pt.n.z*rm.ozz) - 0.7) * 3; l = Math.max(0, Math.min(1, l)); return { x: ((WDT / 2) + rot.x * (WDT / 2 - 1) / rot.z), y: ((HGT / 2) + rot.y * (HGT / 2 - 1) / rot.z), z: rot.z, l: l }; } function draw_object(obj) { var ax = (tm * obj.rot[0]); var ay = (tm * obj.rot[1]); var az = (tm * obj.rot[2]); var s1 = Math.sin(ax); var s2 = Math.sin(ay); var s3 = Math.sin(az); var c1 = Math.cos(ax); var c2 = Math.cos(ay); var c3 = Math.cos(az); var rm = { oxx: (c2 * c1), oxy: (c2 * s1), .... }; var pr = []; for (var i = 0; i < obj.points.length; i++) { pr.push(project(obj.points[i], rm)); } for (var i = 0; i < obj.faces.length; i++) { var face = obj.faces[i]; var fz = (face[3].x*rm.ozx + face[3].y*rm.ozy + face[3].z*rm.ozz); if (fz <= 0) { triangle(pr[face[0]], pr[face[1]], pr[face[2]], obj.color); } } }
Шаг 4
Напишем процедуру рисования горизонтальных линий (не самую оптимальную) и треугольников (тоже не чемпион по скорости):function hline(y, xl, xr, cl, ll, lr, zl, zr) { // Растянем линию горизонтально — так как мы используем треугольники, // а не честные полигоны, то при субпиксельной отрисовке без этого хака // иногда проявляются артефакты xl -= 0.5; xr += 0.5; .... } // **в реальном коде используется изменённая функция** function triangle(a, b, c, cl) { .... сортировка вершин .... var dxl = (c.x - a.x) / (c.y - a.y); var dxr = (b.x - a.x) / (b.y - a.y); .... var y = a.y; while (y < b.y) { // (y - a.y) - не обязательно целочисленное // за счёт этого достигается субпиксельная точность xl = sx + dxl * (y - a.y); xr = sx + dxr * (y - a.y); .... hline(y, xl, xr, cl, ll, lr, zl, zr); y++; } .... }
Шаг 5
Остаётся написать функцию loop и запустить её через setInterval.function loop() { tm = ((new Date()).valueOf() - st) / 1.5; draw_scene(); blit(); }
Извините, а можете пояснить подробнее, зачем эти кубы создаются или подскажите, где почитать
ReplyDelete