Собственный движок WebGL. Статья №3. Примитивы

в 11:29, , рубрики: javascript, WebGL, учимся вместе, метки: , ,

В продолжении статьи

В первой статье уже использовался самый первый примитив, который можно назвать просто «произвольная форма».

Перед описанием примитивов-объектов еще раз повторю два основных требования-замечания от нашей системы:

  • Каждый примитив должен содержать вектор вершин и вектор индексов (vertex, indices).
  • Строится каждый примитив по индексам через треугольники (TRIANGLE), то есть каждые 3 точки образуют независимый треугольник.

По мимо требований к каждому примитиву мы можем подключить матрицу. После подключения можно с легкостью производить следующие манипуляции:

  • Перемещение по любой из осей. Перемещение на определенное кол-во единиц или перемещение к любой точки в пространстве
  • Поворот вокруг любой точки. При подключении матрицы нам становится известен центр, соответственно, мы можем поворачивать примитив вокруг любой точки указав точку или же вокруг собственного центра.

Сами примитивы можно разделить:

  • Простые. Состоят только из одного примитива.
  • Сложные-составные. Состоят из нескольких примитивов

Простые примитивы

Plain

Описание

Данный примитив из себя представляет плоский прямоугольник. Для построения достаточно 2 треугольника. Необходимый минимум входных данных, который мы должны получить — это центр нашей фигуры, его ширина и высота. На выходе мы должны получить как минимум 2 массива: массив вершин, массив индексов. Каждую вершину рассчитываем через центр, ширину и высоту, так верхняя левая вершина — это точка, у которой x смещен от центра на половину ширины влево, а y на половину высоты вверх, z не смещается — фигура плоская. Таким же образом находим и все остальные вершины.

[this.frontCenter[0] - width / 2, this.frontCenter[1] + height / 2, this.frontCenter[2],  /*верхняя левая точка - 0*/
this.frontCenter[0] + width / 2, this.frontCenter[1] + height / 2, this.frontCenter[2],  /*верхняя правая точка - 1*/
this.frontCenter[0] + width / 2, this.frontCenter[1] - height / 2, this.frontCenter[2],  /*нижняя правая точка - 2*/
this.frontCenter[0] - width / 2, this.frontCenter[1] - height / 2, this.frontCenter[2]];  /*нижняя левая точка - 3*/

В массиве индексов мы определяем, как наши точки будут объединяться. В данном примитиве 2 треугольника
Верхняя левая точка, верхняя правая точка, нижняя левая точка. В массиве вершин это элементы — 0,1,3
Верхняя правая точка, нижняя правая точка, нижняя левая точка. В массиве вершин это элементы — 1,2,3
Соответственно, массив индексов выглядит следующим образом:
[0,1,3,1,2,3,];
Порядок индексов меняться не будет, а вот с вершинами могут быть некие изменения. Для того, чтобы легко было совершать манипуляции с нашим примитивом, переведем массив вершин в матрицу.

this.matrix = new botuMatrix(this.vertex,3);

Операции с примитивом

При манипуляции с матрицей будет изменяться массив, переданный как входящий параметр, в данном случае массив вершин. При описании матрицы были указаны возможные манипуляции с матрицей. Подключим данные манипуляции к нашему примитиву.

	moveByX:function(value){
		this.matrix.move(value,0);
	},
	moveByY:function(value){
		this.matrix.move(value,1);
	},
	moveByZ:function(value){
		this.matrix.move(value,2);
	},

	testToPoint:function(value){
		this.matrix.toPoint(value);
	},
	
	rotateAroundCenter:function(angle,xyzType)
	{
		this.matrix.rotate(angle,this.matrix.center,xyzType);
	},

	rotateAroundMaxPoint:function(angle,xyzType)
	{
		this.matrix.rotate(angle,this.matrix.maxval,xyzType);
	},	
	rotateAroundPoint:function(angle,point,xyzType)
	{
		this.matrix.rotate(angle,point,xyzType);
	},

  • moveByX,moveByY,moveByZ — перемещение примитива по X, Y и Z, соответственно. Единственный входящий параметр — кол-во единиц. Пример, obj.moveByX(50) — перемещение obj на 50 единиц вправо.
  • testToPoint — перемещаем примитив (ориентируясь на цент) к определенной точки. Входящий параметр — вектор-точка. Пример, obj.testToPoint([0,50,10]);
  • rotateAroundCenter,rotateAroundMaxPoint — разворот вокруг центра или вокруг максимальных координат. (в примере с прямоугольником, максимальные координаты совпадают с правой верхней точкой, однако максимальные координаты не всегда совпадают с какой либо точкой примитива. Если грубо каждый трехмерный объект упаковывать в куб, то максимальная координата — это верхняя дальняя точка этого куба.). Входящий параметр — угол разворота и ось по которой должен быть разворот. Пример: obj.rotateAroundCenter(45,«byX») — разворот вокруг оси X на 45 градусов. Угол указывается в градусах, оси — «byX»,«byY»,«byZ».
  • rotateAroundPoint — разворот вокруг произвольной точки в пространстве. Пример, obj.rotateAroundPoint(45,[0,0,0],«byZ»);

Данные операции не зависят от примитива, поэтому в дальнейшем будем их подключать без комментариев.

Cub

Данный примитив представляет из себя гексаэдр. Почти куб, только грани могут быть как квадраты, так и прямоугольники.
Описание будет такое же, как и у прямоугольника, только добавим ещё один входящий параметр — глубину.
У куба будет 8 вершин, как будто дальний и ближний прямоугольник. От простого прямоугольника, описанного выше, отличие будет заключаться в расчете координаты Z, которая в ближнем прямоугольнике будет уменьшаться на половину глубины, а в дальнем увеличиваться также на половину глубины.
Для этого просто возьмем два центра

	this.frontCenter=[centerPoint[0],centerPoint[1],centerPoint[2] - depth / 2];
	this.backCenter=[centerPoint[0],centerPoint[1],centerPoint[2] + depth / 2];

И в массиве будем создавать 2 прямоугольника, первый с центром frontCenter, второй с центром backCenter.

        /*ближниий прямоугольник*/
        this.frontCenter[0] - width / 2, this.frontCenter[1] + height / 2, this.frontCenter[2],  /*индекс - 0*/
	this.frontCenter[0] + width / 2, this.frontCenter[1] + height / 2, this.frontCenter[2],/*индекс - 1*/
	this.frontCenter[0] + width / 2, this.frontCenter[1] - height / 2, this.frontCenter[2],/*индекс - 2*/
	this.frontCenter[0] - width / 2, this.frontCenter[1] - height / 2, this.frontCenter[2],/*индекс - 3*/

        /*дальний прямоугольник*/
	this.backCenter[0] - width / 2, this.backCenter[1] + height / 2, this.backCenter[2],/*индекс - 4*/
	this.backCenter[0] + width / 2, this.backCenter[1] + height / 2, this.backCenter[2],/*индекс - 5*/
	this.backCenter[0] + width / 2, this.backCenter[1] - height / 2, this.backCenter[2],/*индекс - 6*/
	this.backCenter[0] - width / 2, this.backCenter[1] - height / 2, this.backCenter[2]/*индекс - 7*/

По поводу вершин индексов. В кубе 6 граней, каждая из который состоит из 2-х треугольников.
/*ближайший к нам прямоугольник, единственный, который мы видим, до манипуляций с кубом*/
0,1,3,
1,2,3,

/*левая грань*/
0,4,7,
0,3,7,

/*нижняя грань*/
3,7,6,
6,3,2,

/*правая грань*/
2,6,1,
1,5,6,

/*верхняя грань*/
0,4,5,
0,5,1,

/*задняя грань*/
7,4,5,
7,5,6

Сложные-составные примитивы

Простые примитивы. которые мы создали состоят из треугольников, и перед тем как создать данный примитив мы мысленно разбивали его на треугольники. Сложные примитивы будут состоять из любой другой геометрической, двухмерной фигуры. В данной статье будет рассмотрен единственный «сложный примитив» — шар. Который будет состоять из прямоугольников.

Шар

Что необходимо знать, чтобы нарисовать шар — координаты и радиус? Да. Но я добавлю ещё 1 маленький параметр — детализация.
Здесь один и тот же круг, с одним и тем же радиусом, только разной детализацией. О том, что в описании примитива будет пониматься под детализацией — чуть позже.
image
Детализация — 35
image
Детализация — 10

Алгоритм:

  • Строим прямоугольник по касательной, то есть центр прямоугольника — это центр круга смещенный по оси Z на величину радиуса. Высота и ширина прямоугольника — длина окружности, которая рассчитывается через радиус, разделенная на детализацию. Чем больше детализация, чем меньше высота-ширина прямоугольников, тем больше самих прямоугольников.
  • Сверху добавляем ещё один прямоугольник, развернутый на угол, равный 360* / (кол-во прямоугольников, которое было найдено на предыдущем шаге).
  • Повторяю предыдущий этап n-раз. Где n — кол-во прямоугольников. В результате получаем колесо. image
  • Делаем копию данного колеса с разворотом по оси Y на угол, равный 360* / (длинна круга, деленная на ширину).
  • Повторяем предыдущую операцию n-раз, где n — это длина круга, деленная на ширину.

Для реализации данного алгоритма

  • Создаем объект к которому можно подсоединять другие объекты. С точки зрения кода, в массивы вершин и индексов добавляются массивы вершин и индексов разных объектов.
    	function botuGroupedObject(){
    		this.vertex = [];
    	        this.indices = [];
    	}
    	botuGroupedObject.prototype = {
    		addObject:function(obj){
    			this.vertex.size = obj.vertex.size;
    			var next = Math.max(this.vertex.length / this.vertex.size,0);
    			this.vertex = this.vertex.concat(obj.vertex);	
    			this.indices = this.indices.concat(obj.indices.map(function(i){return i + next}));
    			this.vertex.size = obj.vertex.size;		
    		}
    	}
    	
  • Создаем вспомогательную функцию создания копии объекта в новом объекте.
    	function makeCopy(object,type){
    		var vertex  = object.vertex.slice(0);
    		var indices = object.indices.slice(0);
    		return new type(vertex,indices);
    	}
    	
  • К примитиву Plain добавляем метод
    		connectUP:function(anotherPlain){
    			var downLeftPoint = anotherPlain.getDownLeft();
    			
    			var dvPoint = downLeftPoint.map(function(value,index){return value - this.getUpperLeft()[index]},this);
    			this.testToPoint(dvPoint);
    		}
    	

    , которые строит примитив — plain вплотную к верхней границе другого примитива plain. getDownLeft() — это нижняя левая точка, то есть элемент из массива вершин с индексом 3. (см. выше в описании примитива Plain). getUpperLeft() — это верхняя левая точка, то есть элемент из массива вершин с индексом 0.

Полный код первых 3-х статей

function makeCopy(object,type){
	var vertex  = object.vertex.slice(0);
	var indices = object.indices.slice(0);
	return new type(vertex,indices);
}

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

	
}



function Scene(canvasID) {
	this.backgroundColor = {red:1.0, green:1.0, blue:1.0, alpha:1.0};
    this.canvas = document.getElementById(canvasID);
    this.getContext();
    this.indicBuffer = "";
	this.vecVertex = [];
	this.vecIndices = [];

}

Scene.prototype = {


	clear: function(){
		this.indicBuffer = "";
		this.vecVertex = [];
		this.vecIndices = [];		
	
	},
	
	getContext:function(){
		var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
		this.gl = null;
		for (var ii = 0; ii < names.length; ++ii) {
			try {
				this.gl = this.canvas.getContext(names[ii]);
			} catch(e) {}
        if (this.gl) {
            break;
        }
		}	
	
	},
	
	
    initBuffers: function (vertex, indices) {
		this.vertexBuffer = this.gl.createBuffer();
		this.vertexBuffer.size = vertex.size;
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);

		this.program.botuPositionAttr = this.gl.getAttribLocation(this.program, "botuPosition");
		this.gl.enableVertexAttribArray(this.program.botuPositionAttr);

		this.gl.bufferData(this.gl.ARRAY_BUFFER,new Float32Array(vertex), this.gl.STATIC_DRAW);
		
	
       if(indices)
        {
            this.indicBuffer = this.gl.createBuffer();
            this.indicBuffer.numberOfItems = indices.length;
            this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.indicBuffer);
            this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), this.gl.STATIC_DRAW);
        }


    },
	


    initProgram: function (vxShaderDom, frShaderDom) {
        var vxShader = document.getElementById(vxShaderDom).textContent;
        var frShader = document.getElementById(frShaderDom).textContent;

        this.program = createProgram(this.gl,vxShader, frShader);
		this.gl.useProgram(this.program);
		
		this.program.botuPositionAttr = this.gl.getAttribLocation(this.program, "botuPosition");
		this.gl.enableVertexAttribArray(this.program.botuPositionAttr);
	
		

        function createProgram(context, vxs, frs) {
            var prg = context.createProgram();
            var VertexShader = createShader(context, context.VERTEX_SHADER, vxs);
            var FragmentShader = createShader(context, context.FRAGMENT_SHADER, frs);
            context.attachShader(prg,VertexShader);
            context.attachShader(prg,FragmentShader);
            context.linkProgram(prg);
            if (!context.getProgramParameter(prg, context.LINK_STATUS)) {
                alert(context.getProgramInfoLog(prg));
            }
            return prg;
        }

        function createShader(context,type,shader)
        {
            var sh = context.createShader(type);
            context.shaderSource(sh, shader);
            context.compileShader(sh);
            if (!context.getShaderParameter(sh, context.COMPILE_STATUS))
            {
                alert(context.getShaderInfoLog(sh));
            }
            return sh;
            
        }


    },


    attributeSetup: function (attribName, attribSize) {
        var attrib = this.gl.getAttribLocation(this.program, attribName);
        this.gl.enableVertexAttribArray(attrib);
        this.gl.vertexAttribPointer(attrib, attribSize, this.gl.FLOAT, false, 0, 0);
        return attrib;
    },

    setViewPort: function(width,height){
        this.gl.viewportWidth = width;
        this.gl.viewportHeight = height;
    },
	setBackgroundColor: function(colorVec){
		if (colorVec){
			if (colorVec.length > 0) 
			{
				this.backgroundColor.red = colorVec[0];
			}
			if (colorVec.length > 1) 
			{
				this.backgroundColor.green = colorVec[1];
			}
			if (colorVec.length > 2) 
			{
				this.backgroundColor.blue = colorVec[2];
			}
			if (colorVec.alpha > 3) 
			{
				this.backgroundColor.red = colorVec[3];
			}
			
		}
	},	
	
	AddObject: function(botuObj){
		this.vecVertex.size = botuObj.vertex.size;
		var next = Math.max(this.vecVertex.length / this.vecVertex.size,0);
		this.vecVertex = this.vecVertex.concat(botuObj.vertex);
		this.vecIndices = this.vecIndices.concat(botuObj.indices.map(function(i){return i + next}));
		this.vecVertex.size = botuObj.vertex.size;
	},

    draw: function () {
		
		this.initProgram("vertexShader", "fragmentShader");
		this.initBuffers(this.vecVertex, this.vecIndices);
    

        this.gl.viewport(0, 0, this.gl.viewportWidth, this.gl.viewportHeight);
        
		this.gl.clearColor(this.backgroundColor.red,this.backgroundColor.green,this.backgroundColor.blue,this.backgroundColor.alpha);
		this.gl.clear(this.gl.COLOR_BUFFER_BIT); 		        	
		this.gl.vertexAttribPointer(this.program.botuPositionAttr,this.vertexBuffer.size,this.gl.FLOAT,false,0,0);
  
		this.gl.enable(this.gl.DEPTH_TEST);
		this.gl.drawElements(this.gl.TRIANGLES, this.indicBuffer.numberOfItems, this.gl.UNSIGNED_SHORT, 0);		
    }
	



}



function botuObject(vertex,indices){
	this.vertex = [];
	this.indices = indices;
		
	this.matrix = new botuMatrix(vertex,3);

	this.vertex = this.matrix.source;
	this.vertex.size = 3;
}

botuObject.prototype = {
	moveByX:function(value){
		this.matrix.move(value,0);
	},
	moveByY:function(value){
		this.matrix.move(value,1);
	},
	moveByZ:function(value){
		this.matrix.move(value,2);
	},

	testToPoint:function(value){
		this.matrix.toPoint(value);
	},
	
	rotateAroundCenter:function(angle,xyzType)
	{
		this.matrix.rotate(angle,this.matrix.center,xyzType);
	},

	rotateAroundMaxPoint:function(angle,xyzType)
	{
		this.matrix.rotate(angle,this.matrix.maxval,xyzType);
	},	
	rotateAroundPoint:function(angle,point,xyzType)
	{
		this.matrix.rotate(angle,point,xyzType);
	}
	
}



function botuGroupedObject(){
	this.vertex = [];
	this.indices = [];
}
botuGroupedObject.prototype = {
	addObject:function(obj){
		this.vertex.size = obj.vertex.size;
		var next = Math.max(this.vertex.length / this.vertex.size,0);
		this.vertex = this.vertex.concat(obj.vertex);	
		this.indices = this.indices.concat(obj.indices.map(function(i){return i + next}));
		this.vertex.size = obj.vertex.size;		
	}
}



function cub(centerPoint,width,height,depth)
{
	this.frontCenter=[centerPoint[0],centerPoint[1],centerPoint[2] + depth / 2];
	this.backCenter=[centerPoint[0],centerPoint[1],centerPoint[2] - depth / 2];

	this.matrix = "";
	this.vertex=[];
	this.indices=[0,1,3,
				  1,2,3,

				  0,4,7,
				  0,3,7,

				  3,7,6,
				  6,3,2,
				  
				  2,6,1,
				  1,5,6,
				  
				  0,4,5,
				  0,5,1,
				  
				  7,4,5,
				  7,5,6			  				  
	];
	this.init(width,height);
}

cub.prototype = {
	init:function(width,height){
		this.matrix = new botuMatrix([ this.frontCenter[0] - width / 2, this.frontCenter[1] + height / 2, this.frontCenter[2],
						this.frontCenter[0] + width / 2, this.frontCenter[1] + height / 2, this.frontCenter[2],
						this.frontCenter[0] + width / 2, this.frontCenter[1] - height / 2, this.frontCenter[2],
						this.frontCenter[0] - width / 2, this.frontCenter[1] - height / 2, this.frontCenter[2],

						this.backCenter[0] - width / 2, this.backCenter[1] + height / 2, this.backCenter[2],
						this.backCenter[0] + width / 2, this.backCenter[1] + height / 2, this.backCenter[2],
						this.backCenter[0] + width / 2, this.backCenter[1] - height / 2, this.backCenter[2],
						this.backCenter[0] - width / 2, this.backCenter[1] - height / 2, this.backCenter[2],						
						
					   ],3);
		this.vertex = this.matrix.source;
		this.vertex.size = 3;		
	},
	moveByX:function(value){
		this.matrix.move(value,0);
	},
	moveByY:function(value){
		this.matrix.move(value,1);
	},
	moveByZ:function(value){
		this.matrix.move(value,2);
	},

	testToPoint:function(value){
		this.matrix.toPoint(value);
	},
	
	rotateAroundCenter:function(angle,xyzType)
	{
		this.matrix.rotate(angle,this.matrix.center,xyzType);
	},

	rotateAroundMaxPoint:function(angle,xyzType)
	{
		this.matrix.rotate(angle,this.matrix.maxval,xyzType);
	},	
	rotateAroundPoint:function(angle,point,xyzType)
	{
		this.matrix.rotate(angle,point,xyzType);
	}	

}



function plainClass(centerPoint,width,height)
{
	this.frontCenter=centerPoint;

	this.matrix = "";
	this.vertex=[];
	this.indices=[0,1,3,
				  1,2,3,
		  		];
	this.init(width,height);
}

plainClass.prototype = {
	init:function(width,height){
		
		this.vertex = [ this.frontCenter[0] - width / 2, this.frontCenter[1] + height / 2, this.frontCenter[2],
						this.frontCenter[0] + width / 2, this.frontCenter[1] + height / 2, this.frontCenter[2],
						this.frontCenter[0] + width / 2, this.frontCenter[1] - height / 2, this.frontCenter[2],
						this.frontCenter[0] - width / 2, this.frontCenter[1] - height / 2, this.frontCenter[2]];
		this.matrix = new botuMatrix(this.vertex,3);				
		this.vertex.size = 3;		
	},
	moveByX:function(value){
		this.matrix.move(value,0);
	},
	moveByY:function(value){
		this.matrix.move(value,1);
	},
	moveByZ:function(value){
		this.matrix.move(value,2);
	},

	testToPoint:function(value){
		this.matrix.toPoint(value);
	},
	
	rotateAroundCenter:function(angle,xyzType)
	{
		this.matrix.rotate(angle,this.matrix.center,xyzType);
	},

	rotateAroundMaxPoint:function(angle,xyzType)
	{
		this.matrix.rotate(angle,this.matrix.maxval,xyzType);
	},	
	rotateAroundPoint:function(angle,point,xyzType)
	{
		this.matrix.rotate(angle,point,xyzType);
	},

	getUpperLeft:function(){
		return this.matrix.rows[0];
	},
	getDownLeft:function(){
		return this.matrix.rows[3];
	},
	getDownRight:function(){
		return this.matrix.rows[2];
	},
	
	connect:function(anotherPlain){
		var downLeftPoint = anotherPlain.getUpperLeft();
		
		var dvPoint = downLeftPoint.map(function(value,index){return value - this.getUpperLeft()[index]},this);
		this.testToPoint(dvPoint);
	},
	
	connectUP:function(anotherPlain){
		var downLeftPoint = anotherPlain.getDownLeft();
		
		var dvPoint = downLeftPoint.map(function(value,index){return value - this.getUpperLeft()[index]},this);
		this.testToPoint(dvPoint);
	},
	
	connectLeft:function(anotherPlain){
		var downRightPoint = anotherPlain.getDownRight();
		
		var dvPoint = downRightPoint.map(function(value,index){return value - this.getDownLeft()[index]},this);
		this.testToPoint(dvPoint);
	}		
	

}



function circle(centerPoint,radius,numOfPlains){
	var plainWidth = 2 * Math.PI * radius / numOfPlains;
	var globalAngle = 45 * plainWidth / radius;  /* (1/2 width * 90) */ 
	var numOfIterations = 360 / globalAngle;
	var plainCenter = [centerPoint[0],centerPoint[1],centerPoint[2] + radius];
	this.angle = 360 / numOfPlains;
	
	this.plainObjects = [];
	
	var plain = "";
	var oldplain = "";
	var grp = new botuGroupedObject();
	
	for(var i = 1;i<=numOfPlains;i++)
	{
		var ang = (i-1) * this.angle
	
		plain = new plainClass(plainCenter,plainWidth,plainWidth);
		plain.rotateAroundCenter(ang,"byX");
		if (i > 1){
			plain.connectUP(oldplain);
		}
		oldplain = plain;
		grp.addObject(plain);
	}	
	plain = null;
	oldplain = null;
	var itt = 0;
	var tempAngle = 0;
	var temp = makeCopy(grp,botuObject); 
	while(tempAngle < 360)
	{	
		itt++;
		tempAngle = itt * globalAngle;
		var grp2 = makeCopy(temp,botuObject);
		grp2.rotateAroundCenter(tempAngle,"byY");
		grp.addObject(grp2);		
	}

/*	var plain = "";
	var oldplain = "";
	
	for(var i = 1;i<=numOfPlains;i++)
	{
		var ang = (i-1) * this.angle
	
		plain = new plainClass(plainCenter,plainWidth,plainWidth);
		plain.rotateAroundCenter(ang,"byY");
//		alert(plain + " " + oldplain + " " + plainCenter + " " + plainWidth);
		if (i > 1){
//			alert(plain + " " + oldplain + " " + i);
			plain.connectLeft(oldplain);
		}
		oldplain = plain;
		
	//	this.plainObjects.push(plain);
		grp.addObject(plain);
	}

*/	
	
	var grpobj = new botuObject(grp.vertex,grp.indices);
	
	this.vertex  = grp.vertex;
	this.indices = grp.indices;
	this.matrix = grpobj.matrix;	


}

circle.prototype = {
	init:function(width,height){
		this.matrix = new botuMatrix([ this.frontCenter[0] - width / 2, this.frontCenter[1] + height / 2, this.frontCenter[2],
						this.frontCenter[0] + width / 2, this.frontCenter[1] + height / 2, this.frontCenter[2],
						this.frontCenter[0] + width / 2, this.frontCenter[1] - height / 2, this.frontCenter[2],
						this.frontCenter[0] - width / 2, this.frontCenter[1] - height / 2, this.frontCenter[2],

						this.backCenter[0] - width / 2, this.backCenter[1] + height / 2, this.backCenter[2],
						this.backCenter[0] + width / 2, this.backCenter[1] + height / 2, this.backCenter[2],
						this.backCenter[0] + width / 2, this.backCenter[1] - height / 2, this.backCenter[2],
						this.backCenter[0] - width / 2, this.backCenter[1] - height / 2, this.backCenter[2],						
						
					   ],3);
		this.vertex = this.matrix.source;
		this.vertex.size = 3;		
	},
	moveByX:function(value){
		this.matrix.move(value,0);
	},
	moveByY:function(value){
		this.matrix.move(value,1);
	},
	moveByZ:function(value){
		this.matrix.move(value,2);
	},

	testToPoint:function(value){
		this.matrix.toPoint(value);
	},
	
	rotateAroundCenter:function(angle,xyzType)
	{
		this.matrix.rotate(angle,this.matrix.center,xyzType);
	},

	rotateAroundMaxPoint:function(angle,xyzType)
	{
		this.matrix.rotate(angle,this.matrix.maxval,xyzType);
	},	
	rotateAroundMinPoint:function(angle,xyzType)
	{
		this.matrix.rotate(angle,this.matrix.minval,xyzType);
	},
	rotateAroundPoint:function(angle,point,xyzType)
	{
		this.matrix.rotate(angle,point,xyzType);
	}	

}









window.onload = function () {
    var scene = new Scene("webgl");
	scene.setBackgroundColor([0.1,0.5,0.6,0.2]);
	
	scene.setViewPort(300, 300);
	
	var angle = 0;
	var debugAngle = document.getElementById("debugAngle");
	var speed = 0;


	var cube = new cub([-100,105,0],90,90,90);
	var circ = new circle([-120,-70,0],60,30);
	var plain = new plainClass([100,10,10],120,130);
	
	
/*	scene.AddObject(cube);
	scene.AddObject(circ);
	scene.AddObject(plain);
	
	scene.draw();*/

	
	

	    (function animloop(){

		  
		  if(angle >=90){
			speed = -0.01;
		  }

		  if(angle <= 0){
			speed = 0.01;
		  }	

		  angle+=speed;
		  debugAngle.innerHTML = angle + " " + speed;
		  scene.clear();
		  
		  cube.rotateAroundCenter(angle,"byY");	
		  cube.rotateAroundCenter(angle,"byX");	
		  cube.rotateAroundCenter(angle,"byZ");	
		  plain.rotateAroundCenter(angle,"byX");	
		  circ.rotateAroundPoint(angle,[-100,-50,20],"byZ");	
	

			scene.AddObject(cube);
			scene.AddObject(circ);
			scene.AddObject(plain);
          scene.draw(); 
          requestAnimFrame(animloop, scene.canvas);
        })();
	

}



window.requestAnimFrame = (function(){
      return  window.requestAnimationFrame       || 
              window.webkitRequestAnimationFrame || 
              window.mozRequestAnimationFrame    || 
              window.oRequestAnimationFrame      || 
              window.msRequestAnimationFrame     ||
             function(callback, element) {
               return window.setTimeout(callback, 1000/100);
             };
})();

HTML файл

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <script src="default.js"></script>
    <script type="x-shader" id="vertexShader">
	attribute vec3 botuPosition;

        
        varying vec4 colorPos;
        void main(){

        colorPos = vec4(max(botuPosition.x,(-1.0) * botuPosition.x) / 200.0, max(botuPosition.y,(-1.0) * botuPosition.y)  / 200.0, max(botuPosition.z,(-1.0) * botuPosition.z)  / 200.0,1.0);
        gl_Position = vec4(botuPosition,200);
        }

    </script>
    <script type="x-shader" id="fragmentShader">
        precision highp float;
        varying vec4 colorPos;
        void main(){
        gl_FragColor = colorPos;
        }
    </script>


</head>
<body class="phone">
    <canvas id="webgl" width="300" height="300"> </canvas>	
	<p id="debugAngle"></p>
</body>
</html>

Автор: Botu

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js