/* Source: http://www.ibiblio.org/e-notes/webgl/models/ethanol.html
         * http://www.worldofmolecules.com/3D/dopamine_3d.htm
         */


        /// <summary>
        /// This is a javascript application.
        /// </summary>
        /// <param name="page">HTML document rendered by the web server which can now be enhanced.</param>
        public Application(IDefault page = null)
        {

            var prMatrix = new CanvasMatrix4();

            var gl_viewportWidth = 500;
            var gl_viewportHeight = 500;

            var gl = new WebGLRenderingContext();
            var canvas = gl.canvas.AttachToDocument();
            #region AtResize
            Action AtResize =
                delegate
                {
                    gl_viewportWidth = Native.window.Width;
                    gl_viewportHeight = Native.window.Height;

                    prMatrix = new CanvasMatrix4();



                    prMatrix.perspective(45f,
                        (f)Native.window.aspect,
                        1f, 100f);

                    canvas.style.SetLocation(0, 0, gl_viewportWidth, gl_viewportHeight);

                    canvas.width = gl_viewportWidth;
                    canvas.height = gl_viewportHeight;
                };

            Native.window.onresize +=
                e =>
                {
                    AtResize();
                };
            AtResize();
            #endregion

            Native.document.body.style.overflow = IStyle.OverflowEnum.hidden;








            var h = 1f;
            var r1 = .5f;
            var r2 = .2f;

            #region requestPointerLock
            var __pointer_x = 0;
            var __pointer_y = 0;

            canvas.onmousedown +=
                delegate
                {
                    canvas.requestPointerLock();
                };

            canvas.onmousemove +=
                e =>
                {
                    if (Native.Document.pointerLockElement == canvas)
                    {

                        __pointer_x += e.movementX;
                        __pointer_y += e.movementY;
                    }
                };

            canvas.onmouseup +=
                delegate
                {
                    //Native.Document.exitPointerLock();
                };
            #endregion



            var prog = gl.createProgram(
                new GeometryVertexShader(),
                new GeometryFragmentShader()
            );

            gl.linkProgram(prog);
            gl.useProgram(prog);

            var uniforms = prog.Uniforms(gl);

            var nPhi = 100;
            var nTheta = 50;
            var dPhi = 2 * Math.PI / nPhi;
            var dTheta = Math.PI / nTheta;

            var vertices = new IArray<float>();
            var ind = new IArray<ushort>();

            for (var j = 0; j <= nTheta; j++)
            {
                var Theta = j * dTheta;
                var cosTheta = Math.Cos(Theta);
                var sinTheta = Math.Sin(Theta);
                for (var i = 0; i <= nPhi; i++)
                {
                    var Phi = i * dPhi;
                    var cosPhi = Math.Cos(Phi);
                    var sinPhi = Math.Sin(Phi);
                    vertices.push((f)(cosPhi * sinTheta));
                    vertices.push((f)(-sinPhi * sinTheta));
                    vertices.push((f)(cosTheta));
                }
            }
            for (var j = 0; j < nTheta; j++)
                for (var i = 0; i <= nPhi; i++)
                {
                    ind.push((ushort)(j * (nPhi + 1) + i));
                    ind.push((ushort)((j + 1) * (nPhi + 1) + i));
                }
            var posLocation = gl.getAttribLocation(prog, "aPos");
            gl.enableVertexAttribArray((uint)posLocation);
            var posBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
            gl.vertexAttribPointer((uint)posLocation, 3, gl.FLOAT, false, 0, 0);

            var indexBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(ind.ToArray()),
              gl.STATIC_DRAW);

            //prMatrix.perspective(45, 1, .1, 100);
            gl.uniformMatrix4fv(gl.getUniformLocation(prog, "prMatrix"),
               false, new Float32Array(prMatrix.getAsArray()));
            var mvMatrix = new CanvasMatrix4();

            var mvMatLoc = gl.getUniformLocation(prog, "mvMatrix");


            gl.enable(gl.DEPTH_TEST);
            gl.depthFunc(gl.LEQUAL);
            gl.clearDepth(1.0f);
            gl.clearColor(0, 0, .8f, 1f);

            var xOffs = 0;
            var yOffs = 0;
            var drag = 0;
            var xRot = 0f;
            var yRot = 1f;
            var transl = -15.5f;




            #region drawScene
            Action drawScene = delegate
            {
                var rotMat = new CanvasMatrix4();
                rotMat.makeIdentity();

                #region draw
                Action<f, f, f, f, f, f, f> drawBall = (x, y, z, r, g, b, _scale) =>
                {
                    var scale = _scale * 1.4f;

                    mvMatrix.makeIdentity();
                    mvMatrix.translate(x, y, z);
                    mvMatrix.multRight(rotMat);
                    mvMatrix.translate(0, 0, transl);
                    gl.uniformMatrix4fv(mvMatLoc, false, new Float32Array(mvMatrix.getAsArray()));

                    //var colorLoc = gl.getUniformLocation(prog, "color");
                    //var scaleLoc = gl.getUniformLocation(prog, "scale");

                    uniforms.color = new __vec3(r, g, b);
                    uniforms.scale = scale;

                    //gl.uniform1f(scaleLoc, scale);
                    //gl.uniform3f(colorLoc, r, g, b);

                    for (var i = 0; i < nTheta; i++)
                        gl.drawElements(gl.TRIANGLE_STRIP, 2 * (nPhi + 1), gl.UNSIGNED_SHORT,
                          4 * (nPhi + 1) * i);
                };

                Action<f, f, f, f> drawBall_white = (x, y, z, _scale) =>
                drawBall(x, y, z, 1, 1, 1, _scale);


                Action<f, f, f, f> drawBall_red = (x, y, z, _scale) =>
                    drawBall(x, y, z, 1, 0, 0, _scale);

                Action<f, f, f, f> drawBall_blue = (x, y, z, _scale) =>
                 drawBall(x, y, z, 0, 0, 1, _scale);

                Action<f, f, f, f> drawBall_gray = (x, y, z, _scale) =>
              drawBall(x, y, z, .3f, .3f, .3f, _scale);
                #endregion

                gl.viewport(0, 0, gl_viewportWidth, gl_viewportHeight);

                #region prMatrix
                gl.uniformMatrix4fv(gl.getUniformLocation(prog, "prMatrix"),
false, new Float32Array(prMatrix.getAsArray()));
                #endregion

                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

                rotMat.rotate(xRot / 3, 1, 0, 0);
                rotMat.rotate(yRot / 3, 0, 1, 0);

                //yRot = 0; 
                //xRot = 0;

                rotMat.rotate(__pointer_y * 1.0f, 1, 0, 0);
                rotMat.rotate(__pointer_x * 1.0f, 0, 1, 0);

                //__pointer_x = 0;
                //__pointer_y = 0;

                //http://en.wikipedia.org/wiki/Cyanogen
                #region C2N2
                drawBall_blue(0, -3, 0, 1f);
                drawBall_gray(0, -1, 0, 1.2f);
                drawBall_gray(0, 1, 0, 1.2f);
                drawBall_blue(0, 3, 0, 1f);
                #endregion


                //#region C6H3
                //drawBall_gray(2, -1, 0, 1.5f);

                //drawBall_gray(0, -2, 0, 1.5f);
                //drawBall_white(0, -3.5f, 0, 1f);


                //drawBall_gray(-2, -1, 0, 1.5f);

                //drawBall_gray(2, 1, 0, 1.5f);
                //drawBall_white(3 + 0.5f, 1.5f + 0.5f, 0, 1f);

                //drawBall_gray(0, 2, 0, 1.5f);
                //drawBall_white(0, 3.5f, 0, 1f);

                //drawBall_gray(-2, 1, 0, 1.5f);
                //#endregion

                //#region CH2-CH2
                //drawBall_white(6, -1 + 1, -1.5f, 1f);
                //drawBall_gray(6, -1, 0, 1.5f);
                //drawBall_white(6, -1 + 1, 1.5f, 1f);


                //drawBall_white(4, -2 - 1, -1.5f, 1f);
                //drawBall_gray(4, -2, 0, 1.5f);
                //drawBall_white(4, -2 - 1, 1.5f, 1f);
                //#endregion

                //#region NH2
                //drawBall_white(8, -2 - 1, -1.5f, 1f);
                //drawBall_blue(8, -2, 0, 1.5f);
                //drawBall_white(8, -2 - 1, 1.5f, 1f);
                //#endregion


                gl.flush();
            };
            #endregion

            #region mouse
            canvas.onmousedown += ev =>
            {
                ev.preventDefault();

                drag = 1;
                xOffs = ev.CursorX;
                yOffs = ev.CursorY;
            };

            canvas.onmouseup += ev =>
            {
                ev.preventDefault();


                drag = 0;
                xOffs = ev.CursorX;
                yOffs = ev.CursorY;
            };

            canvas.onmousemove += ev =>
            {
                if (drag == 0)
                    return;

                ev.preventDefault();

                if (ev.shiftKey)
                {
                    transl *= 1 + (ev.CursorY - yOffs) / 1000;
                    yRot = -xOffs + ev.CursorX;
                }
                else
                {
                    yRot = -xOffs + ev.CursorX;
                    xRot = -yOffs + ev.CursorY;
                }

                xOffs = ev.CursorX;
                yOffs = ev.CursorY;
                drawScene();
            };
            #endregion

            #region onmousewheel
            canvas.onmousewheel +=
                ev =>
                {
                    var del = 1.1f;

                    if (ev.shiftKey)
                        del = 1.01f;

                    if (ev.WheelDirection > 0)
                        transl *= del;
                    else
                        transl *= (1 / del);

                    drawScene();



                    ev.PreventDefault();
                };
            #endregion




            #region IsDisposed
            var IsDisposed = false;

            this.Dispose = delegate
            {
                if (IsDisposed)
                    return;

                IsDisposed = true;

                canvas.Orphanize();
            };
            #endregion


            #region requestFullscreen
            Native.Document.body.ondblclick +=
                delegate
                {
                    if (IsDisposed)
                        return;

                    // http://tutorialzine.com/2012/02/enhance-your-website-fullscreen-api/

                    Native.Document.body.requestFullscreen();


                };
            #endregion


            var c = 0;
            Native.window.onframe += delegate
            {
                if (IsDisposed)
                    return;

                c++;

                xRot += 0.2f;
                yRot += 0.3f;


                drawScene();

            };





            //new IHTMLAnchor { "drag me to my.jsc-solutions.net" }.AttachToDocument().With(
            //    dragme =>
            //    {
            //        dragme.style.position = [email protected];
            //        dragme.style.left = "1em";
            //        dragme.style.bottom = "1em";

            //        dragme.AllowToDragAsApplicationPackage();
            //    }
            //);

        }
        /// <summary>
        /// This is a javascript application.
        /// </summary>
        /// <param name="page">HTML document rendered by the web server which can now be enhanced.</param>
        public Application(IDefault page = null)
        {
            var gl_viewportWidth = Native.window.Width;
            var gl_viewportHeight = Native.window.Height;




            var gl = new WebGLRenderingContext();


            var canvas = gl.canvas.AttachToDocument();

            Native.document.body.style.overflow = IStyle.OverflowEnum.hidden;
            canvas.style.SetLocation(0, 0, gl_viewportWidth, gl_viewportWidth);


            gl.viewport(0, 0, gl_viewportWidth, gl_viewportWidth);


            var prog = gl.createProgram(
                new CubicVertexShader(),
                new CubicFragmentShader()
                );


            var posLoc = 0U;
            gl.bindAttribLocation(prog, posLoc, "aPos");
            var normLoc = 1U;
            gl.bindAttribLocation(prog, normLoc, "aNorm");
            gl.linkProgram(prog);
            gl.useProgram(prog);

            #region data
            var a = 1.0f; // where is it used? what shall be the type?
            var pt0 = new float[] {-a,-a,a, a,-a,a, -a,a,a, a,a,a,  // cubic
                 -a,a,a, a,a,a, -a,a,-a, a,a,-a,
                 -a,a,-a, a,a,-a, -a,-a,-a, a,-a,-a,  -a,-a,-a, a,-a,-a, -a,-a,a, a,-a,a,
                 a,a,a, a,a,-a, a,-a,a, a,-a,-a,  -a,a,a, -a,a,-a, -a,-a,a, -a,-a,-a};
            var nt = new float[] {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,0,-1, 0,0,-1, 0,0,-1, 0,0,-1,  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};
            var ind = new ushort[] {0,1,2,1,2,3, 4,5,6,5,6,7, 8,9,10,9,10,11,
                 12,13,14,13,14,15, 16,17,18,17,18,19, 20,21,22,21,22,23};

            var nPhi = 25;
            var nTheta = 12;
            var r = .15;
            var dPhi = 2.0 * Math.PI / nPhi;
            var dTheta = Math.PI / nTheta;

            for (var j = 0; j <= nTheta; j++)
            {
                var Theta = j * dTheta;
                var cosTheta = Math.Cos(Theta);
                var sinTheta = Math.Sin(Theta);
                for (var i = 0; i <= nPhi; i++)
                {
                    var Phi = i * dPhi;
                    var cosPhi = Math.Cos(Phi);
                    var sinPhi = Math.Sin(Phi);


                    ((IArray<float>)(object)pt0).push((float)(r * cosPhi * sinTheta));
                    ((IArray<float>)(object)pt0).push((float)(-r * sinPhi * sinTheta));
                    ((IArray<float>)(object)pt0).push((float)(r * cosTheta));

                    ((IArray<float>)(object)nt).push((float)(cosPhi * sinTheta));
                    ((IArray<float>)(object)nt).push((float)(-sinPhi * sinTheta));
                    ((IArray<float>)(object)nt).push((float)(cosTheta));
                }
            }
            var n1 = nPhi + 1;
            var off = 24;
            for (var i = 0; i < nTheta; i++)
                for (var j = 0; j < nPhi; j++)
                {
                    ((IArray<int>)(object)ind).push(i * n1 + j + off);
                    ((IArray<int>)(object)ind).push((i + 1) * n1 + j + 1 + off);
                    ((IArray<int>)(object)ind).push(i * n1 + j + 1 + off);
                    ((IArray<int>)(object)ind).push(i * n1 + j + off);
                    ((IArray<int>)(object)ind).push((i + 1) * n1 + j + off);
                    ((IArray<int>)(object)ind).push((i + 1) * n1 + j + 1 + off);
                }
            #endregion

            gl.enableVertexAttribArray(posLoc);
            gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(pt0), gl.STATIC_DRAW);
            gl.vertexAttribPointer(posLoc, 3, gl.FLOAT, false, 0, 0);

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

            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(ind),
              gl.STATIC_DRAW);

            var prMatrix = new CanvasMatrix4();
            prMatrix.perspective(45f, 1f, .1f, 100f);

            gl.uniformMatrix4fv(gl.getUniformLocation(prog, "prMatrix"),
                  false, new Float32Array(prMatrix.getAsArray()));

            var mvMatrix = new CanvasMatrix4();
            var rotMat = new CanvasMatrix4();
            rotMat.makeIdentity();
            rotMat.rotate(25, 1, 1, 0);

            var mvMatLoc = gl.getUniformLocation(prog, "mvMatrix");
            var colorLoc = gl.getUniformLocation(prog, "u_color");

            var line_prog = gl.createProgram(
                new LineVertexShader(),
                new LineFragmentShader()
                );


            var lineLoc = 2U;
            gl.bindAttribLocation(line_prog, lineLoc, "aPos");

            gl.linkProgram(line_prog);
            gl.useProgram(line_prog);
            gl.uniformMatrix4fv(gl.getUniformLocation(line_prog, "prMatrix"),
               false, new Float32Array(prMatrix.getAsArray()));
            var mvMatLineLoc = gl.getUniformLocation(line_prog, "mvMatrix");

            var pt1 = new float[]{2,1,1, -2,1,1, 2,-1,1, -2,-1,1, 2,1,-1, -2,1,-1, 2,-1,-1, -2,-1,-1,
                1,2,1, 1,-2,1, 1,2,-1, 1,-2,-1, -1,2,1, -1,-2,1, -1,2,-1, -1,-2,-1, 
                1,1,2, 1,1,-2, -1,1,2, -1,1,-2, 1,-1,2, 1,-1,-2, -1,-1,2, -1,-1,-2
            };

            gl.enableVertexAttribArray(lineLoc);
            gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(pt1), gl.STATIC_DRAW);
            gl.vertexAttribPointer(lineLoc, 3, gl.FLOAT, false, 0, 0);

            gl.enable(gl.DEPTH_TEST);
            gl.depthFunc(gl.LEQUAL);
            gl.clearDepth(1.0f);
            gl.clearColor(.5f, 1f, .5f, 1f);
            gl.lineWidth(2);

            var xOffs = 0;
            var yOffs = 0;
            var drag = 0;
            var xRot = 0f;
            var yRot = 0f;
            var transl = -6.0f;


            #region drawBall
            Action<f, f, f> drawBall = (x, y, z) =>
            {
                mvMatrix.makeIdentity();
                mvMatrix.translate(x, y, z);
                mvMatrix.multRight(rotMat);
                mvMatrix.translate(0, 0, transl);
                gl.uniformMatrix4fv(mvMatLoc, false,
                  new Float32Array(mvMatrix.getAsArray()));

                gl.drawElements(gl.TRIANGLES, 6 * nPhi * nTheta, gl.UNSIGNED_SHORT, 72);
            };
            #endregion

            #region drawScene
            Action drawScene = delegate
            {


                gl.viewport(0, 0, gl_viewportWidth, gl_viewportWidth);
                gl.useProgram(prog);


                gl.uniformMatrix4fv(gl.getUniformLocation(prog, "prMatrix"),
   false, new Float32Array(prMatrix.getAsArray()));

                rotMat.rotate(xRot / 5, 1, 0, 0);
                rotMat.rotate(yRot / 5, 0, 1, 0);

                yRot = 0;
                xRot = 0;

                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

                gl.uniform4f(colorLoc, 1, 1, 0, 1);

                drawBall(1, 1, 1); drawBall(-1, 1, 1); drawBall(1, -1, 1);
                drawBall(1, 1, -1); drawBall(-1, -1, 1); drawBall(-1, 1, -1);
                drawBall(1, -1, -1); drawBall(-1, -1, -1);

                mvMatrix.load(rotMat);
                mvMatrix.translate(0, 0, transl);

                gl.uniformMatrix4fv(mvMatLoc, false,
                  new Float32Array(mvMatrix.getAsArray()));

                gl.enable(gl.BLEND);
                gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
                gl.uniform4f(colorLoc, .0f, .0f, .9f, .7f);
                gl.depthMask(false);
                gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0);
                gl.depthMask(true);
                gl.disable(gl.BLEND);



                gl.useProgram(line_prog);

                gl.uniformMatrix4fv(gl.getUniformLocation(line_prog, "prMatrix"),
   false, new Float32Array(prMatrix.getAsArray()));

                gl.uniformMatrix4fv(mvMatLineLoc, false,
                  new Float32Array(mvMatrix.getAsArray()));
                gl.drawArrays(gl.LINES, 0, 24);

                gl.flush();
            };
            #endregion


            drawScene();



            #region AtResize
            Action AtResize = delegate
            {
                gl_viewportWidth = Native.window.Width;
                gl_viewportHeight = Native.window.Height;

                prMatrix = new CanvasMatrix4();

                //var aspect = (f)gl_viewportWidth / (f)gl_viewportHeight;
                var aspect = Native.window.aspect;

                Console.WriteLine(
                    new { gl_viewportWidth, gl_viewportHeight, aspect }
                    );
                //Native.document.title = new { aspect }.ToString();

                prMatrix.perspective(45f, (f)aspect, 1f, 100f);


                canvas.style.SetLocation(0, 0, gl_viewportWidth, gl_viewportHeight);

                canvas.width = gl_viewportWidth;
                canvas.height = gl_viewportHeight;

                drawScene();
            };

            AtResize();

            Native.window.onresize += delegate
            {
                AtResize();
            };
            #endregion


            #region mouse
            canvas.onmousedown += ev =>
            {
                ev.PreventDefault();

                drag = 1;
                xOffs = ev.CursorX;
                yOffs = ev.CursorY;

                canvas.requestPointerLock();
            };

            canvas.onmouseup += ev =>
            {
                ev.PreventDefault();


                drag = 0;
                xOffs = ev.CursorX;
                yOffs = ev.CursorY;

                Native.Document.exitPointerLock();
            };

            canvas.onmousemove += ev =>
            {
                if (drag == 0)
                    return;

                if (Native.Document.pointerLockElement == canvas)
                {
                    xRot += ev.movementY;
                    yRot += ev.movementX;
                    drawScene();

                    return;
                }

                ev.PreventDefault();

                if (ev.shiftKey)
                {
                    transl *= 1 + (ev.CursorY - yOffs) / 1000;
                    yRot = -xOffs + ev.CursorX;
                }
                else
                {
                    yRot = -xOffs + ev.CursorX;
                    xRot = -yOffs + ev.CursorY;
                }

                xOffs = ev.CursorX;
                yOffs = ev.CursorY;
                drawScene();
            };
            #endregion

            #region onmousewheel
            canvas.onmousewheel +=
                ev =>
                {
                    var del = 1.1f;

                    if (ev.shiftKey)
                        del = 1.01f;

                    if (ev.WheelDirection > 0)
                        transl *= del;
                    else
                        transl *= (1 / del);

                    drawScene();



                    ev.PreventDefault();
                };
            #endregion


            #region IsDisposed
            var IsDisposed = false;

            this.Dispose = delegate
            {
                if (IsDisposed)
                    return;

                IsDisposed = true;

                canvas.Orphanize();
            };
            #endregion


            #region requestFullscreen
            Native.Document.body.ondblclick +=
                delegate
                {
                    if (IsDisposed)
                        return;

                    // http://tutorialzine.com/2012/02/enhance-your-website-fullscreen-api/

                    canvas.requestFullscreen();


                };
            #endregion

            #region tick

            Native.window.onframe += delegate
            {
                if (IsDisposed)
                    return;

                if (drag == 0)
                {
                    xRot += 2;
                    yRot += 3;
                }

                drawScene();
                //animate();

            };

            #endregion



            //Native.Document.body.style.backgroundColor = Color.FromRGB(0x80, 0xFF, 0x80);
        }
        /// <summary>
        /// This is a javascript application.
        /// </summary>
        /// <param name="page">HTML document rendered by the web server which can now be enhanced.</param>
        public Application(IDefault page = null)
        {


            var gl_viewportWidth = 500;
            var gl_viewportHeight = 500;


            var gl = new WebGLRenderingContext();





            #region canvas
            var canvas = gl.canvas.AttachToDocument();

            Native.document.body.style.overflow = IStyle.OverflowEnum.hidden;
            canvas.style.SetLocation(0, 0, gl_viewportWidth, gl_viewportHeight);

            canvas.width = gl_viewportWidth;
            canvas.height = gl_viewportHeight;
            #endregion




            var h = 1f;
            var r1 = .5f;
            var r2 = .2f;
            var nPhi = 500;


            var prog = gl.createProgram();


            #region createShader
            Func<ScriptCoreLib.GLSL.Shader, WebGLShader> createShader = (src) =>
            {
                var shader = gl.createShader(src);

                // verify
                if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) == null)
                {
                    Native.window.alert("error in SHADER:\n" + gl.getShaderInfoLog(shader));
                    throw new InvalidOperationException("shader failed");
                }

                return shader;
            };
            #endregion

            var vs = createShader(new GeometryVertexShader());
            var fs = createShader(new GeometryFragmentShader());


            gl.attachShader(prog, vs);
            gl.attachShader(prog, fs);


            gl.linkProgram(prog);
            gl.useProgram(prog);



            var pt = new IArray<float>();
            var nt = new IArray<float>();
            var Phi = 0.0;
            var dPhi = 2 * Math.PI / (nPhi - 1);

            var Nx = r1 - r2;
            var Ny = h;
            var N = (float)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);
                pt.push((float)(cosPhi * r1));
                pt.push((float)(sinPhi * r1));   // points

                nt.push(Nx);
                nt.push((float)(Ny * cosPhi));
                nt.push((float)(Ny * sinPhi));         // normals

                pt.push(h / 2);
                pt.push((float)(cosPhi2 * r2));
                pt.push((float)(sinPhi2 * r2));  // points

                nt.push(Nx);
                nt.push((float)(Ny * cosPhi2));
                nt.push((float)(Ny * sinPhi2));       // normals

                Phi += dPhi;
            }

            var posLoc = gl.getAttribLocation(prog, "aPos");
            gl.enableVertexAttribArray((uint)posLoc);
            gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(pt.ToArray()), gl.STATIC_DRAW);
            gl.vertexAttribPointer((uint)posLoc, 3, gl.FLOAT, false, 0, 0);

            var normLoc = gl.getAttribLocation(prog, "aNorm");
            gl.enableVertexAttribArray((uint)normLoc);
            gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(nt), gl.STATIC_DRAW);
            gl.vertexAttribPointer((uint)normLoc, 3, gl.FLOAT, false, 0, 0);

            var prMatrix = new CanvasMatrix4();

            gl.uniformMatrix4fv(gl.getUniformLocation(prog, "prMatrix"),
               false, new Float32Array(prMatrix.getAsArray()));

            var mvMatrix = new CanvasMatrix4();
            var rotMat = new CanvasMatrix4();
            rotMat.makeIdentity();
            rotMat.rotate(-40, 0, 1, 0);
            var mvMatLoc = gl.getUniformLocation(prog, "mvMatrix");

            gl.enable(gl.DEPTH_TEST);
            gl.depthFunc(gl.LEQUAL);
            gl.clearDepth(1.0f);
            gl.clearColor(0, 0, .5f, 1);

            var xOffs = 0;
            var yOffs = 0;
            var drag = 0;
            var xRot = 0;
            var yRot = 0;
            var transl = -1.5f;

            Action drawScene = delegate
            {

                gl.uniformMatrix4fv(gl.getUniformLocation(prog, "prMatrix"),
   false, new Float32Array(prMatrix.getAsArray()));


                gl.viewport(0, 0, gl_viewportWidth, gl_viewportHeight);
                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);


                rotMat.rotate(xRot / 5, 1, 0, 0);
                rotMat.rotate(yRot / 5, 0, 1, 0);

                yRot = 0;
                xRot = 0;

                mvMatrix.load(rotMat);
                mvMatrix.translate(0, 0, transl);

                gl.uniformMatrix4fv(mvMatLoc, false,
                  new Float32Array(mvMatrix.getAsArray()));
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 2 * nPhi);
                gl.flush();
            };



            #region IsDisposed
            var IsDisposed = false;

            this.Dispose = delegate
            {
                if (IsDisposed)
                    return;

                IsDisposed = true;

                canvas.Orphanize();
            };
            #endregion


            #region requestFullscreen
            Native.Document.body.ondblclick +=
                delegate
                {
                    if (IsDisposed)
                        return;

                    // http://tutorialzine.com/2012/02/enhance-your-website-fullscreen-api/

                    Native.Document.body.requestFullscreen();


                };
            #endregion


            var c = 0;





            Native.window.onframe += delegate
            {
                if (IsDisposed)
                    return;

                c++;

                xRot += 2;
                yRot += 3;

                Native.document.title = "" + c;

                drawScene();
                //animate();

            };




            #region AtResize
            Action AtResize =
                delegate
                {
                    gl_viewportWidth = Native.window.Width;
                    gl_viewportHeight = Native.window.Height;

                    prMatrix = new CanvasMatrix4();
                    prMatrix.perspective(45f, (f)gl_viewportWidth / (f)gl_viewportHeight, 1f, 100f);


                    canvas.style.SetLocation(0, 0, gl_viewportWidth, gl_viewportHeight);

                    canvas.width = gl_viewportWidth;
                    canvas.height = gl_viewportHeight;
                };

            Native.window.onresize +=
                e =>
                {
                    AtResize();
                };
            AtResize();
            #endregion



        }
        /* Source: http://www.ibiblio.org/e-notes/webgl/models/ethanol.html
         * 
         */


        /// <summary>
        /// This is a javascript application.
        /// </summary>
        /// <param name="page">HTML document rendered by the web server which can now be enhanced.</param>
        public Application(IDefault page = null)
        {
            var gl_viewportWidth = 500;
            var gl_viewportHeight = 500;




            var gl = new WebGLRenderingContext();

            var canvas = gl.canvas.AttachToDocument();

            Native.document.body.style.overflow = IStyle.OverflowEnum.hidden;
            canvas.style.SetLocation(0, 0, gl_viewportWidth, gl_viewportHeight);

            canvas.width = gl_viewportWidth;
            canvas.height = gl_viewportHeight;




            var h = 1f;
            var r1 = .5f;
            var r2 = .2f;


            var prog = gl.createProgram();


            #region createShader
            Func<ScriptCoreLib.GLSL.Shader, WebGLShader> createShader = (src) =>
            {
                var shader = gl.createShader(src);

                // verify
                if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) == null)
                {
                    Native.window.alert("error in SHADER:\n" + gl.getShaderInfoLog(shader));
                    throw new InvalidOperationException("shader failed");
                }

                return shader;
            };
            #endregion

            var vs = createShader(new GeometryVertexShader());
            var fs = createShader(new GeometryFragmentShader());


            gl.attachShader(prog, vs);
            gl.attachShader(prog, fs);


            gl.linkProgram(prog);
            gl.useProgram(prog);

            var nPhi = 100;
            var nTheta = 50;
            var dPhi = 2 * Math.PI / nPhi;
            var dTheta = Math.PI / nTheta;

            var vertices = new IArray<float>();
            var ind = new IArray<ushort>();

            for (var j = 0; j <= nTheta; j++)
            {
                var Theta = j * dTheta;
                var cosTheta = Math.Cos(Theta);
                var sinTheta = Math.Sin(Theta);
                for (var i = 0; i <= nPhi; i++)
                {
                    var Phi = i * dPhi;
                    var cosPhi = Math.Cos(Phi);
                    var sinPhi = Math.Sin(Phi);
                    vertices.push((f)(cosPhi * sinTheta));
                    vertices.push((f)(-sinPhi * sinTheta));
                    vertices.push((f)(cosTheta));
                }
            }
            for (var j = 0; j < nTheta; j++)
                for (var i = 0; i <= nPhi; i++)
                {
                    ind.push((ushort)(j * (nPhi + 1) + i));
                    ind.push((ushort)((j + 1) * (nPhi + 1) + i));
                }
            var posLocation = gl.getAttribLocation(prog, "aPos");
            gl.enableVertexAttribArray((uint)posLocation);
            var posBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
            gl.vertexAttribPointer((uint)posLocation, 3, gl.FLOAT, false, 0, 0);

            var indexBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(ind.ToArray()),
              gl.STATIC_DRAW);

            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");
            var colorLoc = gl.getUniformLocation(prog, "color");
            var scaleLoc = gl.getUniformLocation(prog, "scale");

            gl.enable(gl.DEPTH_TEST);
            gl.depthFunc(gl.LEQUAL);
            gl.clearDepth(1.0f);
            gl.clearColor(0, 0, .8f, 1f);

            var xOffs = 0;
            var yOffs = 0;
            var drag = 0;
            var xRot = 0f;
            var yRot = 0f;
            var transl = -10.5f;

            #region drawBall
            Action<f, f, f, f, f, f, f> drawBall = (x, y, z, r, g, b, _scale) =>
            {
                var scale = _scale * 1f;

                mvMatrix.makeIdentity();
                mvMatrix.translate(x, y, z);
                mvMatrix.multRight(rotMat);
                mvMatrix.translate(0, 0, transl);
                gl.uniformMatrix4fv(mvMatLoc, false, new Float32Array(mvMatrix.getAsArray()));
                gl.uniform1f(scaleLoc, scale);
                gl.uniform3f(colorLoc, r, g, b);
                for (var i = 0; i < nTheta; i++)
                    gl.drawElements(gl.TRIANGLE_STRIP, 2 * (nPhi + 1), gl.UNSIGNED_SHORT,
                      4 * (nPhi + 1) * i);
            };
            #endregion

            Action<f, f, f, f> drawBall_white = (x, y, z, _scale) =>
                drawBall(x, y, z, 1, 1, 1, _scale);


            Action<f, f, f, f> drawBall_red = (x, y, z, _scale) =>
                drawBall(x, y, z, 1, 0, 0, _scale);


            #region drawScene
            Action drawScene = delegate
            {
                gl.viewport(0, 0, gl_viewportWidth, gl_viewportHeight);

                #region prMatrix
                gl.uniformMatrix4fv(gl.getUniformLocation(prog, "prMatrix"),
false, new Float32Array(prMatrix.getAsArray()));
                #endregion

                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
                rotMat.rotate(xRot / 3, 1, 0, 0); rotMat.rotate(yRot / 3, 0, 1, 0);
                yRot = 0; xRot = 0;
                drawBall(0, 0, 0, .3f, .3f, .3f, 1.5f);
                drawBall(1, 1, 1, .3f, .3f, .3f, 1.5f);

                drawBall_white(2, 2, 0, 1);
                drawBall_white(2, 0, 2, 1);
                drawBall_white(0, 2, 2, 1);
                drawBall_white(-1, -1, 1, 1);
                drawBall_white(1, -1, -1, 1);

                drawBall_red(-1, 1, -1, 1.5f);

                drawBall_white(-2, 0, -2, 1);
                gl.flush();
            };
            #endregion

            #region mouse
            canvas.onmousedown += ev =>
            {
                ev.PreventDefault();

                drag = 1;
                xOffs = ev.CursorX;
                yOffs = ev.CursorY;
            };

            canvas.onmouseup += ev =>
            {
                ev.PreventDefault();


                drag = 0;
                xOffs = ev.CursorX;
                yOffs = ev.CursorY;
            };

            canvas.onmousemove += ev =>
            {
                if (drag == 0)
                    return;
                ev.PreventDefault();

                if (ev.shiftKey)
                {
                    transl *= 1 + (ev.CursorY - yOffs) / 1000;
                    yRot = -xOffs + ev.CursorX;
                }
                else
                {
                    yRot = -xOffs + ev.CursorX;
                    xRot = -yOffs + ev.CursorY;
                }

                xOffs = ev.CursorX;
                yOffs = ev.CursorY;
                drawScene();
            };
            #endregion

            #region onmousewheel
            canvas.onmousewheel +=
                ev =>
                {
                    var del = 1.1f;

                    if (ev.shiftKey)
                        del = 1.01f;

                    if (ev.WheelDirection > 0)
                        transl *= del;
                    else
                        transl *= (1 / del);

                    drawScene();



                    ev.PreventDefault();
                };
            #endregion




            #region IsDisposed
            var IsDisposed = false;

            this.Dispose = delegate
            {
                if (IsDisposed)
                    return;

                IsDisposed = true;

                canvas.Orphanize();
            };
            #endregion


            #region requestFullscreen
            Native.Document.body.ondblclick +=
                delegate
                {
                    if (IsDisposed)
                        return;

                    // http://tutorialzine.com/2012/02/enhance-your-website-fullscreen-api/

                    Native.Document.body.requestFullscreen();


                };
            #endregion


            var c = 0;
            Native.window.onframe += delegate
            {
                if (IsDisposed)
                    return;

                c++;

                xRot += 0.2f;
                yRot += 0.3f;


                drawScene();

            };




            #region AtResize
            Action AtResize =
                delegate
                {
                    gl_viewportWidth = Native.window.Width;
                    gl_viewportHeight = Native.window.Height;

                    prMatrix = new CanvasMatrix4();
                    prMatrix.perspective(45f, (f)Native.window.aspect, 1f, 100f);

                    canvas.style.SetLocation(0, 0, gl_viewportWidth, gl_viewportHeight);

                    canvas.width = gl_viewportWidth;
                    canvas.height = gl_viewportHeight;
                };

            Native.window.onresize +=
                e =>
                {
                    AtResize();
                };
            AtResize();
            #endregion


            new IHTMLAnchor { "drag me to my.jsc-solutions.net" }.AttachToDocument().With(
                dragme =>
                {
                    dragme.style.position = [email protected];
                    dragme.style.left = "1em";
                    dragme.style.bottom = "1em";

                    dragme.AllowToDragAsApplicationPackage();
                }
            );
        }