Use your fingers or mouse to control the model (hold shift key or use mouse wheel to zoom it). Canvas is matched to your browser window.

# Making a Cone

 Below you can find short comments to a simple 5kb script cone. The cone has parameters h, r1, r2 and a normal (in the XY plane)     Nx = (r1 - r2)/N,   Ny = h/N,     N = ((r1 - r2)2 + h2)1/2. The number of triangles in the cone is 2×nPhi = 200. It is not enough for specular color yet and nPhi = 500 is better (see cone).
The CanvasMatrix.js library is used for operations with matrices.
```<script src="../CanvasMatrix.js" type="text/javascript">
```
The vertex shader calculates coordinates and colors (diffusive and specular) of rotated verteces.
```<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aPos;
attribute vec3 aNorm;
uniform mat4 mvMatrix;
uniform mat4 prMatrix;
varying vec4 color;
const vec3 dirDif = vec3(0., 0., 1.);
const vec3 dirHalf = vec3(-.4034, .259, .8776);
void main(void) {
gl_Position = prMatrix * mvMatrix * vec4(aPos, 1.);
vec3 rotNorm = (mvMatrix * vec4(aNorm, .0)).xyz;
float i = max( 0., dot(rotNorm, dirDif) );
color = vec4(.9*i, .5*i, 0., 1.);
i = pow( max( 0., dot(rotNorm, dirHalf) ), 120.);
color += vec4(i, i, i, 0.);
}
</script>
```
The fragment shader just transfers interpolated colors for every pixel.
```<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec4 color;
void main(void) {
gl_FragColor = color;
}
</script>
```
If necessarily it writes compilation errors.
```<script type="text/javascript">

function getShader ( gl, id ){
var shaderScript = document.getElementById ( id );
var str = "";
while ( k ){
if ( k.nodeType == 3 ) str += k.textContent;
k = k.nextSibling;
}
else return null;
}
```
This is the main function executed on load. Initialization of WebGL and canvas matching to the brouser window.
```var gl, canvas, h = 1, r1 = .5, r2 = .2, nPhi = 100;

function webGLStart() {
canvas = document.getElementById("canvas");
var size = Math.min(window.innerWidth, window.innerHeight) - 20;
canvas.width =  size;   canvas.height = size;
canvas.getContext("experimental-webgl");
if (!window.WebGLRenderingContext){
return;}
try { gl = canvas.getContext("experimental-webgl");
} catch(e) {}
if ( !gl ) {alert("Can't get WebGL"); return;}
gl.viewport(0, 0, size, size);
```
```   var prog  = gl.createProgram();
gl.useProgram(prog);
```
Make points and normals for our cone, put them into GPU and bind to shader attributes.
```   var pt = [], nt = [];
var Phi = 0, dPhi = 2*Math.PI / (nPhi-1),
Nx = r1 - r2, Ny = h, N = Math.sqrt(Nx*Nx + Ny*Ny);
Nx /= N; Ny /= N;
for (var i = 0; i < nPhi; i++ ){
var cosPhi = Math.cos( Phi );
var sinPhi = Math.sin( Phi );
var cosPhi2 = Math.cos( Phi + dPhi/2 );
var sinPhi2 = Math.sin( Phi + dPhi/2 );
pt.push ( -h/2, cosPhi * r1, sinPhi * r1 );   // points
nt.push ( Nx, Ny*cosPhi, Ny*sinPhi );         // normals
pt.push ( h/2, cosPhi2 * r2, sinPhi2 * r2 );  // points
nt.push ( Nx, Ny*cosPhi2, Ny*sinPhi2 );       // normals
Phi   += dPhi;
}
var posLoc = gl.getAttribLocation(prog, "aPos");
gl.enableVertexAttribArray( posLoc );
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(pt), gl.STATIC_DRAW);
gl.vertexAttribPointer(posLoc, 3, gl.FLOAT, false, 0, 0);

var normLoc = gl.getAttribLocation(prog, "aNorm");
gl.enableVertexAttribArray( normLoc );
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(nt), gl.STATIC_DRAW);
gl.vertexAttribPointer(normLoc, 3, gl.FLOAT, false, 0, 0);
```
Prepare perspective and modelview matrices. Put the first to the vertex shader as uniform. The rotation matrix is used to save integrated rotation.
```   var prMatrix = new CanvasMatrix4();
prMatrix.perspective(45, 1, .1, 100);
gl.uniformMatrix4fv( gl.getUniformLocation(prog,"prMatrix"),
false, new Float32Array(prMatrix.getAsArray()) );
var mvMatrix = new CanvasMatrix4();
var rotMat = new CanvasMatrix4();
rotMat.makeIdentity();
var mvMatLoc = gl.getUniformLocation(prog,"mvMatrix");
```
GL initialization.
```   gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.clearDepth(1.0);
gl.clearColor(0, 0, .5, 1);
var xOffs = yOffs = 0,  drag  = 0;
var xRot = yRot = 0;
var transl = -2.5;
drawScene();
```
The drawScene() function prepares cone's rotation and translation then draws its points as one TRIANGLE_STRIP.
```  function drawScene(){
rotMat.rotate(xRot/5, 1,0,0);  rotMat.rotate(yRot/5, 0,1,0);
yRot = 0;  xRot = 0;
mvMatrix.translate(0, 0, transl);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.uniformMatrix4fv( mvMatLoc, false,
new Float32Array(mvMatrix.getAsArray()) );
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 2*nPhi);
gl.flush ();
}
```
On window resize action
```  canvas.resize = function (){
var size = Math.min(window.innerWidth, window.innerHeight) - 10;
canvas.width =  size;   canvas.height = size;
gl.viewport(0, 0, size, size);
drawScene();
}
```
Mouse events processing to test if it is "draged" (these methods are outdated but they work).
```  canvas.onmousedown = function ( ev ){
drag  = 1;
xOffs = ev.clientX;  yOffs = ev.clientY;
}
canvas.onmouseup = function ( ev ){
drag  = 0;
xOffs = ev.clientX;  yOffs = ev.clientY;
}
```
On mouse "drag" we calculate two rotations and translation if the shift key is pressd. Then the cone is repainted.
```  canvas.onmousemove = function ( ev ){
if ( drag == 0 ) return;
if ( ev.shiftKey ) {
transl *= 1 + (ev.clientY - yOffs)/1000;
yRot = - xOffs + ev.clientX; }
else {
yRot = - xOffs + ev.clientX;  xRot = - yOffs + ev.clientY; }
xOffs = ev.clientX;  yOffs = ev.clientY;
drawScene();
}
}
```
This is the mouse wheel event handler
```  var wheelHandler = function(ev) {
var del = 1.1;
if (ev.shiftKey) del = 1.01;
var ds = ((ev.detail || ev.wheelDelta) > 0) ? del : (1 / del);
transl *= ds;
drawScene();
ev.preventDefault();
};
```<body onload="webGLStart();" onresize="canvas.resize();">