/**
* WebGL: Лаба 6
* @author Dennis Grishin
*/
( function () {
'use strict';
// Немного глобальных переменных
var shaderProgram,
vertexBuffer, // Буфер вершин
normalBuffer, // Буфер нормалей
indexBuffer, // Буфер индексов
textureCoordsBuffer, // Буфер координат текстуры
gl, // Переменная для хранения контекста WebGl
canvas, // Элемент DOM, содержащий область отрисовки
scene, // Объект сцены
statusbar, // Объект строки состояния
//Классы, используемые в программе
WebGLApplication,
Statusbar,
Scene,
Object3D,
Cube,
Cone,
// Задаем пропорцию сторон холста
screen_ratio = 2,
// Флаги
FIT_TO_WINDOW = true, // false: фиксированное соотношение сторон холста, true: холст точно вписывается в окно
// Константы
ON = 1, OFF = 0,
// Константы, режимы управления сценой
ROTATE_MODE = true, TRANSLATE_MODE = false,
// Константы, режимы отображения полигонов
SOLID_MODE = true, WIREFRAME_MODE = false,
// Константы, коды клавиш
KEY = { SHIFT: 16, SPACE: 32, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40,
X: 88, Z: 90, W: 87, S: 83, A: 65, D: 68,
Q: 81, E: 69, C: 67, V: 86, R: 82, T: 84,
O: 79, L: 76, TAB: 9, NUM1: 49, NUM2: 50, NUM3: 51,
NUM4: 52
};
// Функция задает размер холста, соответствующий размерам окна браузера, при фиксированной пропорции сторон
function set_proper_size(w, h, r) {
if (w < h*r) {
$( 'canvas' ).attr('width', w);
$( 'canvas' ).attr('height', w/r);
} else {
$( 'canvas' ).attr('width', h*r);
$( 'canvas' ).attr('height', h);
}
}
// Функции обратного вызова
// Обработчик клавиатуры
function keyboard_handler( key ) {
var step = 0.05,
angle = 0.05;
switch( key ) {
case KEY.SPACE:
scene.toggleAnimation();
break;
case KEY.NUM1:
case KEY.R:
scene.toggleControlMode();
break;
case KEY.NUM2:
case KEY.O:
scene.switchObject();
break;
case KEY.NUM3:
case KEY.SHIFT:
scene.toggleDisplayMode();
break;
case KEY.NUM4:
case KEY.T:
scene.toggleTexturing();
break;
case KEY.NUM5:
case KEY.L:
scene.toggleLightning();
break;
case KEY.UP:
case KEY.W:
if ( scene.control_mode === ROTATE_MODE ) {
scene.currentObject.rotate_x( angle );
} else {
scene.currentObject.move_y( step );
}
break;
case KEY.DOWN:
case KEY.S:
if ( scene.control_mode === ROTATE_MODE ) {
scene.currentObject.rotate_x( -angle );
} else {
scene.currentObject.move_y( -step );
}
break;
case KEY.LEFT:
case KEY.A:
if ( scene.control_mode === ROTATE_MODE ) {
scene.currentObject.rotate_z( angle );
} else {
scene.currentObject.move_x( -step );
}
break;
case KEY.RIGHT:
case KEY.D:
if ( scene.control_mode === ROTATE_MODE ) {
scene.currentObject.rotate_z( -angle );
} else {
scene.currentObject.move_x( step );
}
break;
case KEY.Q:
if ( scene.control_mode === ROTATE_MODE ) {
scene.currentObject.rotate_y( -angle );
} else {
scene.currentObject.move_z( -step );
}
break;
case KEY.E:
if ( scene.control_mode === ROTATE_MODE ) {
scene.currentObject.rotate_y( angle );
} else {
scene.currentObject.move_z( step );
}
break;
case KEY.Z:
scene.currentObject.resize( 0.99 );
break;
case KEY.X:
scene.currentObject.resize( 1.01 );
break;
case KEY.C:
scene.currentObject.extra( 1 );
break;
case KEY.V:
scene.currentObject.extra( -1 );
break;
}
}
// Обработчик ресайза
function resize_handler( width, height ) {
if ( FIT_TO_WINDOW ) {
canvas.width = width;
canvas.height = height;
screen_ratio = width/height;
gl.uniform1f( shaderProgram.uScreenRatio , screen_ratio );
} else {
set_proper_size( width, height, screen_ratio );
}
gl.viewport( 0, 0, canvas.width, canvas.height );
}
// Функция создания шейдера по типу и id источника в структуре DOM
function getShader( type, id ) {
var source = document.getElementById( id ).innerHTML,
shader = gl.createShader( type ); // Создаем шейдер по типу
// Установка источника шейдера
gl.shaderSource( shader, source );
// Компилируем шейдер
gl.compileShader( shader );
if ( !gl.getShaderParameter( shader, gl.COMPILE_STATUS ) ) {
alert( 'Ошибка компиляции шейдера: ' + gl.getShaderInfoLog( shader ) );
gl.deleteShader( shader );
return null;
}
return shader;
}
// Функция установки шейдеров
function initShaders() {
// Получаем шейдеры
var fragmentShader = getShader( gl.FRAGMENT_SHADER, 'shader-fs' ),
vertexShader = getShader( gl.VERTEX_SHADER, 'shader-vs' ),
uniformVariables, // Набор имен uniform-переменных
index; // Счетчик цикла
// Создаем объект программы шейдеров
shaderProgram = gl.createProgram();
// Прикрепляем к ней шейдеры
gl.attachShader( shaderProgram, vertexShader );
gl.attachShader( shaderProgram, fragmentShader );
// Связываем программу с контекстом webgl
gl.linkProgram( shaderProgram );
if ( !gl.getProgramParameter( shaderProgram, gl.LINK_STATUS ) ) {
alert( 'Не удалсь установить шейдеры' );
}
gl.useProgram( shaderProgram );
// Установка атрибутов программы
shaderProgram.vertexPositionAttribute = gl.getAttribLocation( shaderProgram, 'aVertexPosition' );
shaderProgram.vertexNormalAttribute = gl.getAttribLocation( shaderProgram, 'aVertexNormal' );
shaderProgram.vertexTextureAttribute = gl.getAttribLocation( shaderProgram, 'aVertexTextureCoords' );
uniformVariables = [
// float
'uScreenRatio',
'uTranslateX', 'uTranslateY', 'uTranslateZ',
'uRotateX', 'uRotateY', 'uRotateZ',
// vec3
'uLightPosition',
'uAmbientLight',
'uDiffuseLight',
'uVertexColor',
// int
'uTexturing',
'uLightning',
// sampler2D
'uSampler'
];
for ( index in uniformVariables ) {
var name = uniformVariables[ index ];
shaderProgram[ name ] = gl.getUniformLocation( shaderProgram, name );
}
}
function handleTextureLoaded( image, texture ) {
gl.bindTexture(gl.TEXTURE_2D, texture); //
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); //
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); //
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); //
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); //
gl.bindTexture(gl.TEXTURE_2D, null);
}
function setTextures( texture ) {
gl.bindTexture(gl.TEXTURE_2D, texture); //
var image = new Image();
image.onload = function() {
handleTextureLoaded(image, texture);
}
image.src = 'img/texture8.bmp';
gl.uniform1i(shaderProgram.uSampler, 0);
}
// Класс Statusbar
Statusbar = function() {
this.refresh();
};
Statusbar.prototype = {
setValues: function() {
this.values =
[ scene.currentIndex + '. ' + scene.currentObject.name,
+scene.currentObject.position_x.toFixed( 3 ),
+scene.currentObject.position_y.toFixed( 3 ),
+scene.currentObject.position_z.toFixed( 3 ),
+scene.currentObject.rotation_x.toFixed( 3 ),
+scene.currentObject.rotation_y.toFixed( 3 ),
+scene.currentObject.rotation_z.toFixed( 3 )
];
},
refresh: function() {
var i;
this.setValues();
for ( i = 0; i < this.values.length; i++ ) {
$('.statusbar_value').eq(i).html( this.values[i] );
}
if ( scene.control_mode === TRANSLATE_MODE ) {
$('.statusbar_property-position').addClass('statusbar_property-current');
$('.statusbar_property-rotation').removeClass('statusbar_property-current');
} else {
$('.statusbar_property-position').removeClass('statusbar_property-current');
$('.statusbar_property-rotation').addClass('statusbar_property-current');
}
}
};
// Класс Scene
Scene = function( width, height, depth ) {
this.width = this.height = this.depth = 1; // Значения по умолчанию
if ( typeof width === 'number' && width > 0 ) {
this.width = width;
}
if ( typeof height === 'number' && height > 0 ) {
this.height = height;
}
if ( typeof depth === 'number' && depth > 0 ) {
this.depth = depth;
}
this.initialization();
};
Scene.prototype = {
name: 'Scene',
// Функция установки буферов вершин и индексов
_initBuffers: function() {
// Создаем буфер вершин и индексов
indexBuffer = gl.createBuffer();
vertexBuffer = gl.createBuffer();
normalBuffer = gl.createBuffer();
textureCoordsBuffer = gl.createBuffer();
vertexBuffer.itemSize = 3;
normalBuffer.itemSize = 3;
textureCoordsBuffer.itemSize = 2;
},
setupWebGL: function() {
// Установка размеров области рисования
gl.viewport( 0, 0, canvas.width, canvas.height );
// Установка шейдеров
initShaders();
// Установка буферов вершин, индексов и текстурных координат
this._initBuffers();
// Включение массива атрибутов вершин
gl.enableVertexAttribArray( shaderProgram.vertexPositionAttribute );
if ( this.lightning === ON ) {
gl.enableVertexAttribArray( shaderProgram.vertexNormalAttribute );
}
if ( this.texturing === ON ) {
gl.enableVertexAttribArray( shaderProgram.vertexTextureAttribute );
}
// Задаем цвет заливки
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
gl.enable( gl.DEPTH_TEST );
// Assign the WebGLBuffer object currently bound to the ARRAY_BUFFER target to the vertex attribute at the passed index
gl.bindBuffer( gl.ARRAY_BUFFER, vertexBuffer );
gl.vertexAttribPointer( shaderProgram.vertexPositionAttribute, vertexBuffer.itemSize, gl.FLOAT, false, 0, 0 );
if ( this.lightning === ON ) {
gl.bindBuffer( gl.ARRAY_BUFFER, normalBuffer );
gl.vertexAttribPointer( shaderProgram.vertexNormalAttribute, normalBuffer.itemSize, gl.FLOAT, false, 0, 0 );
}
if ( this.texturing === ON ) {
gl.bindBuffer( gl.ARRAY_BUFFER, textureCoordsBuffer );
gl.vertexAttribPointer( shaderProgram.vertexTextureAttribute, textureCoordsBuffer.itemSize, gl.FLOAT, false, 0, 0 );
}
},
initialization: function() {
this.lightning = OFF; // Освещение ON/OFF
this.texturing = OFF; // Текстурирование ON/OFF
this.control_mode = TRANSLATE_MODE; // Режим управления TRANSLATE_MODE/ROTATE_MODE
this.display_mode = SOLID_MODE; // Режим отображения SOLID_MODE/WIREFRAME_MODE
// Параметры освещения
this.lightPosition = [ 1.0, 0.0, 0.0 ]; // Расположение источника света
this.ambientLight = [ 0.3, 0.3, 0.3 ]; // Фоновый свет, процент отображения каждой цветовой компоненты
this.diffuseLight = [ 1.0, 1.0, 1.0 ]; // Диффузное отражение света, в процентах
this.specularColor = [ 1.0, 1.0, 1.0 ]; // Цвет бликов
this.objects = []; // Массив объектов сцены
this.currentIndex = 0; // Индекс выбранного объекта в массиве объектов сцены
this.currentObject = null; // Выбранный объект, = this.objects[ this.currentIndex ]
this.currentObjectColor = [ 1.0, 1.0, 1.0 ]; // Цвет, которым выделяется выбранный объект
this.animation = false; // Запущена ли анимация
this.images = []; // Массив изображений для текстур
},
toggleAnimation: function() {
if ( this.animation === true ) {
this.animation = false;
} else {
this.animation = true;
this.animate();
}
},
animate: function() {
if ( !this.objects ) {
return;
}
var length = this.objects.length,
obj,
i;
for ( i = 0; i < length; i++ ) {
obj = this.objects[i];
if ( obj.animated === true ) {
obj.animate();
obj.computeNormals();
}
}
},
toggleControlMode: function() {
if ( this.control_mode === TRANSLATE_MODE ) {
this.control_mode = ROTATE_MODE;
} else {
this.control_mode = TRANSLATE_MODE;
}
},
toggleDisplayMode: function() {
if ( this.display_mode === WIREFRAME_MODE ) {
this.display_mode = SOLID_MODE;
} else {
this.display_mode = WIREFRAME_MODE;
}
},
toggleTexturing: function() {
if ( this.texturing === OFF ) {
this.texturing = ON;
} else {
this.texturing = OFF;
}
},
toggleLightning: function() {
if ( this.lightning === OFF ) {
this.lightning = ON;
} else {
this.lightning = OFF;
}
},
addObject: function( obj ) {
// Если аргумент функции - не трехмерный объект
if ( !( obj instanceof Object3D ) ) {
console.log(obj.name + ' is not an instance of Object3D!');
return;
}
// Добавляем объект в массив объектов сцены
this.objects.push( obj );
// Текущим выбранным объектом сцены считаем только что добавленный объект
this.currentObject = obj;
this.currentIndex = this.objects.length - 1;
},
switchObject: function() {
this.currentIndex++;
if ( this.currentIndex >= this.objects.length ) {
this.currentIndex = 0;
}
this.currentObject = this.objects[ this.currentIndex ];
},
setTexture: function( src ) {
var texture = gl.createTexture(),
n = this.images.length;
gl.bindTexture( gl.TEXTURE_2D, texture );
gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, true );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
this.images.push( new Image() );
this.images[n].src = src;
this.images[n].onload = function() {
gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this );
//scene.draw();
};
},
drawObject: function( obj ) {
// Если аргумент функции - не трехмерный объект
if ( !( obj instanceof Object3D ) ) {
console.log(obj.name + ' is not an instance of Object3D!');
return;
}
var vertices, normals, indices, textureCoords;
vertices = obj.getVertices( this.display_mode );
// Если у трехмерного объекта нет массива вершин
if ( !( vertices instanceof Array ) ) {
console.log('Object ' + obj.name + ' doesn\'t provide its vertices ');
return;
}
vertexBuffer.numberOfItems = vertices.length/vertexBuffer.itemSize;
indexBuffer.numberOfItems = vertexBuffer.numberOfItems;
gl.bindBuffer( gl.ARRAY_BUFFER, vertexBuffer );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( vertices ), gl.STATIC_DRAW );
// Если включено освещение
if ( this.lightning === ON ) {
gl.enableVertexAttribArray( shaderProgram.vertexNormalAttribute );
gl.bindBuffer( gl.ARRAY_BUFFER, normalBuffer );
gl.vertexAttribPointer( shaderProgram.vertexNormalAttribute, normalBuffer.itemSize, gl.FLOAT, false, 0, 0 );
normals = obj.getNormals();
// Если у трехмерного объекта нет массива нормалей
if ( !( normals instanceof Array ) ) {
console.log('Object ' + obj.name + ' doesn\'t provide its normals ');
return;
}
normalBuffer.numberOfItems = normals.length;
gl.bindBuffer( gl.ARRAY_BUFFER, normalBuffer );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( normals ), gl.STATIC_DRAW );
}
// Если включено текстурирование
if ( this.texturing === ON ) {
textureCoords = obj.getTextureCoords();
// Если у трехмерного объекта нет массива текстурных координат
if ( !( textureCoords instanceof Array ) ) {
console.log('Object ' + obj.name + ' doesn\'t provide its texture coordinates ');
return;
}
}
var len = vertices.length;
indices = [];
for ( var j = 0; j < len; j++ ) {
indices[j] = j;
}
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, indexBuffer );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( indices ), gl.STATIC_DRAW );
/*if ( this.texturing == ON ) {
gl.bindBuffer( gl.ARRAY_BUFFER, textureCoordsBuffer );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( textureCoords ), gl.STATIC_DRAW );
//textureCoordsBuffer.numberOfItems = textureCoords.length/textureCoordsBuffer.itemSize;
//gl.bindBuffer( gl.ARRAY_BUFFER, textureCoordsBuffer );
//gl.vertexAttribPointer( shaderProgram.vertexTextureAttribute, textureCoordsBuffer.itemSize, gl.FLOAT, false, 0, 0 );
//gl.activeTexture(gl.TEXTURE0);
//gl.bindTexture(gl.TEXTURE_2D, texture);
}*/
if ( this.display_mode === WIREFRAME_MODE ) {
gl.drawElements( gl.LINES, indexBuffer.numberOfItems, gl.UNSIGNED_SHORT, 0 );
gl.drawArrays( gl.LINES, 0, vertexBuffer.numberOfItems );
} else {
gl.drawElements( gl.TRIANGLES, indexBuffer.numberOfItems, gl.UNSIGNED_SHORT, 0 );
gl.drawArrays( gl.TRIANGLES, 0, vertexBuffer.numberOfItems );
}
},
draw: function() {
// Актуализируем строку состояния
statusbar.refresh();
// Переходим на следующий шаг анимации, если она включена
if ( this.animation ) {
this.animate();
}
// Передаем режим отображения текстуры и освещения во фрагментный шейдер
gl.uniform1i( shaderProgram.uTexturing, this.texturing );
gl.uniform1i( shaderProgram.uLightning, this.lightning );
// Очищаем всю область
gl.clear( gl.COLOR_BUFFER_BIT );
for ( var i = 0; i < this.objects.length; i++ ) {
var obj = this.objects[i];
if ( this.currentIndex == i && this.display_mode == WIREFRAME_MODE ) {
gl.uniform3fv( shaderProgram.uVertexColor , new Float32Array( this.currentObjectColor ) );
} else {
gl.uniform3fv( shaderProgram.uVertexColor , new Float32Array( obj.color ) );
}
// Освещение
gl.uniform3fv( shaderProgram.uLightPosition , new Float32Array( this.lightPosition ) );
gl.uniform3fv( shaderProgram.uAmbientLight , new Float32Array( this.ambientLight ) );
gl.uniform3fv( shaderProgram.uDiffuseLight , new Float32Array( this.diffuseLight ) );
// Расположение
gl.uniform1f( shaderProgram.uTranslateX , obj.position_x );
gl.uniform1f( shaderProgram.uTranslateY , obj.position_y );
gl.uniform1f( shaderProgram.uTranslateZ , obj.position_z );
// Поворот
gl.uniform1f( shaderProgram.uRotateX , obj.rotation_x );
gl.uniform1f( shaderProgram.uRotateY , obj.rotation_y );
gl.uniform1f( shaderProgram.uRotateZ , obj.rotation_z );
this.drawObject( scene.objects[i] );
}
},
};
// Класс Object3D
Object3D = function( obj ) {
};
Object3D.prototype = {
name: 'Object3D',
constructor: Object3D,
// Матрицы в шейдере перемножаются в порядке Rx*Ry*Rz
rotate_x: function( phi ) {
this.rotation_x += phi; // Здесь надо ещё как-то изменить y, z
if ( this.rotation_x > Math.PI) {
this.rotation_x -= 2*Math.PI;
}
if ( this.rotation_x < -Math.PI) {
this.rotation_x += 2*Math.PI;
}
},
rotate_y: function( phi ) {
this.rotation_y += phi; // Здесь надо ещё как-то изменить z
if ( this.rotation_y > Math.PI) {
this.rotation_y -= 2*Math.PI;
}
if ( this.rotation_y < -Math.PI) {
this.rotation_y += 2*Math.PI;
}
},
rotate_z: function( phi ) {
this.rotation_z += phi; // Это правильно
if ( this.rotation_z > Math.PI) {
this.rotation_z -= 2*Math.PI;
}
if ( this.rotation_z < -Math.PI) {
this.rotation_z += 2*Math.PI;
}
},
move_x: function( step ) {
this.position_x += step;
},
move_y: function( step ) {
this.position_y += step;
},
move_z: function( step ) {
this.position_z += step;
},
resize: function( k ) {
console.log('object ' + this.name + ' doesn\'t provide function resize');
return -1;
},
animate: function() {
console.log('object ' + this.name + ' doesn\'t provide function animate');
return -1;
},
extra: function( sign ) {
console.log('object ' + this.name + ' doesn\'t provide function extra');
return -1;
},
computeVertices: function() {
console.log('object ' + this.name + ' doesn\'t provide function computeVertices');
return -1;
},
computeIndices: function( display_mode ) {
console.log('object ' + this.name + ' doesn\'t provide function computeIndices');
return -1;
},
computeTextureCoords: function() {
console.log('object ' + this.name + ' doesn\'t provide function computeTextureCoords');
return -1;
},
getVertices: function( display_mode ) {
var vertices = [],
indices = this.getIndices( display_mode ),
i;
for ( i = 0; i < indices.length; i++ ) {
vertices[ 3*i ] = this.vertices[ indices[i]*3 ];
vertices[ 3*i + 1 ] = this.vertices[ indices[i]*3 + 1 ];
vertices[ 3*i + 2 ] = this.vertices[ indices[i]*3 + 2 ];
}
return vertices;
},
getNormals: function() {
return this.normals;
},
getIndices: function( display_mode ) {
if ( display_mode == WIREFRAME_MODE ) {
return this.lines_indices;
} else {
return this.triangles_indices;
}
},
getTextureCoords: function() {
return this.textureCoords;
},
draw: function() {
console.log('object ' + this.name + ' doesn\'t provide function draw');
return -1;
},
};
// Класс Cube
Cube = function( side, color ) {
this.side = 0; // По умолчанию длина стороны равна 0
if ( typeof side === 'number' && side > 0 ) {
this.side = side;
}
this.color = [ 0.8, 0.2, 0.2 ]; // Цвет по умолчанию
if ( color instanceof Array && color.length == 3 ) {
this.color = color;
}
// Координаты положения объекта
this.position_x = 0;
this.position_y = 0;
this.position_z = 0;
// Углы поворота объекта вокруг своих осей
this.rotation_x = 0;
this.rotation_y = 0;
this.rotation_z = 0;
this.computeVertices();
this.computeIndices();
this.computeNormals();
this.computeTextureCoords();
}
Cube.prototype = {
name: 'Cube',
__proto__: Object3D.prototype, // Наследуем от Object3D
resize: function( k ) {
this.side *= k;
this.computeVertices();
},
computeVertices: function() {
var h = this.side/2;
this.vertices =
[
-h, h, h,
-h, -h, h,
h, -h, h,
h, h, h,
-h, h, -h,
-h, -h, -h,
h, -h, -h,
h, h, -h,
];
},
computeNormals: function() {
var h = this.side;
/*this.normals =
[
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1,
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0,
-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0
];*/
this.normals =
[
0, 0,-2, 0, 0,-2, 0, 0,-2, 0, 0,-2, 0, 0,-2, 0, 0,-2,
0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2,
0,-2, 0, 0,-2, 0, 0,-2, 0, 0,-2, 0, 0,-2, 0, 0,-2, 0,
0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0,
2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0,
-2, 0, 0,-2, 0, 0,-2, 0, 0,-2, 0, 0,-2, 0, 0,-2, 0, 0
];
},
computeIndices: function() {
this.lines_indices = [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ];
this.triangles_indices = [
/* front */
1, 2, 0,
2, 0, 3,
/* back */
5, 6, 4,
6, 4, 7,
/* top */
0, 3, 4,
3, 4, 7,
/* bottom */
1, 2, 5,
2, 5, 6,
/* left */
0, 1, 4,
1, 4, 5,
/* right */
3, 2, 7,
2, 7, 6,
];
},
computeTextureCoords: function() {
this.textureCoords = [
0.0, 0.0,
5.0, 0.0,
0.0, 5.0,
5.0, 0.0,
0.0, 0.0,
0.0, 5.0,
5.0, 5.0,
5.0, 0.0,
];
},
};
// Класс Cone
Cone = function( height, semimajor, semiminor, color ) {
// Вызываем конструктор базового класса
this.__proto__.constructor( this );
// Значения по умолчанию
this.height = 1;
this.semimajor = 0.5;
this.semiminor = 0.2;
this.color = [ 0.8, 0.2, 0.2 ];
if ( typeof height === 'number' && height > 0 ) {
this.height = height;
}
if ( typeof semimajor === 'number' && semimajor > 0 ) {
this.semimajor = semimajor;
}
if ( typeof semiminor === 'number' && semiminor > 0 ) {
this.semiminor = semiminor;
}
if ( color instanceof Array && color.length == 3 ) {
this.color = color;
}
// Координаты положения объекта
this.position_x = 0;
this.position_y = 0;
this.position_z = 0;
// Углы поворота объекта вокруг своих осей
this.rotation_x = 0;
this.rotation_y = 0;
this.rotation_z = 0;
// Количество вершин псевдо-эллипса
this.baseVertexCount = 20;
this.corsetsCount = 10;
this.concentricCount = 10;
this.animated = true; // Флаг, подтверждающий, что объект содержит анимацию
this.animation_parameter = 0.0; // Параметр анимации изменяется от 0 до 1
this.animation_step = 0.01; // Шаг приращения параметра анимации
this.computeVertices();
this.computeIndices();
this.computeNormals();
//this.computeTextureCoords();
}
Cone.prototype = {
name: 'Cone',
__proto__: Object3D.prototype, // Наследуем от Object3D
// Меняет состояние объекта в зависимости от параметра анимации, а также изменяет параметр
animate: function() {
var t = this.animation_parameter,
step = this.animation_step;
this.computeVertices();
// Меняем параметр
t += step * ( 1.0 - ( t - 0.5 ) * ( t - 0.5 ) * 4 ) + step/10; // Эффект easing-а
//t += step;
if ( t >= 1.0 ) {
t = 1.0;
this.animation_step = -step;
}
if ( t <= 0.0 ) {
t = 0.0;
this.animation_step = -step;
}
this.animation_parameter = t;
},
// Масштабирование
resize: function( k ) {
this.height *= k;
this.semimajor *= k;
this.semiminor *= k;
this.computeVertices();
},
// Регулирует разбиение
extra: function( sign ) {
if ( sign > 0 ) {
this.baseVertexCount += sign;
this.corsetsCount += sign;
this.concentricCount += sign;
} else {
if ( this.baseVertexCount > 3 ) {
this.baseVertexCount += sign;
}
if ( this.corsetsCount > 1 ) {
this.corsetsCount += sign;
}
if ( this.concentricCount > 1 ) {
this.concentricCount += sign;
}
}
this.computeVertices();
this.computeIndices();
this.computeNormals();
},
// Вычисление координат вершин конуса (здесь же вызывается функция подсчета вершин сферы и учитывается преобразование анимации)
computeVertices: function() {
var semiheight = this.height/2, // Половина высоты конуса
a = this.semimajor, // Большая полуось
b = this.semiminor, // Малая полуось
n = this.baseVertexCount, // Количество вершин основания
c = this.corsetsCount, // Количество дополнительных "перетяжек"
conc = this.concentricCount, // Количество дополнительных эллипсов в основании
n, // Количество вершин
t, // Параметр анимации
i, k; // Счетчики
this.vertices = []; // Массив вершин для заполнения
for ( k = 0; k < c; k++ ) { // Цикл по дополнительным "перетяжкам"
for ( i = 0; i < n; i++ ) { // Цикл по количеству вершин у каждой перетяжки (столько же, сколько у основания)
var phi = 2*i*Math.PI/n, // Полярный угол вершины
betta = b*Math.cos( phi ),
alpha = a*Math.sin( phi );
this.vertices.push( betta*(c - k)/c );
this.vertices.push( -semiheight + k*this.height/c );
this.vertices.push( alpha*(c - k)/c );
}
}
// Добавляем верхушку конуса
this.vertices.push( 0 );
this.vertices.push( semiheight );
this.vertices.push( 0 );
for ( k = 0; k < conc; k++ ) { // Цикл по дополнительным эллипсам
for ( i = 0; i < n; i++ ) { // Цикл по количеству вершин у эллипса (столько же, сколько у эллипса-основания)
var phi = 2*i*Math.PI/n, // Полярный угол вершины
betta = b*Math.cos( phi ),
alpha = a*Math.sin( phi );
this.vertices.push( betta*(conc - k)/conc );
this.vertices.push( -semiheight );
this.vertices.push( alpha*(conc - k)/conc);
}
}
// Добавляем центр основания конуса
this.vertices.push( 0 );
this.vertices.push( -semiheight );
this.vertices.push( 0 );
// Вычисляем координаты вершин сферы
this.computeVerticesSphere();
// Учитываем преобразование анимации
n = this.vertices.length;
t = this.animation_parameter;
for ( i = 0; i < n; i+=3 ) {
var T = 1.0 - t,
A = this.vertices[ i + 1 ]/2;
// Реализация кубического твининга
this.vertices[ i ] = this.vertices[ i ]*T*T*T + this.vertices_sphere[ i ]*t*t*t;
this.vertices[ i + 1 ] = this.vertices[ i + 1 ]*T*T*T + A*T*T*t + this.vertices_sphere[ i + 1 ]*t*t*t;
this.vertices[ i + 2 ] = this.vertices[ i + 2 ]*T*T*T + this.vertices_sphere[ i + 2 ]*t*t*t;
}
},
// Вычисление нормалей
computeNormals: function() {
var indices = this.getIndices(),
vertices = this.getVertices(),
a = {}, b = {}, c = {}, v = {}, w = {},x, y, z;
this.normals = [];
for ( var i = 0; i < indices.length/3; i++ ) {
a.x = vertices[ (i*3 + 0)*3 + 0 ];
a.y = vertices[ (i*3 + 0)*3 + 1 ];
a.z = vertices[ (i*3 + 0)*3 + 2 ];
b.x = vertices[ (i*3 + 1)*3 + 0 ];
b.y = vertices[ (i*3 + 1)*3 + 1 ];
b.z = vertices[ (i*3 + 1)*3 + 2 ];
c.x = vertices[ (i*3 + 2)*3 + 0 ];
c.y = vertices[ (i*3 + 2)*3 + 1 ];
c.z = vertices[ (i*3 + 2)*3 + 2 ];
v = { x: b.x - a.x, y: b.y - a.y, z: b.z - a.z };
w = { x: c.x - a.x, y: c.y - a.y, z: c.z - a.z };
x = v.y * w.z - v.z * w.y;
y = v.z * w.x - v.x * w.z;
z = v.x * w.y - v.y * w.x;
this.normals[ (i*3 + 0)*3 + 0 ] = x;
this.normals[ (i*3 + 0)*3 + 1 ] = y;
this.normals[ (i*3 + 0)*3 + 2 ] = z;
this.normals[ (i*3 + 1)*3 + 0 ] = x;
this.normals[ (i*3 + 1)*3 + 1 ] = y;
this.normals[ (i*3 + 1)*3 + 2 ] = z;
this.normals[ (i*3 + 2)*3 + 0 ] = x;
this.normals[ (i*3 + 2)*3 + 1 ] = y;
this.normals[ (i*3 + 2)*3 + 2 ] = z;
}
},
// Вычисление координат вершин cферы
computeVerticesSphere: function() {
var r = this.semimajor, // Большая полуось - теперь радиус сферы
n = this.baseVertexCount, // Количество вершин основания - теперь количество меридианов
c = this.corsetsCount, // Количество дополнительных "перетяжек" - теперь количество параллелей выше экватора
conc = this.concentricCount, // Количество дополнительных эллипсов в основании - теперь количесво параллелей от экватора и ниже
i, k; // Счетчики
this.vertices_sphere = []; // Массив вершин для заполнения
for ( k = 0; k < c; k++ ) { // Цикл по верхним параллелям
for ( i = 0; i < n; i++ ) { // Цикл по меридианам
var phi = 2 * i * Math.PI / n, // Полярный угол вершины
psi = k * Math.PI/( 2 * c ), // Угол между радиус-вектором и осью
h = Math.sin( psi ) * r;
this.vertices_sphere.push( Math.cos( phi ) * Math.sqrt( r*r - h*h ) );
this.vertices_sphere.push( h );
this.vertices_sphere.push( Math.sin( phi ) * Math.sqrt( r*r - h*h ) );
}
}
// Добавляем верхнюю точку сферы
this.vertices_sphere.push( 0 );
this.vertices_sphere.push( r );
this.vertices_sphere.push( 0 );
for ( k = 0; k < conc; k++ ) { // Цикл по нижним параллелям
for ( i = 0; i < n; i++ ) { // Цикл по меридианам
var phi = 2 * i * Math.PI / n, // Полярный угол вершины
psi = k * Math.PI / ( 2 * c ), // Угол между радиус-вектором и осью
h = Math.sin( psi ) * r;
this.vertices_sphere.push( Math.cos( phi ) * Math.sqrt( r*r - h*h ) );
this.vertices_sphere.push( -h );
this.vertices_sphere.push( Math.sin( phi ) * Math.sqrt( r*r - h*h ) );
}
}
// Добавляем центр основания конуса
this.vertices_sphere.push( 0 );
this.vertices_sphere.push( -r );
this.vertices_sphere.push( 0 );
},
// Вычисление индексов, по которым строится фигура (конус/сфера)
computeIndices: function() {
var semiheight = this.height/2, // Половина высоты конуса
a = this.semimajor, // Большая полуось
b = this.semiminor, // Малая полуось
n = this.baseVertexCount, // Количество вершин основания
c = this.corsetsCount, // Количество дополнительных "перетяжек"
conc = this.concentricCount, // Количество дополнительных эллипсов в основании
so_far, // Количество уже записанных индексов
i, j, k; // Счетчики
// Индексы каркасного отображения
this.lines_indices = []; // Массив индексов для заполнения
for ( k = 0; k < c; k++ ) {
for ( i = 0; i < n; i++ ) {
j = i - 1;
if ( j < 0 ) j = n - 1;
this.lines_indices.push( j + k*n );
this.lines_indices.push( i + k*n );
// Проводим медианы
if ( k < c - 1 ) {
this.lines_indices.push( i + k*n );
this.lines_indices.push( i + (k + 1)*n );
}
}
}
for ( i = 0; i < n; i++ ) {
this.lines_indices.push( i + (c - 1)*n );
this.lines_indices.push( c*n );
}
so_far = c*n + 1;
for ( k = 0; k < conc; k++ ) {
for ( i = 0; i < n; i++ ) {
j = i - 1;
if ( j < 0 ) j = n - 1;
this.lines_indices.push( so_far + j + k*n );
this.lines_indices.push( so_far + i + k*n );
// Проводим медианы
if ( k < conc - 1 ) {
this.lines_indices.push( so_far + i + k*n );
this.lines_indices.push( so_far + i + (k + 1)*n );
}
}
}
for ( i = 0; i < n; i++ ) {
this.lines_indices.push( so_far + i + (conc - 1)*n );
this.lines_indices.push( so_far + conc*n );
}
// Индексы сплошного (твердотельного) отображения
this.triangles_indices = []; // Массив индексов для заполнения
for ( k = 1; k < c; k++ ) {
for ( i = 0; i < n; i++ ) {
j = i - 1;
if ( j < 0 ) j = n - 1;
this.triangles_indices.push( i + (k - 1)*n );
this.triangles_indices.push( i + k*n );
this.triangles_indices.push( j + k*n );
this.triangles_indices.push( j + k*n );
this.triangles_indices.push( j + (k - 1)*n );
this.triangles_indices.push( i + (k - 1)*n );
}
}
for ( i = 0; i < n; i++ ) {
j = i - 1;
if ( j < 0 ) j = n - 1;
this.triangles_indices.push( c*n ); // Верхушка конуса
this.triangles_indices.push( j + (c - 1)*n );
this.triangles_indices.push( i + (c - 1)*n );
}
so_far = c*n + 1;
for ( k = 1; k < conc; k++ ) {
for ( i = 0; i < n; i++ ) {
j = i - 1;
if ( j < 0 ) j = n - 1;
this.triangles_indices.push( so_far + j + k*n );
this.triangles_indices.push( so_far + i + k*n );
this.triangles_indices.push( so_far + i + (k - 1)*n );
this.triangles_indices.push( so_far + i + (k - 1)*n );
this.triangles_indices.push( so_far + j + (k - 1)*n );
this.triangles_indices.push( so_far + j + k*n );
}
}
so_far += (conc - 1)*n;
for ( i = 0; i < n; i++ ) {
j = i - 1;
if ( j < 0 ) j = n - 1;
this.triangles_indices.push( so_far + i );
this.triangles_indices.push( so_far + j );
this.triangles_indices.push( so_far + n ); // Центр основания конуса
}
},
};
// ----------------- Main --------------------
canvas = document.getElementById("canvas3D");
try {
// Сначала пытаемся получить стандартный контекст WebGL
// Если не получится, обращаемся к экспериментальному контексту
gl = canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' );
}
catch(e) {}
// Если контекст не удалось получить, выводим сообщение
if (!gl) {
alert("Ваш браузер не поддерживает WebGL");
return;
}
set_proper_size( $(window).width(), $(window).height(), screen_ratio );
scene = new Scene( 1, 1, 1 );
scene.setupWebGL();
var color = [ 0.42, 0.61, 0.69 ]; // #6A9CB0
scene.addObject( new Cube( 0.4, color ) );
scene.addObject( new Cone( -1, -1, -1, color ) );
scene.currentObject.move_x( -0.8 );
//scene.setTexture( 'img/texture8.bmp' );
statusbar = new Statusbar();
resize_handler( $( window ).width(), $( window ).height() );
(function animloop(){
scene.draw();
window.requestAnimationFrame(animloop, canvas);
})();
// Анимация
var requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback, element ) {
return window.setTimeout(callback, 1000/60);
};
})();
// Привязка обработчиков к событийной модели браузера
$( 'body' ).keydown(function( eventObject ) {
keyboard_handler( eventObject.which );
});
$( window ).resize(function() {
resize_handler( $(this).width(), $(this).height() );
});
})();