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

© Habrahabr.ru