📜 ⬆️ ⬇️

Own WebGL engine. Article number 2. Matrix

In the continuation of the article .

Matrix


When I first started developing the matrix, I didn’t even imagine how much it would simplify our life in the future. The matrix has many properties, but in our task I would reduce them all to one - “separation of flies from cutlets,” that is, an array of points from the general array of coordinates. From the point of view of our code, it will be a selection of an array of rows, each of which is a point and an array of columns, an array of one of the x, y, z or w coordinates. I have a simplified model, so I will not use “w”.

Having described our object through the matrix, you can easily move the object along any of the axes and rotate, and you can immediately determine the center of our object.

When describing a class of a matrix, we only need to know the array from which we obtain the matrix and the dimension of the points. I use a dimension of three - x, y, z.
')
So the code itself.

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; } } } 


Here, I first defined arrays of rows and columns, this is a mandatory part.
Then some characteristics of the matrix - the center, the radius and the maximum (minimum) values ​​of all coordinates, this cycle probably makes sense to put in a separate method (function) or if you do not need them - remove. "Center" we need in the future when you rotate the matrix.

Matrix operations

Now the whole beauty of the matrix is ​​revealed.

Move

 move: function(value,xyzw){ this.column[xyzw] = this.column[xyzw].map(function(i){return i+value;}) this.updateByColumn(); } 


When moving, we must at each point of the object change the necessary coordinates to the required value. xyzw - coordinate index, value - value. That is, if we need to shift the object to the right by 10 units, it suffices to apply the following method to the matrix of the object: move (10.0);
After the conversion, we update the entire matrix - updateByColumn.

Move to a certain point

 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(); } } } 


This movement at once in all coordinates. A very useful method, in this article we will use it to rotate around a certain point, in the next article it will also be useful to us when building complex, “composite” primitives.


Matrix rotation

  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); } 


Rotation around a certain point point at a certain angle angle, along a certain xyzType axis. First, I move the matrix to the point around which there will be rotation, then we form the rotation matrix, depending on the xyzType axis, around which there will be a rotation. I expand each point (row) of our object, then move the expanded matrix to the initial point.

Matrix update

Our matrix has 3 main variables. The entire array, an array of points-rows (rows), an array of column coordinates (columns). When changing one of these arrays, the other arrays need to be updated, for this and 2 methods are used

 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; }, 


The vectorByMatrix function - multiplication of the vector by the matrix, we used it when the matrix was rotated, moved out of the matrix class, I considered this function as static. If I had to separately make a class for a vector, then this function would be in the prototype of the vector.

 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; } 


Full code:
 /*  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); } } 


Conclusion

The botuMatrix class is auxiliary for our primitives. All methods that have been described in this matrix will be used inside the methods of primitives.

In the next article, primitives will be considered - a cube, a ball, a flat surface, and so on.

Source: https://habr.com/ru/post/227285/


All Articles