voxels.html
<!--
	Voxels Rehash by Wizardry and Steamworks:
	    http://grimore.org/web/threejs/voxels
	Original from: http://mrdoob.com/projects/voxels/
-->
<!DOCTYPE HTML>
<html lang="en">
    <head>
        <title>Voxels</title>
        <meta charset="utf-8">
        <style type="text/css">
            body {
                font-family: Monospace;
                font-size: 12px;
                background-color: #f0f0f0;
                margin: 0px;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
 
        <script type="text/javascript" src="js/ThreeCanvas.js"></script>
        <script type="text/javascript" src="js/Cube.js"></script>
        <script type="text/javascript" src="js/Plane.js"></script>
 
        <script type="text/javascript">
 
            var container, interval,
            camera, scene, renderer,
            projector, plane, cube, linesMaterial,
            color = 0,colors = [ 0xDF1F1F, 0xDFAF1F, 0x80DF1F, 0x1FDF50, 0x1FDFDF, 0x1F4FDF, 0x7F1FDF, 0xDF1FAF, 0xEFEFEF, 0x303030 ],
            ray, brush, objectHovered,
            mouse3D, isMouseDown = false, onMouseDownPosition,
            radious = 1600, theta = 45, onMouseDownTheta = 45, phi = 60, onMouseDownPhi = 60,
            isShiftDown = false;
 
            var gridUnit = 50;
            var gridSize = 23;
 
            var cubeX = 50;
            var cubeY = 50;
            var cubeZ = 50;
 
            init();
            render();
 
            function init() {
 
                container = document.createElement( 'div' );
                document.body.appendChild( container );
 
                var info = document.createElement( 'div' );
                info.style.position = 'absolute';
                info.style.top = '5px';
                info.style.width = '100%';
                info.style.textAlign = 'center';
                info.innerHTML = '<span style="color: #444; background-color: #fff; border-bottom: 1px solid #ddd; padding: 8px 10px; text-transform: uppercase;"><strong>WSAD/EC<strong>: move, <strong>0 - 9</strong>: colors, <strong>click</strong>: add voxel, <strong>shift + click</strong>: remove voxel, <strong>drag</strong>: rotate | <a id="link" href="" target="_blank">share</a> <a href="javascript:save();">save</a> <a href="javascript:clear();">clear</a></span>';
                container.appendChild( info );
 
                camera = new THREE.Camera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
                camera.position.x = radious * Math.sin( theta * Math.PI / 360 ) * Math.cos( phi * Math.PI / 360 );
                camera.position.y = radious * Math.sin( phi * Math.PI / 360 );
                camera.position.z = radious * Math.cos( theta * Math.PI / 360 ) * Math.cos( phi * Math.PI / 360 );
                camera.target.position.y = 200;
 
                scene = new THREE.Scene();
 
                // Grid
 
                var geometry = new THREE.Geometry();
                geometry.vertices.push( new THREE.Vertex( new THREE.Vector3( - Math.floor(gridSize * gridUnit / 2.0), -cubeX/2, 0 ) ) );
                geometry.vertices.push( new THREE.Vertex( new THREE.Vector3( Math.floor(gridSize * gridUnit / 2.0), -cubeY/2, 0 ) ) );
 
                linesMaterial = new THREE.LineColorMaterial( 0, 0.2 );
 
                var step = 2 * Math.floor(gridSize * gridUnit / 2.0) / gridUnit;
                for ( var i = 0; i <= step; i ++ ) {
 
                    var line = new THREE.Line( geometry, linesMaterial );
                    line.position.z = ( i * gridUnit ) - Math.floor(gridSize * gridUnit / 2.0);
                    scene.addObject( line );
 
                    var line = new THREE.Line( geometry, linesMaterial );
                    line.position.x = ( i * gridUnit ) - Math.floor(gridSize * gridUnit / 2.0);
                    line.rotation.y = 90 * Math.PI / 180;
                    scene.addObject( line );
 
                }
 
                projector = new THREE.Projector();
 
                plane = new THREE.Mesh( new Plane( Math.floor(gridSize * gridUnit / 2.0) * 2, Math.floor(gridSize * gridUnit / 2.0) * 2 ) );
                plane.rotation.x = - 90 * Math.PI / 180;
                scene.addObject( plane );
 
                cube = new Cube( cubeX, cubeY, cubeZ );
 
                ray = new THREE.Ray( camera.position, null );
 
                brush = new THREE.Mesh( cube, new THREE.MeshColorFillMaterial( colors[ color ], 0.4 ) );
                brush.position.y = 2000;
                brush.overdraw = true;
                scene.addObject( brush );
 
                onMouseDownPosition = new THREE.Vector2();
 
                // Lights
 
                var ambientLight = new THREE.AmbientLight( 0x404040 );
                scene.addLight( ambientLight );
 
                var directionalLight = new THREE.DirectionalLight( 0xffffff );
                directionalLight.position.x = 1;
                directionalLight.position.y = 1;
                directionalLight.position.z = 0.75;
                directionalLight.position.normalize();
                scene.addLight( directionalLight );
 
                var directionalLight = new THREE.DirectionalLight( 0x808080 );
                directionalLight.position.x = - 1;
                directionalLight.position.y = 1;
                directionalLight.position.z = - 0.75;
                directionalLight.position.normalize();
                scene.addLight( directionalLight );
 
                renderer = new THREE.CanvasRenderer();
                renderer.setSize( window.innerWidth, window.innerHeight );
 
                container.appendChild(renderer.domElement);
 
                document.addEventListener( 'keydown', onDocumentKeyDown, false );
                document.addEventListener( 'keyup', onDocumentKeyUp, false );
 
                document.addEventListener( 'mousemove', onDocumentMouseMove, false );
                document.addEventListener( 'mousedown', onDocumentMouseDown, false );
                document.addEventListener( 'mouseup', onDocumentMouseUp, false );
 
                document.addEventListener( 'mousewheel', onDocumentMouseWheel, false );
 
                if ( window.location.hash ) {
                    buildFromUnits();
                }
 
            }
 
            function onDocumentKeyDown( event ) {
 
                switch( event.keyCode ) {
                    case 49: setBrushColor( 0 ); break;
                    case 50: setBrushColor( 1 ); break;
                    case 51: setBrushColor( 2 ); break;
                    case 52: setBrushColor( 3 ); break;
                    case 53: setBrushColor( 4 ); break;
                    case 54: setBrushColor( 5 ); break;
                    case 55: setBrushColor( 6 ); break;
                    case 56: setBrushColor( 7 ); break;
                    case 57: setBrushColor( 8 ); break;
                    case 48: setBrushColor( 9 ); break;
 
                    case 16: isShiftDown = true; interact(); render(); break;
 
                    // a
                    case 65: offsetScene( - 1, 0, 0 ); break;
                    // w
                    case 87: offsetScene( 0, 0, - 1 ); break;
                    // d
                    case 68: offsetScene( 1, 0, 0 ); break;
                    // s
                    case 83: offsetScene( 0, 0, 1 ); break;
                    // e
                    case 69: offsetScene(0, 1, 0); break;
                    // c
                    case 67: offsetScene(0, -1, 0); break;
                }
 
            }
 
            function onDocumentKeyUp( event ) {
 
                switch( event.keyCode ) {
 
                    case 16: isShiftDown = false; interact(); render(); break;
 
                }
 
            }
 
            function onDocumentMouseDown( event ) {
 
                event.preventDefault();
 
                isMouseDown = true;
 
                onMouseDownTheta = theta;
                onMouseDownPhi = phi;
                onMouseDownPosition.x = event.clientX;
                onMouseDownPosition.y = event.clientY;
 
            }
 
            function onDocumentMouseMove( event ) {
 
                event.preventDefault();
 
                if ( isMouseDown ) {
                    theta = - ( ( event.clientX - onMouseDownPosition.x ) * 0.5 ) + onMouseDownTheta;
                    phi = ( ( event.clientY - onMouseDownPosition.y ) * 0.5 ) + onMouseDownPhi;
 
                    phi = Math.min( 180, Math.max( -180, phi ) );
 
                    camera.position.x = radious * Math.sin( theta * Math.PI / 360 ) * Math.cos( phi * Math.PI / 360 );
                    camera.position.y = radious * Math.sin( phi * Math.PI / 360 );
                    camera.position.z = radious * Math.cos( theta * Math.PI / 360 ) * Math.cos( phi * Math.PI / 360 );
                    camera.updateMatrix();
                    render();
                    return;
                }
 
                mouse3D = projector.unprojectVector( new THREE.Vector3( ( event.clientX / renderer.domElement.width ) * 2 - 1, - ( event.clientY / renderer.domElement.height ) * 2 + 1, 0.5 ), camera );
                ray.direction = mouse3D.subSelf( camera.position ).normalize();
 
                interact();
                render();
 
            }
 
            function onDocumentMouseUp( event ) {
 
                event.preventDefault();
 
                isMouseDown = false;
 
                onMouseDownPosition.x = event.clientX - onMouseDownPosition.x;
                onMouseDownPosition.y = event.clientY - onMouseDownPosition.y;
 
                if ( onMouseDownPosition.length() > 5 ) {
                    return;
                }
 
                var intersect, intersects = ray.intersectScene( scene );
 
                if ( intersects.length > 0 ) {
 
                    intersect = intersects[ 0 ].object == brush ? intersects[ 1 ] : intersects[ 0 ];
 
                    if ( intersect ) {
                        if ( isShiftDown ) {
                            if ( intersect.object != plane ) {
                                scene.removeObject( intersect.object );
                            }
                        } else {
                            var position = new THREE.Vector3().add( intersect.point, intersect.object.matrixRotation.transform( intersect.face.normal.clone() ) );
 
                            var voxel = new THREE.Mesh( cube, new THREE.MeshColorFillMaterial( colors[ color ] ) );
                            voxel.position.x = Math.round( position.x / cubeX ) * cubeX;
                            voxel.position.y = Math.round( position.y / cubeY ) * cubeY;
                            voxel.position.z = Math.round( position.z / cubeZ ) * cubeZ;
                            voxel.overdraw = true;
                            scene.addObject( voxel );
                        }
                    }
                }
 
                updateUnits();
                interact();
                render();
 
            }
 
            function onDocumentMouseWheel( event ) {
 
                radious -= event.wheelDeltaY;
 
                camera.position.x = radious * Math.sin( theta * Math.PI / 360 ) * Math.cos( phi * Math.PI / 360 );
                camera.position.y = radious * Math.sin( phi * Math.PI / 360 );
                camera.position.z = radious * Math.cos( theta * Math.PI / 360 ) * Math.cos( phi * Math.PI / 360 );
                camera.updateMatrix();
 
                interact();
                render();
 
            }
 
            function setBrushColor( value ) {
 
                color = value;
                brush.material[ 0 ].color.setHex( colors[ color ] ^ 0x4C000000 );
                render();
            }
 
            function buildFromUnits() {
                var hash = window.location.hash.substr( 1 );
                var version = hash.substr( 0, 2 );
 
                if(version != "W/") return;
 
                var data = hash.substr(2);
 
                if(data == "") return;
 
                data = wasCSVToArray(data);
 
                for(i = 0; i<data.length; i += 4) { 
                    var voxel = new THREE.Mesh( cube, new THREE.MeshColorFillMaterial( colors[ data[i + 3] ] ) );
                    voxel.position.x = data[i] * cubeX;
                    voxel.position.y = data[ i + 1 ] * cubeY;
                    voxel.position.z = data[ i + 2 ] * cubeZ;
                    voxel.overdraw = true;
                    scene.addObject( voxel );
                }
                updateUnits();
            }
 
            /* Place the object positions in the URL. */
            function updateUnits() {
                var data = [];
 
                for(var i in scene.objects) { 
                    object = scene.objects[i];
                    if(!(object instanceof THREE.Mesh) || 
                        object == plane || 
                        object == brush) continue;
                    data.push(( object.position.x ) / cubeX);
                    data.push(( object.position.y ) / cubeY);
                    data.push(( object.position.z ) / cubeZ);
                    data.push(colors.indexOf( object.material[ 0 ].color.hex & 0xffffff ));
                }
                window.location.hash = "W/" + data;
                document.getElementById( 'link' ).href = "http://grimore.org/opt/threejs/voxels/voxels.html#W/" + wasArrayToCSV(data);
            }
 
            function offsetScene( x, y, z ) {
 
                var offset = new THREE.Vector3( x * cubeX, y * cubeZ, z * cubeZ );
                for ( var i in scene.objects ) {
                    object = scene.objects[ i ];
                    if ( object instanceof THREE.Mesh && object !== plane && object !== brush ) {
 
                        object.position.addSelf( offset );
                    }
                }
 
                updateUnits();
                interact();
                render();
            }
 
            function interact() {
 
                if ( objectHovered ) {
                    objectHovered.material[ 0 ].color.a = 1;
                    objectHovered.material[ 0 ].color.updateStyleString();
                    objectHovered = null;
                }
 
                var position, intersect, intersects = ray.intersectScene( scene );
                if ( intersects.length > 0 ) {
                    intersect = intersects[ 0 ].object != brush ? intersects[ 0 ] : intersects[ 1 ];
                    if ( intersect ) {
                        if ( isShiftDown ) {
                            // Suppress the current brush during shift-hoover.
                            brush.material[0].color.a = 0;
                            if ( intersect.object != plane ) {
                                objectHovered = intersect.object;
                                objectHovered.material[ 0 ].color.a = 0.5;
                                objectHovered.material[ 0 ].color.updateStyleString();
                                return;
                            }
                        } else {
                            position = new THREE.Vector3().add( intersect.point, intersect.object.matrixRotation.transform( intersect.face.normal.clone() ) );
 
                            brush.position.x = Math.round( position.x / cubeX ) * cubeX;
                            brush.position.y = Math.round( position.y / cubeY ) * cubeY;
                            brush.position.z = Math.round( position.z / cubeZ ) * cubeZ;
 
                            // Restore the brush.
                            brush.material[0].color.a = 0.5;
                            return;
                        }
                    }
                }
 
                brush.position.y = 2000;
            }
 
            function render() {
                renderer.render( scene, camera );
            }
 
            function save() {
                linesMaterial.color.setRGBA( 0, 0, 0, 0 );
                brush.position.y = 2000;
                render();
                window.open( renderer.domElement.toDataURL('image/png'), 'mywindow' );
                linesMaterial.color.setRGBA( 0, 0, 0, 0.2 );
                render();
            }
 
            function clear() {
                if ( !confirm( 'Are you sure?' ) ) {
                    return
                }
                window.location.hash = "";
                var i = 0;
                while ( i < scene.objects.length ) {
                    object = scene.objects[ i ];
                    if ( object instanceof THREE.Mesh && object !== plane && object !== brush ) {
 
                        scene.removeObject( object );
                        continue;
                    }
                    i ++;
                }
                updateUnits();
                render();
            }
 
            ///////////////////////////////////////////////////////////////////////////
            //    Copyright (C) 2016 Wizardry and Steamworks - License: GNU GPLv3    //
            ///////////////////////////////////////////////////////////////////////////
            function wasCSVToArray(csv) {
                var l = [];
                var s = [];
                var m = "";
 
                do {
                    var a = csv.charAt(0);
                    csv = csv.slice(1, csv.length);
                    if(a == ",") {
                        if(s[s.length-1] != '"') {
                            l.push(m);
                            m = "";
                            continue;
                        }
                        m += a;
                        continue;
                    }
                    if(a == '"' && csv.charAt(0) == a) {
                        m += a;
                        csv = csv.slice(1, csv.length);
                        continue;
                    }
                    if(a == '"') {
                        if(s[s.length-1] != a) {
                            s.push(a);
                            continue;
                        }
                        s.pop();
                        continue;
                    }
                    m += a;
                } while(csv != "");
 
                l.push(m);
 
                return l;
            }
 
            ///////////////////////////////////////////////////////////////////////////
            //    Copyright (C) 2016 Wizardry and Steamworks - License: GNU GPLv3    //
            ///////////////////////////////////////////////////////////////////////////
            function wasArrayToCSV(a) {
                var csv = [];
                for(var i=0; i<a.length; ++i) {
                    var cell = a[i].toString().replace('"', '""');
                    if(/"\s,\r\n/.test(cell)) {
                        csv[i] = '"' + cell + '"';
                        continue;
                    }
                    csv[i] = cell;
                }
                return csv.join();
            }
 
        </script>
    </body>
</html>