Pull to refresh

Собственный движок WebGL. Статья №3. Примитивы

Reading time6 min
Views7.5K
В продолжении статьи

В первой статье уже использовался самый первый примитив, который можно назвать просто «произвольная форма».

Перед описанием примитивов-объектов еще раз повторю два основных требования-замечания от нашей системы:
  • Каждый примитив должен содержать вектор вершин и вектор индексов (vertex, indices).
  • Строится каждый примитив по индексам через треугольники (TRIANGLE), то есть каждые 3 точки образуют независимый треугольник.


По мимо требований к каждому примитиву мы можем подключить матрицу. После подключения можно с легкостью производить следующие манипуляции:
  • Перемещение по любой из осей. Перемещение на определенное кол-во единиц или перемещение к любой точки в пространстве
  • Поворот вокруг любой точки. При подключении матрицы нам становится известен центр, соответственно, мы можем поворачивать примитив вокруг любой точки указав точку или же вокруг собственного центра.


Сами примитивы можно разделить:
  • Простые. Состоят только из одного примитива.
  • Сложные-составные. Состоят из нескольких примитивов


Простые примитивы



Plain

Описание

Данный примитив из себя представляет плоский прямоугольник. Для построения достаточно 2 треугольника. Необходимый минимум входных данных, который мы должны получить — это центр нашей фигуры, его ширина и высота. На выходе мы должны получить как минимум 2 массива: массив вершин, массив индексов. Каждую вершину рассчитываем через центр, ширину и высоту, так верхняя левая вершина — это точка, у которой x смещен от центра на половину ширины влево, а y на половину высоты вверх, z не смещается — фигура плоская. Таким же образом находим и все остальные вершины.
[this.frontCenter[0] - width / 2, this.frontCenter[1] + height / 2, this.frontCenter[2],  /*верхняя левая точка - 0*/
this.frontCenter[0] + width / 2, this.frontCenter[1] + height / 2, this.frontCenter[2],  /*верхняя правая точка - 1*/
this.frontCenter[0] + width / 2, this.frontCenter[1] - height / 2, this.frontCenter[2],  /*нижняя правая точка - 2*/
this.frontCenter[0] - width / 2, this.frontCenter[1] - height / 2, this.frontCenter[2]];  /*нижняя левая точка - 3*/

В массиве индексов мы определяем, как наши точки будут объединяться. В данном примитиве 2 треугольника
Верхняя левая точка, верхняя правая точка, нижняя левая точка. В массиве вершин это элементы — 0,1,3
Верхняя правая точка, нижняя правая точка, нижняя левая точка. В массиве вершин это элементы — 1,2,3
Соответственно, массив индексов выглядит следующим образом:
[0,1,3,1,2,3,];
Порядок индексов меняться не будет, а вот с вершинами могут быть некие изменения. Для того, чтобы легко было совершать манипуляции с нашим примитивом, переведем массив вершин в матрицу.
this.matrix = new botuMatrix(this.vertex,3);

Операции с примитивом

При манипуляции с матрицей будет изменяться массив, переданный как входящий параметр, в данном случае массив вершин. При описании матрицы были указаны возможные манипуляции с матрицей. Подключим данные манипуляции к нашему примитиву.
	moveByX:function(value){
		this.matrix.move(value,0);
	},
	moveByY:function(value){
		this.matrix.move(value,1);
	},
	moveByZ:function(value){
		this.matrix.move(value,2);
	},

	testToPoint:function(value){
		this.matrix.toPoint(value);
	},
	
	rotateAroundCenter:function(angle,xyzType)
	{
		this.matrix.rotate(angle,this.matrix.center,xyzType);
	},

	rotateAroundMaxPoint:function(angle,xyzType)
	{
		this.matrix.rotate(angle,this.matrix.maxval,xyzType);
	},	
	rotateAroundPoint:function(angle,point,xyzType)
	{
		this.matrix.rotate(angle,point,xyzType);
	},

  • moveByX,moveByY,moveByZ — перемещение примитива по X, Y и Z, соответственно. Единственный входящий параметр — кол-во единиц. Пример, obj.moveByX(50) — перемещение obj на 50 единиц вправо.
  • testToPoint — перемещаем примитив (ориентируясь на цент) к определенной точки. Входящий параметр — вектор-точка. Пример, obj.testToPoint([0,50,10]);
  • rotateAroundCenter,rotateAroundMaxPoint — разворот вокруг центра или вокруг максимальных координат. (в примере с прямоугольником, максимальные координаты совпадают с правой верхней точкой, однако максимальные координаты не всегда совпадают с какой либо точкой примитива. Если грубо каждый трехмерный объект упаковывать в куб, то максимальная координата — это верхняя дальняя точка этого куба.). Входящий параметр — угол разворота и ось по которой должен быть разворот. Пример: obj.rotateAroundCenter(45,«byX») — разворот вокруг оси X на 45 градусов. Угол указывается в градусах, оси — «byX»,«byY»,«byZ».
  • rotateAroundPoint — разворот вокруг произвольной точки в пространстве. Пример, obj.rotateAroundPoint(45,[0,0,0],«byZ»);

Данные операции не зависят от примитива, поэтому в дальнейшем будем их подключать без комментариев.

Cub

Данный примитив представляет из себя гексаэдр. Почти куб, только грани могут быть как квадраты, так и прямоугольники.
Описание будет такое же, как и у прямоугольника, только добавим ещё один входящий параметр — глубину.
У куба будет 8 вершин, как будто дальний и ближний прямоугольник. От простого прямоугольника, описанного выше, отличие будет заключаться в расчете координаты Z, которая в ближнем прямоугольнике будет уменьшаться на половину глубины, а в дальнем увеличиваться также на половину глубины.
Для этого просто возьмем два центра
	this.frontCenter=[centerPoint[0],centerPoint[1],centerPoint[2] - depth / 2];
	this.backCenter=[centerPoint[0],centerPoint[1],centerPoint[2] + depth / 2];

И в массиве будем создавать 2 прямоугольника, первый с центром frontCenter, второй с центром backCenter.
        /*ближниий прямоугольник*/
        this.frontCenter[0] - width / 2, this.frontCenter[1] + height / 2, this.frontCenter[2],  /*индекс - 0*/
	this.frontCenter[0] + width / 2, this.frontCenter[1] + height / 2, this.frontCenter[2],/*индекс - 1*/
	this.frontCenter[0] + width / 2, this.frontCenter[1] - height / 2, this.frontCenter[2],/*индекс - 2*/
	this.frontCenter[0] - width / 2, this.frontCenter[1] - height / 2, this.frontCenter[2],/*индекс - 3*/

        /*дальний прямоугольник*/
	this.backCenter[0] - width / 2, this.backCenter[1] + height / 2, this.backCenter[2],/*индекс - 4*/
	this.backCenter[0] + width / 2, this.backCenter[1] + height / 2, this.backCenter[2],/*индекс - 5*/
	this.backCenter[0] + width / 2, this.backCenter[1] - height / 2, this.backCenter[2],/*индекс - 6*/
	this.backCenter[0] - width / 2, this.backCenter[1] - height / 2, this.backCenter[2]/*индекс - 7*/


По поводу вершин индексов. В кубе 6 граней, каждая из который состоит из 2-х треугольников.
/*ближайший к нам прямоугольник, единственный, который мы видим, до манипуляций с кубом*/
0,1,3,
1,2,3,

/*левая грань*/
0,4,7,
0,3,7,

/*нижняя грань*/
3,7,6,
6,3,2,

/*правая грань*/
2,6,1,
1,5,6,

/*верхняя грань*/
0,4,5,
0,5,1,

/*задняя грань*/
7,4,5,
7,5,6

Сложные-составные примитивы


Простые примитивы. которые мы создали состоят из треугольников, и перед тем как создать данный примитив мы мысленно разбивали его на треугольники. Сложные примитивы будут состоять из любой другой геометрической, двухмерной фигуры. В данной статье будет рассмотрен единственный «сложный примитив» — шар. Который будет состоять из прямоугольников.
Шар

Что необходимо знать, чтобы нарисовать шар — координаты и радиус? Да. Но я добавлю ещё 1 маленький параметр — детализация.
Здесь один и тот же круг, с одним и тем же радиусом, только разной детализацией. О том, что в описании примитива будет пониматься под детализацией — чуть позже.
image
Детализация — 35
image
Детализация — 10
Алгоритм:

  • Строим прямоугольник по касательной, то есть центр прямоугольника — это центр круга смещенный по оси Z на величину радиуса. Высота и ширина прямоугольника — длина окружности, которая рассчитывается через радиус, разделенная на детализацию. Чем больше детализация, чем меньше высота-ширина прямоугольников, тем больше самих прямоугольников.
  • Сверху добавляем ещё один прямоугольник, развернутый на угол, равный 360* / (кол-во прямоугольников, которое было найдено на предыдущем шаге).
  • Повторяю предыдущий этап n-раз. Где n — кол-во прямоугольников. В результате получаем колесо. image
  • Делаем копию данного колеса с разворотом по оси Y на угол, равный 360* / (длинна круга, деленная на ширину).
  • Повторяем предыдущую операцию n-раз, где n — это длина круга, деленная на ширину.

Для реализации данного алгоритма

  • Создаем объект к которому можно подсоединять другие объекты. С точки зрения кода, в массивы вершин и индексов добавляются массивы вершин и индексов разных объектов.
    	function botuGroupedObject(){
    		this.vertex = [];
    	        this.indices = [];
    	}
    	botuGroupedObject.prototype = {
    		addObject:function(obj){
    			this.vertex.size = obj.vertex.size;
    			var next = Math.max(this.vertex.length / this.vertex.size,0);
    			this.vertex = this.vertex.concat(obj.vertex);	
    			this.indices = this.indices.concat(obj.indices.map(function(i){return i + next}));
    			this.vertex.size = obj.vertex.size;		
    		}
    	}
    	
  • Создаем вспомогательную функцию создания копии объекта в новом объекте.
    	function makeCopy(object,type){
    		var vertex  = object.vertex.slice(0);
    		var indices = object.indices.slice(0);
    		return new type(vertex,indices);
    	}
    	
  • К примитиву Plain добавляем метод
    		connectUP:function(anotherPlain){
    			var downLeftPoint = anotherPlain.getDownLeft();
    			
    			var dvPoint = downLeftPoint.map(function(value,index){return value - this.getUpperLeft()[index]},this);
    			this.testToPoint(dvPoint);
    		}
    	
    , которые строит примитив — plain вплотную к верхней границе другого примитива plain. getDownLeft() — это нижняя левая точка, то есть элемент из массива вершин с индексом 3. (см. выше в описании примитива Plain). getUpperLeft() — это верхняя левая точка, то есть элемент из массива вершин с индексом 0.


Полный код первых 3-х статей с подробными комментариями


Пример работы.
Tags:
Hubs:
+2
Comments6

Articles