/// <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(IApp page = null)
        {
            // 20140704 no balls shown?
            // broken?
            // view-source:http://www.mrdoob.com/lab/javascript/beachballs/
            //Action Toggle = DiagnosticsConsole.ApplicationContent.BindKeyboardToDiagnosticsConsole();


            var origin = new THREE.Vector3(0, 15, 0);
            var isMouseDown = false;



            var renderer = new THREE.WebGLRenderer(
                new
                {
                    antialias = true,
                    alpha = false
                });
            renderer.setClearColor(new THREE.Color(0x101010));

            renderer.domElement.AttachToDocument();



            // scene

            var camera = new THREE.PerspectiveCamera(
                40,
                Native.window.aspect, 1,
                1000
                );

            camera.position.x = -30;
            camera.position.y = 10;
            camera.position.z = 30;
            camera.lookAt(new THREE.Vector3(0, 10, 0));


            #region AtResize
            Action AtResize = delegate
            {
                camera.aspect = (double)Native.window.aspect;
                camera.updateProjectionMatrix();
                renderer.setSize(Native.window.Width, Native.window.Height);
            };
            Native.window.onresize +=
              delegate
              {
                  AtResize();
              };

            AtResize();
            #endregion


            var scene = new THREE.Scene();

            var light = new THREE.HemisphereLight(0xffffff, 0x606060, 1.2);
            light.position.set(-10, 10, 10);
            scene.add(light);

            {
                var geometry = new THREE.CubeGeometry(20, 20, 20);
                var material = new THREE.MeshBasicMaterial(
                    new { wireframe = true, opacity = 0.1, transparent = true });
                var mesh = new THREE.Mesh(geometry, material);
                mesh.position.y = 10;
                scene.add(mesh);
            }

            var intersectionPlane = new THREE.Mesh(new THREE.PlaneGeometry(20, 20, 8, 8));
            intersectionPlane.position.y = 10;
            intersectionPlane.visible = false;
            scene.add(intersectionPlane);

            // geometry

            var ballGeometry = new THREE.Geometry();

            var ballMaterial = new THREE.MeshPhongMaterial(

                new __MeshPhongMaterialDictionary
                {
                    vertexColors = THREE.FaceColors,
                    specular = 0x808080,
                    shininess = 2000
                }
             );

            //

            var colors = new[] {
                    new THREE.Color( 0xe52b30 ),
                    new THREE.Color( 0xe52b30 ),
                    new THREE.Color( 0x2e1b6a ),
                    new THREE.Color( 0xdac461 ),
                    new THREE.Color( 0xf07017 ),
                    new THREE.Color( 0x38b394 ),
                    new THREE.Color( 0xeaf1f7 )
                };

            var amount = colors.Length;

            var geometryTop = new THREE.SphereGeometry(1, 5 * amount, 2, 0, Math.PI * 2.0, 0, 0.30);

            for (var j = 0; j < geometryTop.faces.Length; j++)
            {

                geometryTop.faces[j].color = colors[0];

            }

            THREE.GeometryUtils.merge(ballGeometry, geometryTop);

            var geometryBottom = new THREE.SphereGeometry(1, 5 * amount, 2, 0, Math.PI * 2, Math.PI - 0.30, 0.30);

            for (var j = 0; j < geometryBottom.faces.Length; j++)
            {

                geometryBottom.faces[j].color = colors[0];

            }

            THREE.GeometryUtils.merge(ballGeometry, geometryBottom);

            {
                var sides = amount - 1;
                var size = (Math.PI * 2) / sides;

                for (var i = 0; i < sides; i++)
                {

                    var patch = new THREE.SphereGeometry(1, 5, 10, i * size, size, 0.30, Math.PI - 0.60);

                    for (var j = 0; j < patch.faces.Length; j++)
                    {

                        patch.faces[j].color = colors[i + 1];

                    }

                    THREE.GeometryUtils.merge(ballGeometry, patch);

                }

            }
            // physics

            var world = new CANNON.World();
            world.broadphase = new CANNON.NaiveBroadphase();
            world.gravity.set(0, -15, 0);
            world.solver.iterations = 7;
            world.solver.tolerance = 0.1;

            var groundShape = new CANNON.Plane();
            var groundMaterial = new CANNON.Material();
            var groundBody = new CANNON.RigidBody(0, groundShape, groundMaterial);
            groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2.0);
            world.add(groundBody);

            var planeShapeXmin = new CANNON.Plane();
            var planeXmin = new CANNON.RigidBody(0, planeShapeXmin, groundMaterial);
            planeXmin.quaternion.setFromAxisAngle(new CANNON.Vec3(0, 1, 0), Math.PI / 2.0);
            planeXmin.position.set(-10, 0, 0);
            world.add(planeXmin);

            var planeShapeXmax = new CANNON.Plane();
            var planeXmax = new CANNON.RigidBody(0, planeShapeXmax, groundMaterial);
            planeXmax.quaternion.setFromAxisAngle(new CANNON.Vec3(0, 1, 0), -Math.PI / 2.0);
            planeXmax.position.set(10, 0, 0);
            world.add(planeXmax);

            var planeShapeYmin = new CANNON.Plane();
            var planeZmin = new CANNON.RigidBody(0, planeShapeYmin, groundMaterial);
            planeZmin.position.set(0, 0, -10);
            world.add(planeZmin);

            var planeShapeYmax = new CANNON.Plane();
            var planeZmax = new CANNON.RigidBody(0, planeShapeYmax, groundMaterial);
            planeZmax.quaternion.setFromAxisAngle(new CANNON.Vec3(0, 1, 0), Math.PI);
            planeZmax.position.set(0, 0, 10);
            world.add(planeZmax);

            var ballBodyMaterial = new CANNON.Material();
            world.addContactMaterial(new CANNON.ContactMaterial(groundMaterial, ballBodyMaterial, 0.2, 0.5));
            world.addContactMaterial(new CANNON.ContactMaterial(ballBodyMaterial, ballBodyMaterial, 0.2, 0.8));



            var spheres = new Queue<THREE.Mesh>();
            var bodies = new Queue<CANNON.RigidBody>();

            Func<double> random = new Random().NextDouble;


            #region addBall
            Action<double, double, double> addBall = (x, y, z) =>
            {

                x = Math.Max(-10, Math.Min(10, x));
                y = Math.Max(5, y);
                z = Math.Max(-10, Math.Min(10, z));

                var size = 1.25;

                var sphere = new THREE.Mesh(ballGeometry, ballMaterial);
                sphere.scale.multiplyScalar(size);
                //sphere.useQuaternion = true;
                scene.add(sphere);

                spheres.Enqueue(sphere);

                var sphereShape = new CANNON.Sphere(size);
                var sphereBody = new CANNON.RigidBody(0.1, sphereShape, ballBodyMaterial);
                sphereBody.position.set(x, y, z);

                var q = new
                {
                    a = random() * 3.0,
                    b = random() * 3.0,
                    c = random() * 3.0,
                    d = random() * 3.0
                };
                Console.WriteLine("addBall " + new { x, y, z, q });

                //sphereBody.quaternion.set(q.a, q.b, q.c, q.d);
                world.add(sphereBody);

                bodies.Enqueue(sphereBody);

            };
            #endregion


            for (var i = 0; i < 100; i++)
            {

                addBall(
                   random() * 10 - 5,
                   random() * 20,
                   random() * 10 - 5
                );

            }

            //

            var projector = new THREE.Projector();
            var ray = new THREE.Raycaster();
            var mouse3D = new THREE.Vector3();

            Native.Document.body.ontouchstart +=
               e =>
               {
                   e.preventDefault();
                   isMouseDown = true;
               };


            Native.Document.body.ontouchmove +=
               e =>
               {
                   e.preventDefault();
               };

            Native.Document.body.ontouchend +=
            e =>
            {
                e.preventDefault();
                isMouseDown = false;
            };

            #region onmousemove
            Native.document.body.onmousedown +=
                e =>
                {
                    e.preventDefault();
                    isMouseDown = true;
                };
            Native.document.body.onmouseup +=
                 e =>
                 {
                     e.preventDefault();
                     isMouseDown = false;



                     if (e.MouseButton == IEvent.MouseButtonEnum.Middle)
                     {
                         if (Native.Document.pointerLockElement == Native.Document.body)
                         {
                             // cant requestFullscreen while pointerLockElement
                             Console.WriteLine("exitPointerLock");
                             Native.Document.exitPointerLock();
                             Native.Document.exitFullscreen();
                             return;
                         }

                         Console.WriteLine("requestFullscreen");
                         Native.Document.body.requestFullscreen();
                         Native.Document.body.requestPointerLock();
                         return;
                     }
                 };
            Native.document.body.onmousemove +=
               e =>
               {

                   mouse3D.set(
                       ((double)e.CursorX / (double)Native.window.Width) * 2 - 1,
                       -((double)e.CursorY / (double)Native.window.Height) * 2 + 1,
                       0.5
                   );

                   projector.unprojectVector(mouse3D, camera);

                   ray.set(camera.position, mouse3D.sub(camera.position).normalize());

                   var intersects = ray.intersectObject(intersectionPlane);

                   if (intersects.Length > 0)
                   {

                       origin.copy(intersects[0].point);

                   }
               };
            #endregion








            #region removeBall
            Action removeBall = delegate
            {

                scene.remove(spheres.Dequeue());
                world.remove(bodies.Dequeue());

            };
            #endregion




            var sw0 = Stopwatch.StartNew();
            var sw = Stopwatch.StartNew();
            //var time = Native.window.performance.now();
            //var lastTime = Native.window.performance.now();

            #region animate
            Action render =
                delegate
                {

                    var delta = sw.ElapsedMilliseconds;
                    sw.Restart();

                    //time = Native.window.performance.now();

                    camera.position.x = -Math.Cos(sw.ElapsedMilliseconds * 0.0001) * 40;
                    camera.position.z = Math.Sin(sw.ElapsedMilliseconds * 0.0001) * 40;
                    camera.lookAt(new THREE.Vector3(0, 10, 0));

                    intersectionPlane.lookAt(camera.position);

                    world.step(delta * 0.001);
                    //lastTime = time;

                    for (var i = 0; i < spheres.Count; i++)
                    {

                        var sphere = spheres.ElementAt(i);
                        var body = bodies.ElementAt(i);

                        sphere.position.copy(body.position);
                        sphere.quaternion.copy(body.quaternion);

                    }

                    renderer.render(scene, camera);

                };



            Native.window.onframe += delegate
            {


                if (isMouseDown)
                {

                    if (spheres.Count > 200)
                    {

                        removeBall();

                    }

                    addBall(
                        origin.x + (random() * 4.0 - 2),
                        origin.y + (random() * 4.0 - 2),
                        origin.z + (random() * 4.0 - 2)
                    );

                }

                render();

            };

            #endregion


        }
        /// <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(IApp page = null)
        {
            InitializeComponent();

            // http://johnstejskal.com/wp/super-hero-city-my-3d-webgl-game-using-three-js/
            // http://www.johnstejskal.com/dev/super-hero-city/

            //DiagnosticsConsole.ApplicationContent.BindKeyboardToDiagnosticsConsole();


            // decent simcity comes to mind
            // http://www.mrdoob.com/lab/javascript/webgl/city/01/

            //var renderer = this.renderer.renderer;

            var renderer = new THREE.WebGLRenderer(
                new { antialias = false, alpha = false }
            );

            renderer.setClearColor(new THREE.Color(0xd8e7ff));

            renderer.setSize(Native.window);

            // INodeConvertible?
            renderer.domElement.AttachToDocument();
            renderer.domElement.style.SetLocation(0, 0);

            var camera = new THREE.PerspectiveCamera(40, Native.window.aspect, 1, 3000);
            camera.position.y = 80;

            var scene = new THREE.Scene();
            scene.fog = new THREE.FogExp2(0xd0e0f0, 0.0025);


            {
                var light = new THREE.HemisphereLight(0xfffff0, 0x101020, 1.25);
                light.position.set(0.75, 1, 0.25);
                scene.add(light);
            }



            var plane = new THREE.Mesh(
                new THREE.PlaneGeometry(2000, 2000),
                new THREE.MeshBasicMaterial(new { color = 0x101018 })
            );

            plane.rotation.x = -90 * Math.PI / 180;
            scene.add(plane);







            var building_geometry = new THREE.CubeGeometry(1, 1, 1);
            building_geometry.applyMatrix(new THREE.Matrix4().makeTranslation(0, 0.5, 0));

            ((IArray<THREE.Face4>)(object)building_geometry.faces).splice(3, 1);

            ((IArray<object>)(object)building_geometry.faceVertexUvs[0]).splice(3, 1);

            building_geometry.faceVertexUvs[0][2][0].set(0, 0);
            building_geometry.faceVertexUvs[0][2][1].set(0, 0);
            building_geometry.faceVertexUvs[0][2][2].set(0, 0);

            // Uncaught TypeError: Cannot call method 'set' of undefined view-source:84609
            //building_geometry.faceVertexUvs[0][2][3].set(0, 0);


            Func<double> random = new Random().NextDouble;


            var building = new THREE.Mesh(building_geometry);
            var city = new THREE.Geometry();

            {
                var light = new THREE.Color(0xffffff);
                var shadow = new THREE.Color(0x303050);

                #region city
                for (var i = 0; i < 20000; i++)
                {

                    var value = 1 - random() * random();
                    var color = new THREE.Color(0).setRGB(value + random() * 0.1, value, value + random() * 0.1);

                    var top = color.clone().multiply(light);
                    var bottom = color.clone().multiply(shadow);

                    building.position.x = Math.Floor(random() * 200 - 100) * 10;
                    building.position.z = Math.Floor(random() * 200 - 100) * 10;
                    building.rotation.y = random();

                    building.scale.z = random() * random() * random() * random() * 50 + 10;
                    building.scale.x = building.scale.z;

                    building.scale.y = (random() * random() * random() * building.scale.x) * 8 + 8;

                    var geometry = building.geometry;

                    var jl = geometry.faces.Length;

                    for (var j = 0; j < jl; j++)
                    {
                        if (j == 2)
                        {
                            geometry.faces[j].vertexColors = new[] { color, color, color, color };
                        }
                        else
                        {
                            geometry.faces[j].vertexColors = new[] { top, bottom, bottom, top };
                        }
                    }

                    // THREE.GeometryUtils: .merge() has been moved to Geometry. Use geometry.merge( geometry2, matrix, materialIndexOffset ) instead.
                    // stop moving around code!


                    //city.merge
                    //city.merge(building.geometry);
                    // how??
                    THREE.GeometryUtils.merge(city, building);

                }
                #endregion

                #region generateTexture
                Func<IHTMLCanvas> generateTexture = delegate
                {


                    var context1 = new CanvasRenderingContext2D(32, 64);

                    context1.fillStyle = "#ffffff";
                    context1.fillRect(0, 0, 32, 64);

                    for (var y = 2; y < 64; y += 2)
                        for (var x = 0; x < 32; x += 2)
                        {

                            var value = Math.Floor(random() * 64);
                            context1.fillStyle = "rgb(" + value + "," + value + "," + value + ")";
                            context1.fillRect(x, y, 2, 1);

                        }


                    var context = new CanvasRenderingContext2D(512, 1024)
                    {
                        ImageSmoothingEnabled = false
                    };


                    context.drawImage(context1.canvas, 0, 0, context.canvas.width, context.canvas.height);

                    return context.canvas;

                };
                #endregion

                var texture = new THREE.Texture(generateTexture())
                {
                    anisotropy = renderer.getMaxAnisotropy(),
                    needsUpdate = true
                };

                var mesh = new THREE.Mesh(city, new THREE.MeshLambertMaterial(new { map = texture, vertexColors = THREE.VertexColors }));
                scene.add(mesh);
            }


            var controls = new THREE.FirstPersonControls(camera)
            {
                movementSpeed = 20,
                lookSpeed = 0.05,
                lookVertical = true
            };


            //var lastTime = Native.window.performance.now() / 1000;
            var delta = new Stopwatch();

            Native.window.onframe +=
                delegate
                {
                    //var time = Native.window.performance.now() / 1000;

                    controls.update(delta.ElapsedMilliseconds);
                    renderer.render(scene, camera);

                    //lastTime = time;
                };
        }