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

в 14:25, , рубрики: javascript, matrix, WebGL, матрица, учимся вместе, метки: , ,

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

Матрица.

Когда только начал разрабатывать матрицу, даже не предполагал — на сколько она в дальнейшем нам упростит жизнь. У матрицы много свойств, но в нашей задаче я бы их все свел к одному — «отделение мух от котлет», то есть массива точек от общего массива координат. С точки зрения нашего кода — это будет выделение массива строк, каждая из которых является точкой и массива столбцов, массив одной из координат 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 является вспомогательным для наших примитивов. Все методы, которые были описаны в данной матрице будут использоваться внутри методов примитивов.

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

Автор: Botu

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js