hexcraft
ARCHIVED - browser-based 3D hexagonal tile editor built with Three.js
git clone https://git.vogt.world/hexcraft.git
Log | Files | README.md | LICENSE
← Commit log
commit
message
Improved comments, added WebGL detection.
author
date
2014-01-04 02:36:13
stats
4 file(s) changed, 168 insertions(+), 100 deletions(-)
files
index.html
js/hexagon.js
js/main.js
js/vendor/Detector.js
  1diff --git a/index.html b/index.html
  2index b30baf0..4642cfc 100644
  3--- a/index.html
  4+++ b/index.html
  5@@ -123,7 +123,8 @@
  6     </div>
  7 
  8     
  9-		<script src="js/vendor/three.min.js"></script>
 10+    <script src="js/vendor/three.min.js"></script>
 11+    <script src="js/vendor/Detector.js"></script>
 12     <script src="js/vendor/stats.min.js"></script>
 13     <script src="js/vendor/jquery.min.js"></script>
 14     <script src="js/vendor/moment.min.js"></script>
 15diff --git a/js/hexagon.js b/js/hexagon.js
 16index 2559290..38f8f5c 100644
 17--- a/js/hexagon.js
 18+++ b/js/hexagon.js
 19@@ -43,14 +43,11 @@ function newHexagon(size, thickness) {
 20   object.faces.push( new THREE.Face3( 7, 4, 8) );
 21   
 22   object.computeFaceNormals();
 23-  // object.computeVertexNormals();
 24-  // object.computeBoundingSphere();
 25   
 26   return object;
 27 }
 28 
 29 
 30-
 31 function newFloorHexagon(size, thickness) {
 32   var object = new THREE.Geometry();
 33 
 34@@ -69,30 +66,7 @@ function newFloorHexagon(size, thickness) {
 35   object.faces.push( new THREE.Face3( 1, 4, 5) );
 36   object.faces.push( new THREE.Face3( 2, 3, 4) );
 37 
 38-  // object.faces.push( new THREE.Face3( 10, 1, 0) );
 39-  // object.faces.push( new THREE.Face3( 0, 11, 10) );
 40-  // 
 41-  // object.faces.push( new THREE.Face3( 5, 6, 0) );
 42-  // object.faces.push( new THREE.Face3( 6, 11, 0) );
 43-  // 
 44-  // object.faces.push( new THREE.Face3( 2, 1, 10) );
 45-  // object.faces.push( new THREE.Face3( 10, 9, 2) );
 46-  // 
 47-  // object.faces.push( new THREE.Face3( 3, 2, 9) );
 48-  // object.faces.push( new THREE.Face3( 9, 8, 3) );
 49-  // 
 50-  // object.faces.push( new THREE.Face3( 5, 4, 7) );
 51-  // object.faces.push( new THREE.Face3( 7, 6, 5) );
 52-  // 
 53-  // object.faces.push( new THREE.Face3( 4, 3, 7) );
 54-  // object.faces.push( new THREE.Face3( 3, 8, 7) );
 55-  // 
 56-  // object.faces.push( new THREE.Face3( 8, 4, 3) );
 57-  // object.faces.push( new THREE.Face3( 7, 4, 8) );
 58-  // 
 59   object.computeFaceNormals();
 60-  // object.computeVertexNormals();
 61-  // object.computeBoundingSphere();
 62-  
 63+
 64   return object;
 65 }
 66diff --git a/js/main.js b/js/main.js
 67index b9b69c6..266c02a 100644
 68--- a/js/main.js
 69+++ b/js/main.js
 70@@ -1,30 +1,26 @@
 71+//Scene variables
 72 var camera, scene, renderer;
 73 var mesh, light;
 74 var dirLight, hemiLight;
 75 var controls;
 76-var cross;
 77+var delta;
 78 var projector, raycaster, intersects;
 79-
 80-var radius = 6371;
 81-var tilt = 0.41;
 82-var rotationSpeed = 0.6;
 83-
 84 var mouse = new THREE.Vector2(), INTERSECTED;
 85 var clock = new THREE.Clock();
 86 
 87-//HEXAGON CONSTANTS
 88+//Hexagon constants
 89 var SIZE = 12;
 90 var THICKNESS = 5;
 91 var HEIGHT = SIZE*2;
 92 var WIDTH = (Math.sqrt(3)/2) * HEIGHT;
 93 
 94-//BOARD CONTSTANTS
 95+//Board Constants
 96 var xCount = 40;
 97 var yCount = 40;
 98 var zCount = 40;
 99 var board;
100 
101-//TOOLS
102+//Tools
103 var TOOLS = {
104   add: 0,
105   remove: 1
106@@ -34,15 +30,17 @@ var currentHexagonPosition = {x:0, y:0, z:0};
107 var phantomHexagon;
108 var hexcount = 0;
109 var currentGame;
110+
111+//Game element to stamp out in the DOM
112 var gameElement = $('.modal-body.list').html();
113 
114-//COLORS
115+//Colors
116 var COLORS = [0xFFFFFF, 0xFAFAFA, 0xF6F6F6, 0xF1F1F1, 0xEDEDED, 0xE8E8E8];
117 var currentColor = 0xE32818;
118 var sky = 0xBFE6FF;
119 var ground = 0x333333;
120 
121-
122+//Set the draw div to be the height of the window
123 $('#draw').css('height', window.innerWidth - $('.nav').height());
124 
125 init();
126@@ -50,6 +48,7 @@ hud();
127 animate();
128 firstLoad();
129 
130+//Places the heads up display
131 function hud() {
132   var hud = $('.hud');
133   hud.css('left', ((window.innerWidth/2) - (hud.width()/2)));
134@@ -58,9 +57,11 @@ function hud() {
135 }
136 
137 function firstLoad() {
138+  //If this is a first time user, trigger the welcome modal.
139   if (localStorage.hex == undefined) {
140     $('#welcomeModal').modal('toggle');
141   }
142+  //Find which game we'll be saving to
143   for (var i = 1; i < 30; i++) {
144     var name = 'game' + i;
145     if (!localStorage.hasOwnProperty(name)) {
146@@ -71,33 +72,37 @@ function firstLoad() {
147 }
148 
149 function init() {
150+  //WebGL detection and redirection
151+  if (!Detector.webgl) {
152+    window.location = "http://get.webgl.org";
153+  }
154 
155-  //CAMERA
156-	camera = new THREE.PerspectiveCamera( 39, window.innerWidth / window.innerHeight, 1, 2000 );
157+  //Camera initialization
158+  camera = new THREE.PerspectiveCamera( 39, window.innerWidth / window.innerHeight, 1, 2000 );
159   camera.eulerOrder = "YXZ"
160-	camera.position.z = 200;
161+  camera.position.z = 200;
162   camera.position.y = 40;
163   camera.position.x = 100;
164   camera.lookAt(getHexagonPositionFromLocation(Math.floor(xCount/2), 0, Math.floor(zCount/2)));
165-  
166-  //CONTROLS
167-  controls = new THREE.FlyControls( camera );
168+
169+  //Controls
170+  controls = new THREE.FlyControls(camera);
171   controls.movementSpeed = 70;
172-	controls.domElement = draw;
173-	controls.rollSpeed = Math.PI / 7;
174-	controls.autoForward = false;
175-	controls.dragToLook = false;
176+  controls.domElement = draw;
177+  controls.rollSpeed = Math.PI / 7;
178+  controls.autoForward = false;
179+  controls.dragToLook = false;
180 
181-  //SCENE
182-	scene = new THREE.Scene();
183+  //Scene
184+  scene = new THREE.Scene();
185   scene.fog = new THREE.Fog(0xffffff, 500, 1200);
186 
187-  //LIGHTS
188+  //Lights
189   hemiLight = new THREE.HemisphereLight( 0xffffff, 0xffffff, 0.6 );
190-	hemiLight.color.setHSL( 0.6, 1, 0.6 );
191-	hemiLight.groundColor.setHSL( 0.095, 1, 0.75 );
192-	hemiLight.position.set( 0, 500, 0 );
193-	scene.add( hemiLight );
194+  hemiLight.color.setHSL( 0.6, 1, 0.6 );
195+  hemiLight.groundColor.setHSL( 0.095, 1, 0.75 );
196+  hemiLight.position.set( 0, 500, 0 );
197+  scene.add( hemiLight );
198   dirLight = new THREE.DirectionalLight( 0xffffff, 1 );
199   dirLight.color.setHSL( 0.1, 1, 0.95 );
200   dirLight.position.set( -1, 1.75, 1 );
201@@ -116,19 +121,18 @@ function init() {
202   dirLight.shadowDarkness = 0.35;
203 
204 
205-  // GROUND
206-	var ground = new THREE.Mesh(new THREE.PlaneGeometry(50000, 50000), 
207-    new THREE.MeshPhongMaterial({ambient: ground, color: ground, specular: ground}) );
208+  //Ground
209+  var ground = new THREE.Mesh(new THREE.PlaneGeometry(50000, 50000), new THREE.MeshPhongMaterial({ambient: ground, color: ground, specular: ground}) );
210   ground.name = 'ground';
211   ground.rotation.x = -Math.PI/2;
212-	ground.position.y = 0;
213-	scene.add( ground );
214-	ground.receiveShadow = true;
215+  ground.position.y = 0;
216+  scene.add( ground );
217+  ground.receiveShadow = true;
218 
219-  //BOARD
220+  //Board to store hexagon colors for loading, saving
221   board = new Board(xCount, zCount, yCount);
222-  
223-  //INITIALIZING HEXAGONS
224+
225+  //Initializing floor hexagons
226   for (var x = 0; x < board.width; x++) {
227     for (var z = 0; z < board.depth; z++) {
228       var hexagon = newFloorHexagon(SIZE, THICKNESS);
229@@ -144,38 +148,42 @@ function init() {
230     }
231   }
232   
233-  //SELECTOR HEXAGON
234+  //Phantom hexagon to act as selector for adding and removing.
235   var hexagon = newHexagon(SIZE, THICKNESS);
236   phantomHexagon = new THREE.Mesh(hexagon, new THREE.MeshNormalMaterial( { color: currentColor, emissive: currentColor, ambient: currentColor, transparent: true, opacity: 0 } ));
237   scene.add(phantomHexagon);
238   phantomHexagon.name = 'phantomHexagon';
239   
240-  //MOUSE INTERACTION
241+  //Projector and raycaster for instersection with mouse location
242   projector = new THREE.Projector();
243   raycaster = new THREE.Raycaster();
244   
245-  //EVENT HANDLING
246+  //Bind events
247   bindEvents();
248   
249-  //RENDERING
250+  //Engage the rendered
251   renderer = new THREE.WebGLRenderer( { antialias: false } );
252-	renderer.setSize( window.innerWidth, window.innerHeight );
253-	draw.appendChild( renderer.domElement );
254+  renderer.setSize( window.innerWidth, window.innerHeight );
255+  draw.appendChild( renderer.domElement );
256   renderer.setClearColor( sky, 1 );
257   renderer.gammaInput = true;
258   renderer.gammaOutput = true;
259   renderer.physicallyBasedShading = true;
260   renderer.shadowMapEnabled = false;
261-	renderer.shadowMapCullFace = THREE.CullFaceBack;
262-	document.body.appendChild( renderer.domElement );
263+  renderer.shadowMapCullFace = THREE.CullFaceBack;
264+  document.body.appendChild( renderer.domElement );
265 }
266 
267+//Binding common mouse events
268 function bindEvents() {
269-	window.addEventListener('resize', onWindowResize, false);
270+  //Handle apsect ratios when the user resizes
271+  window.addEventListener('resize', onWindowResize, false);
272+  //Change location of phantom hexagon on mouse move
273   $('#draw').on('mousemove', onDocumentMouseMove);
274+  //Handle mouse clicks
275   $('#draw').on('click', onDocumentMouseClick);
276   
277-  //ADD HEXAGON BUTTON
278+  //Select 'add hexagon' tool
279   $('.button.add').on('click', function(e) {
280     //remove is not active
281     var button = $('.button.remove');
282@@ -192,7 +200,7 @@ function bindEvents() {
283     currentTool = TOOLS.add;
284   });
285   
286-  //REMOVE HEXAGON BUTTON
287+  //Select 'remove hexagon' tool
288   $('.button.remove').on('click', function(e) {
289     //add is not active
290     var button = $('.button.add');
291@@ -209,14 +217,14 @@ function bindEvents() {
292     currentTool = TOOLS.remove;
293   });
294   
295-  //SAVE SCENE
296+  //Save the current scene to local storage
297   $('.button.save').on('click', function(e) {
298     localStorage.hex = true;
299     var game = {tiles: board.getActiveTiles(), date: moment().format('MMMM Do YYYY, h:mm a')};
300     localStorage[currentGame] = JSON.stringify(game);
301   });
302   
303-  //LOAD SCENE
304+  //Trigger the load modal, and populated it with saved games
305   $('.button.load').on('click', function(e) {
306     $('.modal-body.list').empty();
307     for (var i = 1; i < 30; i++) {
308@@ -240,7 +248,7 @@ function bindEvents() {
309     $('#loadModal').modal('toggle');
310   });
311   
312-  //NEW SCENE
313+  //Wipe the current game, start a new localStorage game
314   $('.button.new').on('click', function(e) {
315     clearScene();
316     resetScene();
317@@ -253,11 +261,12 @@ function bindEvents() {
318     }
319   });
320   
321-  //INFO
322+  //Show welcome modal for more information
323   $('.button.info').on('click', function(e) {
324     $('#welcomeModal').modal('show');
325   });
326   
327+  //Load demo game
328   $('.demo').on('click', function(e) {
329     $('#welcomeModal').modal('hide');
330     clearScene();
331@@ -265,7 +274,7 @@ function bindEvents() {
332     loadGame(demos[event.target.id].tiles);
333   });
334   
335-  //ZOOM CONTROLS
336+  //Bind mouse wheel for zoom/perspective control
337   $("#draw").bind('mousewheel', function (e) { 
338    if(e.originalEvent.wheelDelta /120 > 0) {
339      if (camera.fov < 60)
340@@ -277,7 +286,7 @@ function bindEvents() {
341    }
342   });
343   
344-  //Key binding
345+  //Binding the enter key to add a hexagon
346   $(document).on('keypress', function (event) {
347     if (event.keyCode == 13) {
348       switch (currentTool) {
349@@ -291,7 +300,7 @@ function bindEvents() {
350     }
351   });
352   
353-  //Color picker
354+  //Binding the collor picker
355   $('.picker').colorpicker({color: '#0066cc'})
356   .on('changeColor', function(ev) {
357     currentColor = parseInt(ev.color.toHex().replace('#', '0x'));
358@@ -306,13 +315,17 @@ function bindEvents() {
359   });
360 }
361 
362+//Handling mouse moves
363 function onDocumentMouseMove( event ) {
364+  //When the mouse is over the game, and not the color picker, hide color picker
365   $('.picker').colorpicker('hide');
366-	event.preventDefault();
367-	mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
368-	mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
369+  event.preventDefault();
370+  //Capture mouse location
371+  mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
372+  mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
373 }
374 
375+//Routing mouse click events based on tool
376 function onDocumentMouseClick() {
377   switch (currentTool) {
378     case TOOLS.remove:
379@@ -324,6 +337,7 @@ function onDocumentMouseClick() {
380   }
381 }
382 
383+//Reset the 'floor' hexagons for a new scene
384 function resetScene() {
385   for (var x = 0; x < board.width; x++) {
386     for (var z = 0; z < board.depth; z++) {
387@@ -341,11 +355,12 @@ function resetScene() {
388   }
389 }
390 
391+//Load a saved game
392 function loadGame(tiles) {
393   clearScene();
394   resetScene();
395 
396-  
397+  //Add the hexagons one by one
398   for (var i = 0; i < tiles.length; i++) {
399     var hexagon = newHexagon(SIZE, THICKNESS);
400     var mesh = new THREE.Mesh(hexagon, new THREE.MeshLambertMaterial(
401@@ -362,6 +377,7 @@ function loadGame(tiles) {
402   }
403 }
404 
405+//Clears an entire scene, including the floor.
406 function clearScene() {
407   board = new Board(xCount, zCount, yCount);
408   var removeList = [];
409@@ -376,11 +392,13 @@ function clearScene() {
410   updateHexcount(0);
411 }
412 
413+//Push the hexagon count to the HUD
414 function updateHexcount(number) {
415   hexcount = number;
416   $('.hexcount').text(hexcount + ' hexagons');
417 }
418 
419+//Remove the selected hexagon
420 function removeHexagon() {
421   if ((INTERSECTED.name != 'ground' && INTERSECTED.name != 'floor') && intersects[1].object.name !== 'phantomHexagon') {
422     scene.remove(intersects[1].object);
423@@ -391,6 +409,7 @@ function removeHexagon() {
424   }
425 }
426 
427+//Add a hexagon at the appropriate location
428 function addHexagon() {
429   if (INTERSECTED.name != 'ground') {
430     var hexagon = newHexagon(SIZE, THICKNESS);
431@@ -409,7 +428,7 @@ function addHexagon() {
432   }
433 }
434 
435-//SELECTOR - selects hexagons
436+//Places the phantomHexagon at the appropriate location, and keeps track of the foremost intersected hexagon
437 function selector() {
438   var mouseVector = new THREE.Vector3(mouse.x, mouse.y, 1);
439   projector.unprojectVector(mouseVector, camera);
440@@ -441,12 +460,14 @@ function selector() {
441   }
442 }
443 
444+//Set the phantomHexagons board location
445 function setPhantomPosition(x, y, z) {
446   phantomHexagon.position.x = x;
447   phantomHexagon.position.y = y;
448   phantomHexagon.position.z = z;
449 }
450 
451+//Get a hexagons real-world position based upon its board location
452 function getHexagonPositionFromLocation(x, y, z) {
453   var position = new THREE.Vector3();
454   if (z % 2 == 0) {
455@@ -460,6 +481,7 @@ function getHexagonPositionFromLocation(x, y, z) {
456   return position;
457 }
458 
459+//Ensures the user doesn't fly the camera below the board, or too far in any direction.
460 function checkCameraBoundries() {
461   // //Y Boundries.
462   if (camera.position.y < 10) {
463@@ -487,21 +509,24 @@ function checkCameraBoundries() {
464   }
465 }
466 
467+//When the user resizes the window, we handle the camera's aspect ratio
468 function onWindowResize() {
469-	camera.aspect = window.innerWidth / window.innerHeight;
470-	camera.updateProjectionMatrix();
471-	renderer.setSize( window.innerWidth, window.innerHeight );
472+  camera.aspect = window.innerWidth / window.innerHeight;
473+  camera.updateProjectionMatrix();
474+  renderer.setSize( window.innerWidth, window.innerHeight );
475 }
476 
477+//Perform selector update, control update, camera boundry checks, and render
478 function animate() {
479   selector();
480-	requestAnimationFrame( animate );
481+  delta = clock.getDelta();
482+  controls.update(delta);
483+  checkCameraBoundries();
484+  requestAnimationFrame(animate);
485   render();
486 }
487 
488+//Render
489 function render() {
490-  var delta = clock.getDelta();
491-  controls.update( delta );
492-  checkCameraBoundries();
493-  renderer.render( scene, camera );
494+  renderer.render(scene, camera);
495 }
496\ No newline at end of file
497diff --git a/js/vendor/Detector.js b/js/vendor/Detector.js
498new file mode 100644
499index 0000000..02e49e0
500--- /dev/null
501+++ b/js/vendor/Detector.js
502@@ -0,0 +1,59 @@
503+/**
504+ * @author alteredq / http://alteredqualia.com/
505+ * @author mr.doob / http://mrdoob.com/
506+ */
507+
508+var Detector = {
509+
510+	canvas: !! window.CanvasRenderingContext2D,
511+	webgl: ( function () { try { var canvas = document.createElement( 'canvas' ); return !! window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ); } catch( e ) { return false; } } )(),
512+	workers: !! window.Worker,
513+	fileapi: window.File && window.FileReader && window.FileList && window.Blob,
514+
515+	getWebGLErrorMessage: function () {
516+
517+		var element = document.createElement( 'div' );
518+		element.id = 'webgl-error-message';
519+		element.style.fontFamily = 'monospace';
520+		element.style.fontSize = '13px';
521+		element.style.fontWeight = 'normal';
522+		element.style.textAlign = 'center';
523+		element.style.background = '#fff';
524+		element.style.color = '#000';
525+		element.style.padding = '1.5em';
526+		element.style.width = '400px';
527+		element.style.margin = '5em auto 0';
528+
529+		if ( ! this.webgl ) {
530+
531+			element.innerHTML = window.WebGLRenderingContext ? [
532+				'Your graphics card does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation" style="color:#000">WebGL</a>.<br />',
533+				'Find out how to get it <a href="http://get.webgl.org/" style="color:#000">here</a>.'
534+			].join( '\n' ) : [
535+				'Your browser does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation" style="color:#000">WebGL</a>.<br/>',
536+				'Find out how to get it <a href="http://get.webgl.org/" style="color:#000">here</a>.'
537+			].join( '\n' );
538+
539+		}
540+
541+		return element;
542+
543+	},
544+
545+	addGetWebGLMessage: function ( parameters ) {
546+
547+		var parent, id, element;
548+
549+		parameters = parameters || {};
550+
551+		parent = parameters.parent !== undefined ? parameters.parent : document.body;
552+		id = parameters.id !== undefined ? parameters.id : 'oldie';
553+
554+		element = Detector.getWebGLErrorMessage();
555+		element.id = id;
556+
557+		parent.appendChild( element );
558+
559+	}
560+
561+};
562\ No newline at end of file