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