Собственный движок WebGL. Статья №2. Матрица

от автора

В продолжении статьи

Матрица.

Когда только начал разрабатывать матрицу, даже не предполагал — на сколько она в дальнейшем нам упростит жизнь. У матрицы много свойств, но в нашей задаче я бы их все свел к одному — «отделение мух от котлет», то есть массива точек от общего массива координат. С точки зрения нашего кода — это будет выделение массива строк, каждая из которых является точкой и массива столбцов, массив одной из координат x,y,z или w. У меня упрощенная модель, поэтому «w» использовать не буду.

Описав наш объект через матрицу, можно с легкостью перемещать объект по любой из осей и поворачивать, а также можно сразу определить центр нашего объекта.

При описании класса матрицы, нам достаточно знать массив из которого мы получим матрицу и размерность точек. Я использую размерность равную трем — x,y,z.

Итак, сам код.

function botuMatrix (source,columns) { 	this.source = source; 	this.columnNumbers = columns; 	this.rowNumbers = source.length / columns; 	this.rows = []; 	this.minval = []; 	this.maxval = []; 	this.radius = []; 	this.center = []; 	this.column = [];  		 	if (source.length > 0) 	{ 		var count = 0;  		while(count < source.length) 		{ 			var currentRow = this.source.slice(count,count + this.columnNumbers);		 		    this.rows.push(currentRow); 			 			var columnCount = 0; 			while(columnCount <= this.columnNumbers) 			{ 				if (!this.column[columnCount])  				{ 					this.column[columnCount] = []; 				} 				this.column[columnCount].push(currentRow[columnCount]); 				columnCount += 1; 			} 			 			count = count + this.columnNumbers;  		}	 		this.rowNumbers = this.rows.length;  		 		if (this.rows.length > 0) 		{ 			count = 0; 			while(count < this.rows.length) 			{ 				var tempRow = this.rows[count].slice(0); 				if (count == 0 ) 				{ 					this.minval = tempRow.slice(0); 					this.maxval = tempRow.slice(0); 					this.radius = tempRow.slice(0); 					this.center = tempRow.slice(0); 				} 			 				if (count > 0) 				{ 					var rowcount = 0; 					while(rowcount < tempRow.length) 					{ 						this.minval.splice(rowcount,1,Math.min(this.minval[rowcount],tempRow[rowcount])); 					 						this.maxval.splice(rowcount,1,Math.max(this.maxval[rowcount],tempRow[rowcount])); 						this.radius.splice(rowcount,1,(this.maxval[rowcount] - this.minval[rowcount]) / 2); 						this.center.splice(rowcount,1,this.maxval[rowcount] - this.radius[rowcount]); 						rowcount = rowcount + 1;  					} 				} 				tempRow = null; 				count = count + 1; 			} 			tempRow = null; 		} 	 	}  } 

Здесь я сначало определил массивы строк и колонок, это обязательная часть.
Потом некие характеристики матрицы — центр, радиус и максимальные(минимальные) значения всех координат, данный цикл возможно имеет смысл вынести в отдельный метод (функцию) или если они вам не нужны — убрать. «Центр» нам понадобится в дальнейшем при повороте матрицы.

Операции с матрицей.

Вот сейчас и проявляется вся прелесть матрицы.

Перемещение

move: function(value,xyzw){ 	this.column[xyzw] = this.column[xyzw].map(function(i){return i+value;})	 	this.updateByColumn(); } 

При перемении мы должны у каждой точки объекта изменить нужные координаты на необходимое значение. xyzw — индекс координаты, value — значение. То есть, если нам надо сместить объект вправо на 10 единиц, достаточно к матрице объекта применить следующий метод: move(10,0);
После преобразования мы обновляем всю матрицу — updateByColumn.

Перемещение к определенной точки.

toPoint:function(point){     if (point)     {         if(point.length == this.columnNumbers) 	{ 	    this.rows = this.rows.map(function(rowArray)                                             { 				                return rowArray.map(function(rowElement,index) 			                             { 						         return rowElement + point[index]; 					             }) 		                            });             this.updateByRow(); 	}		 	} } 

Это перемещение сразу по всем координатам. Очень полезный метод, в этой статье мы его будем использовать для поворота вокруг определенной точки, в следующей статье он нам также пригодиться при построении сложных, «составных» примитивов.

Поворот матрицы.

	rotate:function(angle,point,xyzType){ 		function multPointByValue(point,value){ 			return point.map(function(val){return value * val}); 		} 		this.toPoint(multPointByValue(point,-1)); 		var rotateSource = []; 		var radians = angle * Math.PI / 180.0;  		 		switch(xyzType){ 			case "byX": 				rotateSource = [1,0,0, 								0,Math.cos(radians),Math.sin(radians), 								0,-1 * Math.sin(radians),Math.cos(radians) 				]; 				break; 			case "byY": 				rotateSource = [Math.cos(radians),0,-1 * Math.sin(radians), 								0,1,0, 								Math.sin(radians),0,Math.cos(radians) 								]; 				break; 			case "byZ": 				rotateSource = [Math.cos(radians),Math.sin(radians),0, 								-1 * Math.sin(radians),Math.cos(radians),0, 								0,0,1]; 				break;			  		} 		 		var rotateMatrix = new botuMatrix(rotateSource,3); 		this.rows = this.rows.map(function(irow){ 			return vectorByMatrix(irow,rotateMatrix); 		}); 		rotateMatrix = null; 		rotateSource = null; 		this.updateByRow(); 		this.toPoint(point); 	} 

Поворот вокруг определенной точки point на определенный угол angle, по определенной оси xyzType. Вначале перемещаю матрицу к той точки, вокруг которой будет вращение, потом формируем матрицу поворота в зависимости от оси xyzType, вокруг которой будет поворот. Разворачиваю каждую точку (строку) нашего объекта, после этого перемещаю развернутую матрицу в исходную точку.

Обновление матрицы

У нашей матрицы 3 основных переменных. Весь массив, массив точек-строк (rows), массив координат-колонок (columns). При изменении одного из этих массивов, другие массивы требуется обновить, для этого и используются 2 метода

updateByColumn:function(){ 	var columnCount = 0; 	while(columnCount < this.columnNumbers) 	{ 		var rowCount = 0; 		while(rowCount < this.rowNumbers) 		{ 			this.rows[rowCount][columnCount] = this.column[columnCount][rowCount]; 			this.source[columnCount + rowCount * this.columnNumbers] = this.column[columnCount][rowCount];  			rowCount++; 		}			 		columnCount++; 	}	 },  updateByRow:function(){        var rowCount = 0; 	while(rowCount < this.rowNumbers) 	{ 		var columnCount = 0; 		while(columnCount < this.columnNumbers) 		{ 			this.column[columnCount][rowCount] = this.rows[rowCount][columnCount]; 			this.source[columnCount + rowCount * this.columnNumbers] = this.column[columnCount][rowCount];  			columnCount++; 		} 		columnCount = null;	 		rowCount++; 	}	 	columnCount = null; 	rowCount = null; },	 

Функция vectorByMatrix — умножение вектора на матрицу, мы её использовали при повороте матрицы, вынесена за пределы класса матрицы, я данную функцию рассматривал как статическую. Если б мне пришлось отдельно делать класс для вектора, то данная функция была бы в прототипе вектора.

function vectorByMatrix(vector,matrix) { 	//alert(vector); 	var resultVector = []; 	if (vector.length == matrix.rowNumbers) 	{ 		var columnCount = 0; 		while(columnCount < matrix.columnNumbers){ 			var rowCount = 0; 			var value = 0; 			while(rowCount < matrix.rowNumbers) 			{  				value += vector[rowCount] * matrix.column[columnCount][rowCount];	 				rowCount++; 			} 			//alert(value); 			resultVector.push(value); 			columnCount++; 		} 	}  	return resultVector; } 

Полный код:

function vectorByMatrix(vector,matrix) { 	var resultVector = []; 	if (vector.length == matrix.rowNumbers) 	{ 		var columnCount = 0; 		while(columnCount < matrix.columnNumbers){ 			var rowCount = 0; 			var value = 0; 			while(rowCount < matrix.rowNumbers) 			{  				value += vector[rowCount] * matrix.column[columnCount][rowCount];	 				rowCount++; 			} 			resultVector.push(value); 			columnCount++; 		} 	}  	return resultVector; }   function botuMatrix (source,columns) { 	this.source = source; 	this.columnNumbers = columns; 	this.rowNumbers = source.length / columns; 	this.rows = []; 	this.minval = []; 	this.maxval = []; 	this.radius = []; 	this.center = []; 	this.column = [];  		 	if (source.length > 0) 	{ 		var count = 0;  		while(count < source.length) 		{ 			var currentRow = this.source.slice(count,count + this.columnNumbers);		 		    this.rows.push(currentRow); 			 			var columnCount = 0; 			while(columnCount <= this.columnNumbers) 			{ 				if (!this.column[columnCount])  				{ 					this.column[columnCount] = []; 				} 				this.column[columnCount].push(currentRow[columnCount]); 				columnCount += 1; 			} 			 			count = count + this.columnNumbers;  		}	 		this.rowNumbers = this.rows.length;  		 		if (this.rows.length > 0) 		{ 			count = 0; 			while(count < this.rows.length) 			{ 				var tempRow = this.rows[count].slice(0); 				if (count == 0 ) 				{ 					this.minval = tempRow.slice(0); 					this.maxval = tempRow.slice(0); 					this.radius = tempRow.slice(0); 					this.center = tempRow.slice(0); 				} 			 				if (count > 0) 				{ 					var rowcount = 0; 					while(rowcount < tempRow.length) 					{ 						this.minval.splice(rowcount,1,Math.min(this.minval[rowcount],tempRow[rowcount])); 					 						this.maxval.splice(rowcount,1,Math.max(this.maxval[rowcount],tempRow[rowcount])); 						this.radius.splice(rowcount,1,(this.maxval[rowcount] - this.minval[rowcount]) / 2); 						this.center.splice(rowcount,1,this.maxval[rowcount] - this.radius[rowcount]); 						rowcount = rowcount + 1;  					} 				} 				tempRow = undefined; 				count = count + 1; 			} 			tempRow = undefined; 		} 	 	}  }  botuMatrix.prototype = { 	move: function(value,xyzw){ 		this.column[xyzw] = this.column[xyzw].map(function(i){return i+value;})	 		this.updateByColumn(); 	}, 	updateByColumn:function(){ 		var columnCount = 0; 		while(columnCount < this.columnNumbers) 		{ 			var rowCount = 0; 			while(rowCount < this.rowNumbers) 			{ 				this.rows[rowCount][columnCount] = this.column[columnCount][rowCount]; 				this.source[columnCount + rowCount * this.columnNumbers] = this.column[columnCount][rowCount];  				rowCount++; 			} 			 			columnCount++; 		}	 	}, 	updateByRow:function(){ 		var rowCount = 0; 		while(rowCount < this.rowNumbers) 		{ 			var columnCount = 0; 			while(columnCount < this.columnNumbers) 			{ 				this.column[columnCount][rowCount] = this.rows[rowCount][columnCount]; 				this.source[columnCount + rowCount * this.columnNumbers] = this.column[columnCount][rowCount];  				columnCount++; 			} 			columnCount = undefined; 			 			rowCount++; 		}	 		columnCount = undefined; 		rowCount = undefined; },	 	toPoint:function(point){ 		if (point) 		{ 			if(point.length == this.columnNumbers) 			{ 				this.rows = this.rows.map(function(rowArray){ 					return rowArray.map(function(rowElement,index) 					{ 						return rowElement + point[index]; 					} 					) 				}); 				this.updateByRow(); 			}		 		} 	}, 	 	byPoint:function(point){ 		if (point) 		{ 			if(point.length == this.columnNumbers) 			{ 				this.rows = this.rows.map(function(rowArray){ 					return rowArray.map(function(rowElement,index) 					{ 						return rowElement * point[index]; 					} 					) 				}); 				this.updateByRow(); 			}		 		} 	},	 	 	 	rotate:function(angle,point,xyzType){ 		function multPointByValue(point,value){ 			return point.map(function(val){return value * val}); 		} 		this.toPoint(multPointByValue(point,-1)); 		var rotateSource = []; 		var radians = angle * Math.PI / 180.0;  		 		switch(xyzType){ 			case "byX": 				rotateSource = [1,0,0, 								0,Math.cos(radians),Math.sin(radians), 								0,-1 * Math.sin(radians),Math.cos(radians) 				]; 				break; 			case "byY": 				rotateSource = [Math.cos(radians),0,-1 * Math.sin(radians), 								0,1,0, 								Math.sin(radians),0,Math.cos(radians) 								]; 				break; 			case "byZ": 				rotateSource = [Math.cos(radians),Math.sin(radians),0, 								-1 * Math.sin(radians),Math.cos(radians),0, 								0,0,1]; 				break;			  		} 		 		var rotateMatrix = new botuMatrix(rotateSource,3); 		this.rows = this.rows.map(function(irow){ 			return vectorByMatrix(irow,rotateMatrix); 		}); 		rotateMatrix = null; 		rotateSource = null; 		this.updateByRow(); 		this.toPoint(point); 	}  	 } 
Заключение

Класс botuMatrix является вспомогательным для наших примитивов. Все методы, которые были описаны в данной матрице будут использоваться внутри методов примитивов.

В следующей статье будут рассмотрены примитивы — куб, шар, плоская поверхность и так далее.

ссылка на оригинал статьи http://habrahabr.ru/post/227285/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *