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