Maze 3D. Experimenting with THREE.js

Hello all! Today, as promised in my previous post, we will elevate to 3D the maze we created with the deep search algorithm. For that, I’m going to use a recent discovery to me, THREE.js, a WebGL library. As I had seen, this is a very very complete 3D library with all uses (textures, shaders, particles, transformations, etc) and with the feature that is full for Javascript, so, rendering it’s realized 100% in browser, and with a real good performance, as in desktop as in mobile device.

My post it’s just a simple modification of one of the samples that comes with the library, so, really, there’s no more that I had included except the representation of the JSON that we generate in our prior service.

Then, there’s no really more than understand a little that example, and replacing the part where are generated the cubes with the representation of our maze, just using a similar method: instead of using standard html5 canvas, we use here the 3D scene prepared from three.js.

Any more! You can see code is self explaining the most, using knew OpenGL behaviour (a scene, adding objects to that scene, adding lights, cameras, etc), as it was for example in the ancient GLScene for Delphi, with that comodity and ease-to-use.  If you want to explore more about this impressive library, you can check out threejs.org and a lot of very good examples around internet.

Here the link, and just here down the code with some on-air comments.

 <html lang="en"><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
        <title>maze 3D</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
        <style>
            body {
                font-family: Monospace;
                background-color: #f0f0f0;
                margin: 0px;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
         src="./three.js">
         src="./Projector.js">
         src="./CanvasRenderer.js">
         src="./stats.js">
        >
            
            // Lets draw map!
            function DrawMap(p, scene)
            {
                var w = p['width'];
                var h = p['height'];
                var m = p['map'];                            
                var c = atob(p['map']);
                var o = 0;
                var d;
                
                var material = new THREE.MeshLambertMaterial( { color: 0xffffff, overdraw: 0.5 } );                                          
                for (var x=0;xw;x++)
                for (var y=0;yh;y++)
                {                
                    d = ~c.charCodeAt(x + y*w); // making NOT. Easier and faster (not to compare to exact number each time, just true/false)
                                                                                                   
                    // Let's masking to bits 1,2,3,4, and depending on result, paint a wall or not.
                    if (d & 1) { paintRightWall(x,y,scene, material); }
                    if (d & 2) { paintUpWall(x,y,scene, material);    }                 
                    if (d & 4) { paintLeftWall(x,y,scene, material);  }          
                    if (d & 8) { paintDownWall(x,y,scene, material);  }                                                         
                }                       
            }           
            
            // generate a 'cube' mesh
            function getCube(x,y,geometry,material)
            {
                var cube = new THREE.Mesh(geometry, material);
                
                cube.position.x = x*50 - 500 + 25;
                cube.position.z = y*50 - 500 + 25;
                cube.position.y = 40;
                
                return cube;
            }
            
            function getHWall(x,y, material)
            {
                var geometry = new THREE.BoxGeometry(60,80,10);
                return getCube(x,y,geometry, material);
            }
            
            function getVWall(x,y,material)
            {
                var geometry = new THREE.BoxGeometry(10,80,60);
                return getCube(x,y,geometry, material);
            }
            
            function paintRightWall(x,y,mesh, material)
            {
                var VWall = getVWall(x,y,material);
                VWall.position.x += 25;
                mesh.add(VWall);
            }
            
            function paintLeftWall(x,y,mesh, material)
            {
                var VWall = getVWall(x,y,material);
                VWall.position.x -= 25;
                mesh.add(VWall);
            }
            
            function paintUpWall(x,y,mesh, material)
            {
                var HWall = getHWall(x,y,material);
                HWall.position.z -= 25;
                mesh.add(HWall);
            }
            
            function paintDownWall(x,y,mesh, material)
            {
                var HWall = getHWall(x,y,material);
                HWall.position.z += 25;
                mesh.add(HWall);
            }            

            // calling service to retrieve a maze. Remember each byte it's a cell, and of each byte
            // 1st bit means right corridor open
            // 2nd bit means up corridor open
            // 3rd bit means left corridor open 
            // 4th one menas down corridor open            
            function getmaze(scene) 
            {
                // doing the call to service...
                var uri = "http://vps264757.ovh.net/GameFactoryService/resources/maze?w=20&h=20";
                var r = new XMLHttpRequest();              
                r.onreadystatechange = 
                function() {
                    if (r.readyState == 4)
                    if (r.status == 200) 
                        DrawMap(JSON.parse(r.response), scene);                        
                    else // ooops...
                        window.alert("Call failed!");
                }
                r.open("GET",uri,true);
                r.send(null);
            }            
            

            var container, stats;
            var camera, scene, renderer;
            var frustumSize = 1000;

            init();
            animate();

            function init() {

                container = document.createElement( 'div' );
                document.body.appendChild( container );

                var info = document.createElement( 'div' );
                info.style.position = 'absolute';
                info.style.top = '10px';
                info.style.width = '100%';
                info.style.textAlign = 'center';
                info.innerHTML = 'a href="http://threejs.org" target="_blank" rel="noopener">three.js/a> - maze 3D';
                container.appendChild(info);

                var aspect = window.innerWidth / window.innerHeight;
/*                camera = new THREE.OrthographicCamera( frustumSize * aspect / - 2, frustumSize * aspect / 2, frustumSize / 2, frustumSize / - 2, 1, 2000);*/

                console.log(aspect);
                camera = new THREE.PerspectiveCamera( 60, aspect, 1, 10000);                          
                camera.position.y = 500;

                scene = new THREE.Scene();
                scene.background = new THREE.Color( 0xf0f0f0 );

                // Grid
                var gridHelper = new THREE.GridHelper( 1000, 20 );
                scene.add( gridHelper );

                // Add walls from maze...                
                getmaze(scene);

                // Lights
                var ambientLight = new THREE.AmbientLight( Math.random() * 0x10 );
                scene.add( ambientLight );

                var directionalLight = new THREE.DirectionalLight( Math.random() * 0xffffff );
                directionalLight.position.x = Math.random() + 0.5;
                directionalLight.position.y = Math.random() + 0.5;
                directionalLight.position.z = Math.random() + 0.5;
                directionalLight.position.normalize();
                scene.add( directionalLight );

                var directionalLight = new THREE.DirectionalLight( Math.random() * 0xffffff );
                directionalLight.position.x = Math.random() - 0.5;
                directionalLight.position.y = Math.random() - 0.5;
                directionalLight.position.z = Math.random() - 0.5;
                directionalLight.position.normalize();
                scene.add( directionalLight );

                var directionalLight = new THREE.DirectionalLight( Math.random() * 0xffffff );
                directionalLight.position.x = Math.random() - 1;
                directionalLight.position.y = Math.random() - 1;
                directionalLight.position.z = Math.random() - 1;
                directionalLight.position.normalize();
                scene.add( directionalLight );          
                
                renderer = new THREE.CanvasRenderer();
                renderer.setPixelRatio( window.devicePixelRatio );
                renderer.setSize(window.innerWidth, window.innerHeight );
                container.appendChild( renderer.domElement );

                stats = new Stats();
                container.appendChild( stats.dom );

                window.addEventListener( 'resize', onWindowResize, false );

            }

            function onWindowResize() {

                var aspect = window.innerWidth / window.innerHeight;

                camera.aspect = window.innerWidth/ window.innerHeight;
                camera.updateProjectionMatrix();

                renderer.setSize( window.innerWidth, window.innerHeight );
            }

            function animate() {

                requestAnimationFrame( animate );

                stats.begin();
                render();
                stats.end();
            }

            function render() {

                var timer = Date.now() * 0.0001;

                camera.position.x = Math.cos( timer ) * 1000;
                camera.position.z = Math.sin( timer ) * 1000;
                
                camera.lookAt( scene.position );

                renderer.render( scene, camera );

            }

              
</body></html

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s