В продолжении статьи.
Когда только начал разрабатывать матрицу, даже не предполагал — на сколько она в дальнейшем нам упростит жизнь. У матрицы много свойств, но в нашей задаче я бы их все свел к одному — «отделение мух от котлет», то есть массива точек от общего массива координат. С точки зрения нашего кода — это будет выделение массива строк, каждая из которых является точкой и массива столбцов, массив одной из координат x,y,z или w. У меня упрощенная модель, поэтому «w» использовать не буду.
Описав наш объект через матрицу, можно с легкостью перемещать объект по любой из осей и поворачивать, а также можно сразу определить центр нашего объекта.
При описании класса матрицы, нам достаточно знать массив из которого мы получим матрицу и размерность точек. Я использую размерность равную трем — x,y,z.
Итак, сам код.
Здесь я сначала определил массивы строк и колонок, это обязательная часть.
Потом некие характеристики матрицы — центр, радиус и максимальные(минимальные) значения всех координат, данный цикл возможно имеет смысл вынести в отдельный метод (функцию) или если они вам не нужны — убрать. «Центр» нам понадобится в дальнейшем при повороте матрицы.
Вот сейчас и проявляется вся прелесть матрицы.
При перемещении мы должны у каждой точки объекта изменить нужные координаты на необходимое значение. xyzw — индекс координаты, value — значение. То есть, если нам надо сместить объект вправо на 10 единиц, достаточно к матрице объекта применить следующий метод: move(10,0);
После преобразования мы обновляем всю матрицу — updateByColumn.
Это перемещение сразу по всем координатам. Очень полезный метод, в этой статье мы его будем использовать для поворота вокруг определенной точки, в следующей статье он нам также пригодиться при построении сложных, «составных» примитивов.
Поворот вокруг определенной точки point на определенный угол angle, по определенной оси xyzType. Вначале перемещаю матрицу к той точки, вокруг которой будет вращение, потом формируем матрицу поворота в зависимости от оси xyzType, вокруг которой будет поворот. Разворачиваю каждую точку (строку) нашего объекта, после этого перемещаю развернутую матрицу в исходную точку.
У нашей матрицы 3 основных переменных. Весь массив, массив точек-строк (rows), массив координат-колонок (columns). При изменении одного из этих массивов, другие массивы требуется обновить, для этого и используются 2 метода
Функция vectorByMatrix — умножение вектора на матрицу, мы её использовали при повороте матрицы, вынесена за пределы класса матрицы, я данную функцию рассматривал как статическую. Если б мне пришлось отдельно делать класс для вектора, то данная функция была бы в прототипе вектора.
Полный код:
Класс botuMatrix является вспомогательным для наших примитивов. Все методы, которые были описаны в данной матрице будут использоваться внутри методов примитивов.
В следующей статье будут рассмотрены примитивы — куб, шар, плоская поверхность и так далее.
Матрица
Когда только начал разрабатывать матрицу, даже не предполагал — на сколько она в дальнейшем нам упростит жизнь. У матрицы много свойств, но в нашей задаче я бы их все свел к одному — «отделение мух от котлет», то есть массива точек от общего массива координат. С точки зрения нашего кода — это будет выделение массива строк, каждая из которых является точкой и массива столбцов, массив одной из координат 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; }
Полный код:
/*умножение вектора vector на матрицу matrix. Возвращает вектор - 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; } /*описание матрицы. Входящий параметр, массив source и кол-во колонок, на который данный массив требуется разбить - columns*/ 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 = []; // массив колонок - координат. this.column[0] - это массив координат x по всем точкам. /*Если есть хотя бы одно значение в входящем векторе.*/ if (source.length > 0) { var count = 0; /*заполняем массив строк - rows и массив колонок column*/ 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; }, /*перемещение матрицы к определенной точки - point*/ toPoint:function(point){ if (point) { if(point.length == this.columnNumbers) { /*каждую строку-точку изменяем на значение из точки - point. */ this.rows = this.rows.map(function(rowArray){ return rowArray.map(function(rowElement,index) { return rowElement + point[index]; } ) }); /*Обновляем матрицу по новому значению строк*/ this.updateByRow(); } } }, /*разворот матрицы this на угол angle, вокруг точки point по оси - xyzType*/ rotate:function(angle,point,xyzType){ /*умножение вектора на число*/ function multPointByValue(point,value){ return point.map(function(val){return value * val}); } /*смещаем матрицу так, чтобы точка point, вокруг которой идет разворот сместилась в точку [0,0,0]*/ this.toPoint(multPointByValue(point,-1)); //массив для матрицы разворота var rotateSource = []; // перевод градусы в радианы var radians = angle * Math.PI / 180.0; //выбор оси вокруг которой происходит разворот switch(xyzType){ case "byX": //формируем массив для матрицы разворота, вокруг оси X rotateSource = [1,0,0, 0,Math.cos(radians),Math.sin(radians), 0,-1 * Math.sin(radians),Math.cos(radians) ]; break; case "byY": //формируем массив для матрицы разворота, вокруг оси y rotateSource = [Math.cos(radians),0,-1 * Math.sin(radians), 0,1,0, Math.sin(radians),0,Math.cos(radians) ]; break; case "byZ": //формируем массив для матрицы разворота, вокруг оси Z 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); //разворачиваем текущую матрицу, умножив каждую точку - строки rows[i] на матрицу разворота this.rows = this.rows.map(function(irow){ return vectorByMatrix(irow,rotateMatrix); }); //обнуляем матрицу разворота и массив для матрицы разворота. Данные операции автомат. произведутся сборщиком мусора. На всякий случай очищаем явно. rotateMatrix = null; //обнуляем массив для матрицы разворота rotateSource = null; /*обновление матрицы по обновленным строкам-точкам, которые были развернуты*/ this.updateByRow(); //перемещение матрицы в исходное положение. this.toPoint(point); } }
Заключение
Класс botuMatrix является вспомогательным для наших примитивов. Все методы, которые были описаны в данной матрице будут использоваться внутри методов примитивов.
В следующей статье будут рассмотрены примитивы — куб, шар, плоская поверхность и так далее.
