<!-- 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>