Среда программирования:
Notepad++
Статья по теме:
Цель: Показать работу алгоритма удаления невидимых граней выпуклого многогранника.
Краткое описание: Куб задан множеством из 6 граней. Вначале находятся нормали к граням, затем рассчитывается, какие из граней будут невидимы при отрисовке.
Для того, чтобы куб появился, нажмите ЛКМ и потяните мышку на canvas'е.
Код программы:
Вначале создаем canvas для рисования
<html> <head></head> <body> <p>Для отображения тела нажмите ЛКМ в поле ниже и потяните в любую сторону </p> <canvas id="pic" width="700" height="700" style="border:1px solid red"> </canvas><br> <button onclick="ctx.clearRect(0,0,500,500);">clear</button><br> <script type="text/javascript">//... реализация приведена ниже</script> </body> </html>
Инициализация canvas и реализация алгоритма:
let _ = undefined; let c = document.getElementById('pic'); let ctx = c.getContext('2d'); let colors = ['#ff0000', '#00ff00', '#0000ff', '#ff00ff', '#00ffff', '#ffff00'] // цвета граней // данные для вращения мышкой let prevPointer = []; let ctxClicked = false; let xRotation=0, yRotation=0; let K = 30; // описание грани фигуры class Face { constructor(pts) { if(pts[0].length!=3) throw 'можно только 3D'; if(pts.length<3) throw 'минимальная фигура - треугольник'; this.center = []; this.points = pts.slice(); for(let i in this.points[0]) { this.center[i] = 0; for(let j of this.points) this.center[i] += j[i]; this.center[i] /= this.points.length; } this.normal = [ (this.points[0][1]-this.points[1][1])*(this.points[2][2]-this.points[1][2])- (this.points[0][2]-this.points[1][2])*(this.points[2][1]-this.points[1][1]), (this.points[0][2]-this.points[1][2])*(this.points[2][0]-this.points[1][0])- (this.points[0][0]-this.points[1][0])*(this.points[2][2]-this.points[1][2]), (this.points[0][0]-this.points[1][0])*(this.points[2][1]-this.points[1][1])- (this.points[0][1]-this.points[1][1])*(this.points[2][0]-this.points[1][0])]; } rotate(alpha=undefined, axis=0, self=false) // axis 1=x, 2=y, 3=z. 0=нет оси поворота { let x, y, z; let offset = []; if(axis!=0 && alpha!=undefined) { alpha = alpha*(Math.PI/180); if(self) // есть возможность повернуть грань вокруг ее центра { for(const o in this.center) offset[o] = this.center[o]; for(const i in this.points) for(const j in this.points[i]) this.points[i][j]-=offset[j]; for(const i in this.center) { this.center[i]-=offset[i] this.normal[i]-=offset[i] } } // поворот данной грани вокруг точки for(const i in this.points) { x = this.points[i][0]; y = this.points[i][1]; z = this.points[i][2]; if(axis==1) this.points[i] = [x, y*Math.cos(alpha)+z*Math.sin(alpha), -y*Math.sin(alpha)+z*Math.cos(alpha)]; else if(axis==2) this.points[i] = [x*Math.cos(alpha)+z*Math.sin(alpha), y, -x*Math.sin(alpha)+z*Math.cos(alpha)]; else if(axis==3) this.points[i] = [x*Math.cos(alpha)+y*Math.sin(alpha), -x*Math.sin(alpha)+y*Math.cos(alpha), z]; } if(axis==1) { this.normal = [this.normal[0], this.normal[1]*Math.cos(alpha)+this.normal[2]*Math.sin(alpha), -this.normal[1]*Math.sin(alpha)+this.normal[2]*Math.cos(alpha)]; this.center = [this.center[0], this.center[1]*Math.cos(alpha)+this.center[2]*Math.sin(alpha), -this.center[1]*Math.sin(alpha)+this.center[2]*Math.cos(alpha)]; } else if(axis==2) { this.normal = [this.normal[0]*Math.cos(alpha)+this.normal[2]*Math.sin(alpha), this.normal[1], -this.normal[0]*Math.sin(alpha)+this.normal[2]*Math.cos(alpha)]; this.center = [this.center[0]*Math.cos(alpha)+this.center[2]*Math.sin(alpha), this.center[1], -this.center[0]*Math.sin(alpha)+this.center[2]*Math.cos(alpha)]; } else if(axis==3) { this.normal = [this.normal[0]*Math.cos(alpha)+this.normal[1]*Math.sin(alpha), -this.normal[0]*Math.sin(alpha)+this.normal[1]*Math.cos(alpha), this.normal[2]]; this.center = [this.center[0]*Math.cos(alpha)+this.center[1]*Math.sin(alpha), -this.center[0]*Math.sin(alpha)+this.center[1]*Math.cos(alpha), this.center[2]]; } if(self) { for(const i in this.points) for(const j in this.points[i]) this.points[i][j]+=offset[j]; for(const i in this.center) { this.center[i]+=offset[i] this.normal[i]+=offset[i] } } } } } // задание куба let shape = [ new Face([[-5, -5, -5], [-5, -5, 5], [-5, 5, 5], [-5, 5, -5]]), new Face([[5, 5, 5], [5, -5, 5], [5, -5, -5], [5, 5, -5]]), new Face([[-5, 5, -5], [5, 5, -5], [5, -5, -5], [-5, -5, -5]]), new Face([[-5, 5, 5], [5, 5, 5], [5, -5, 5], [-5, -5, 5]]), new Face([[-5, 5, -5], [-5, 5, 5], [5, 5, 5], [5, 5, -5]]), new Face([[-5, -5, -5], [-5, -5, 5], [5, -5, 5], [5, -5, -5]]) ]; checkNormals(shape); // проверка нормалей, чтобы они смотрели "наружу" // обработка события нажатия и перетаскивания мышью на canvas c.onmousedown = (e)=>{ ctxClicked = true; prevPointer = [e.pageX-8, e.pageY-8];} c.onmouseup = (e)=> { ctxClicked = false; prevPointer = []; } c.onmousemove = (e)=> { if(ctxClicked) { x = e.pageX-8; y = e.pageY-8; if(prevPointer[0]!=x || prevPointer[1]!=y) { xRotation = ((x - prevPointer[0])*20)/(Math.PI*180); yRotation = ((y - prevPointer[1])*20)/(Math.PI*180); draw(yRotation, -xRotation); prevPointer = [x, y]; } } } function checkNormals(shp) { let testVector = [0,0,0]; let result = 0; for(let i in shp) { for(let j in shp[i].center) testVector[j] += shp[i].center[j]; for(let j in testVector) result += shp[i].normal[j]*(shp[i].center[j] - (testVector[j]/shp.length)); // dot(A,B), где B=вектор из центра многогранника до центра данной грани if(result<0) for(let j in shp[i].normal) shp[i].normal[j] *= -1; // разворот вектора нормали result = 0; testVector = [0,0,0]; } } function getProjectionXY(x, y, z, k=K, x_bias=350, y_bias=350) // проекция на экран { let scale = 15; return [((k*x)/(z+k))*scale+x_bias, ((k*y)/(z+k))*scale+y_bias]; } //непосредственно отрисовка function draw(xRotation, yRotation) { ctx.clearRect(0,0,700,700); for(const i in shape) { ctx.beginPath(); ctx.fillStyle = colors[i]; shape[i].rotate(xRotation, 1); // поворот всех точек shape[i].rotate(yRotation, 2); for(let j in shape[i].points) { if(( (shape[i].normal[0]*(-shape[i].center[0])) + (shape[i].normal[1]*(-shape[i].center[1])) + (shape[i].normal[2]*(-K-shape[i].center[2])) )>0) ctx.lineTo(...getProjectionXY(...shape[i].points[j])); // отрисовка грани, если скалярное произведение между вектором "камеры" и вектором нормали грани > 0 } ctx.closePath(); ctx.fill(); } }
Прикрепленный файл | Размер |
---|---|
Melnikov_invisibleFaceDelete.zip | 2.39 кб |
Комментарии
Скажите пожалуйста, подскажите алгоритм по которому по заданным точкам можно определить тип многогранника, скажем это куб или прямоугольный параллелепипед. Нашел теорию по этим фигурам:
Акцентировать внимание необходимо на свойствах фигур или нет?
Если Вы встречались с подобными задачами, прошу подсказать.