Уроки, алгоритмы, программы, примеры

Вход на сайт

Материалы по разделам

Построения
на плоскости (2D)
Графика
в пространстве (3D)
Вычислительная
геометрия
Физическое
моделирование
Фрактальная
графика

Новые комментарии

Не получается, емаё
огромное спасибо за подробное объяснение про 3д графику на питоне, в интернете очень мало подобной информации
dobryj den, popytalas otkryt prikreplionnyj fail ctoby posmotret kak rabotaet, no mne ego ne pokazyvaet vydajet osibku. Pochemu?
Очень интересно! ии сайт крутой жалко что умирает(
У Вас число превысит максимальное число int. Можно использовать в Вашем случае uint, но лучше все переписать на double.

Счетчики и рейтинг

Рейтинг@Mail.ru Яндекс.Метрика
Скриншот к примеру
Среда программирования: 
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.zip2.39 кб