name:
js/main.js
-rw-r--r--
16817
1//Scene variables
2var camera, scene, renderer;
3var mesh, light;
4var dirLight, hemiLight;
5var controls;
6var delta;
7var projector, raycaster, intersects;
8var mouse = new THREE.Vector2(), INTERSECTED;
9var clock = new THREE.Clock();
10
11//Hexagon constants
12var SIZE = 12;
13var THICKNESS = 5;
14var HEIGHT = SIZE*2;
15var WIDTH = (Math.sqrt(3)/2) * HEIGHT;
16
17//Board Constants
18var xCount = 40;
19var yCount = 40;
20var zCount = 40;
21var board;
22
23//Tools
24var TOOLS = {
25 add: 0,
26 remove: 1
27};
28var currentTool = TOOLS.add;
29var currentHexagonPosition = {x:0, y:0, z:0};
30var phantomHexagon;
31var hexcount = 0;
32var currentGame;
33
34//Game element to stamp out in the DOM
35var gameElement = $('.modal-body.list').html();
36
37//Colors
38var COLORS = [0xFFFFFF, 0xFAFAFA, 0xF6F6F6, 0xF1F1F1, 0xEDEDED, 0xE8E8E8];
39var currentColor = 0xE32818;
40var sky = 0xBFE6FF;
41var ground = 0x333333;
42
43//Set the draw div to be the height of the window
44$('#draw').css('height', window.innerWidth - $('.nav').height());
45
46init();
47hud();
48animate();
49firstLoad();
50
51//Places the heads up display
52function hud() {
53 var hud = $('.hud');
54 hud.css('left', ((window.innerWidth/2) - (hud.width()/2)));
55 hud.find('.center').css('width', hud.width()-24)
56 hud.show();
57}
58
59function firstLoad() {
60 //If this is a first time user, trigger the welcome modal.
61 if (localStorage.hex == undefined) {
62 $('#welcomeModal').modal('toggle');
63 }
64 //Find which game we'll be saving to
65 for (var i = 1; i < 30; i++) {
66 var name = 'game' + i;
67 if (!localStorage.hasOwnProperty(name)) {
68 currentGame = name;
69 break;
70 }
71 }
72}
73
74function init() {
75 //WebGL detection and redirection
76 if (!Detector.webgl) {
77 window.location = "http://get.webgl.org";
78 }
79
80 //Camera initialization
81 camera = new THREE.PerspectiveCamera( 39, window.innerWidth / window.innerHeight, 1, 2000 );
82 camera.eulerOrder = "YXZ"
83 camera.position.z = 200;
84 camera.position.y = 40;
85 camera.position.x = 100;
86 camera.lookAt(getHexagonPositionFromLocation(Math.floor(xCount/2), 0, Math.floor(zCount/2)));
87
88 //Controls
89 controls = new THREE.FlyControls(camera);
90 controls.movementSpeed = 70;
91 controls.domElement = draw;
92 controls.rollSpeed = Math.PI / 7;
93 controls.autoForward = false;
94 controls.dragToLook = false;
95
96 //Scene
97 scene = new THREE.Scene();
98 scene.fog = new THREE.Fog(0xffffff, 500, 1200);
99
100 //Lights
101 hemiLight = new THREE.HemisphereLight( 0xffffff, 0xffffff, 0.6 );
102 hemiLight.color.setHSL( 0.6, 1, 0.6 );
103 hemiLight.groundColor.setHSL( 0.095, 1, 0.75 );
104 hemiLight.position.set( 0, 500, 0 );
105 scene.add( hemiLight );
106 dirLight = new THREE.DirectionalLight( 0xffffff, 1 );
107 dirLight.color.setHSL( 0.1, 1, 0.95 );
108 dirLight.position.set( -1, 1.75, 1 );
109 dirLight.position.multiplyScalar( 50 );
110 scene.add( dirLight );
111 dirLight.castShadow = true;
112 dirLight.shadowMapWidth = 2048;
113 dirLight.shadowMapHeight = 2048;
114 var d = 50;
115 dirLight.shadowCameraLeft = -d;
116 dirLight.shadowCameraRight = d;
117 dirLight.shadowCameraTop = d;
118 dirLight.shadowCameraBottom = -d;
119 dirLight.shadowCameraFar = 3500;
120 dirLight.shadowBias = -0.0001;
121 dirLight.shadowDarkness = 0.35;
122
123
124 //Ground
125 var ground = new THREE.Mesh(new THREE.PlaneGeometry(50000, 50000), new THREE.MeshPhongMaterial({ambient: ground, color: ground, specular: ground}) );
126 ground.name = 'ground';
127 ground.rotation.x = -Math.PI/2;
128 ground.position.y = 0;
129 scene.add( ground );
130 ground.receiveShadow = true;
131
132 //Board to store hexagon colors for loading, saving
133 board = new Board(xCount, zCount, yCount);
134
135 //Initializing floor hexagons
136 for (var x = 0; x < board.width; x++) {
137 for (var z = 0; z < board.depth; z++) {
138 var hexagon = newFloorHexagon(SIZE, THICKNESS);
139 var mesh = new THREE.Mesh(hexagon, new THREE.MeshLambertMaterial(
140 { color: COLORS[Math.floor(Math.random()*6)], shading: THREE.FlatShading, ambient: 0xffffff, wireframe: false }));
141 mesh.castShadow = true;
142 mesh.receiveShadow = true;
143 mesh.boardLocation = {x: x, z: z, y: 0};
144 mesh.name = "floor";
145 board.setHexagon({x: x, z: z, y: 0}, mesh.material.color);
146 scene.add(mesh);
147 mesh.position = getHexagonPositionFromLocation(x, 0, z);
148 }
149 }
150
151 //Phantom hexagon to act as selector for adding and removing.
152 var hexagon = newHexagon(SIZE, THICKNESS);
153 phantomHexagon = new THREE.Mesh(hexagon, new THREE.MeshNormalMaterial( { color: currentColor, emissive: currentColor, ambient: currentColor, transparent: true, opacity: 0 } ));
154 scene.add(phantomHexagon);
155 phantomHexagon.name = 'phantomHexagon';
156
157 //Projector and raycaster for instersection with mouse location
158 projector = new THREE.Projector();
159 raycaster = new THREE.Raycaster();
160
161 //Bind events
162 bindEvents();
163
164 //Engage the rendered
165 renderer = new THREE.WebGLRenderer( { antialias: false } );
166 renderer.setSize( window.innerWidth, window.innerHeight );
167 draw.appendChild( renderer.domElement );
168 renderer.setClearColor( sky, 1 );
169 renderer.gammaInput = true;
170 renderer.gammaOutput = true;
171 renderer.physicallyBasedShading = true;
172 renderer.shadowMapEnabled = false;
173 renderer.shadowMapCullFace = THREE.CullFaceBack;
174 document.body.appendChild( renderer.domElement );
175}
176
177//Binding common mouse events
178function bindEvents() {
179 //Handle apsect ratios when the user resizes
180 window.addEventListener('resize', onWindowResize, false);
181 //Change location of phantom hexagon on mouse move
182 $('#draw').on('mousemove', onDocumentMouseMove);
183 //Handle mouse clicks
184 $('#draw').on('click', onDocumentMouseClick);
185
186 //Select 'add hexagon' tool
187 $('.button.add').on('click', function(e) {
188 //remove is not active
189 var button = $('.button.remove');
190 var classes = button.attr('class');
191 classes = classes.replace(' active', '');
192 button.attr('class', classes);
193 //add is active
194 button = $('.button.add');
195 var classes = button.attr('class');
196 if (classes.search(' active') == -1) {
197 classes = classes + ' active';
198 button.attr('class', classes);
199 }
200 currentTool = TOOLS.add;
201 });
202
203 //Select 'remove hexagon' tool
204 $('.button.remove').on('click', function(e) {
205 //add is not active
206 var button = $('.button.add');
207 var classes = button.attr('class');
208 classes = classes.replace(' active', '');
209 button.attr('class', classes);
210 //remove is active
211 button = $('.button.remove');
212 var classes = button.attr('class');
213 if (classes.search('active') == -1) {
214 classes = classes + ' active';
215 button.attr('class', classes);
216 }
217 currentTool = TOOLS.remove;
218 });
219
220 //Save the current scene to local storage
221 $('.button.save').on('click', function(e) {
222 localStorage.hex = true;
223 var game = {tiles: board.getActiveTiles(), date: moment().format('MMMM Do YYYY, h:mm a')};
224 localStorage[currentGame] = JSON.stringify(game);
225 });
226
227 //Trigger the load modal, and populated it with saved games
228 $('.button.load').on('click', function(e) {
229 $('.modal-body.list').empty();
230 for (var i = 1; i < 30; i++) {
231 var name = 'game' + i.toString();
232 if (localStorage.hasOwnProperty(name)) {
233 $('.modal-body.list').append(gameElement);
234 var game = JSON.parse(localStorage[name]);
235 var single = $('.modal-body.list').children().last();
236 single.find('.how-many').text(game.tiles.length.toString() + ' hexagons');
237 single.find('.date').text(game.date);
238 single.find('.loadme a').attr('id', name).on('click', function(event) {
239 currentGame = event.target.id;
240 var game = JSON.parse(localStorage[currentGame]);
241 loadGame(game.tiles);
242 $('#loadModal').modal('hide');
243 });
244 } else {
245 break;
246 }
247 }
248 $('#loadModal').modal('toggle');
249 });
250
251 //Wipe the current game, start a new localStorage game
252 $('.button.new').on('click', function(e) {
253 clearScene();
254 resetScene();
255 for (var i = 1; i < 30; i++) {
256 var name = 'game' + i;
257 if (!localStorage.hasOwnProperty(name)) {
258 currentGame = name;
259 break;
260 }
261 }
262 });
263
264 //Show welcome modal for more information
265 $('.button.info').on('click', function(e) {
266 $('#welcomeModal').modal('show');
267 });
268
269 //Load demo game
270 $('.demo').on('click', function(e) {
271 $('#welcomeModal').modal('hide');
272 clearScene();
273 resetScene();
274 loadGame(demos[event.target.id].tiles);
275 });
276
277 //Bind mouse wheel for zoom/perspective control
278 $("#draw").bind('mousewheel', function (e) {
279 if(e.originalEvent.wheelDelta /120 > 0) {
280 if (camera.fov < 60)
281 camera.projectionMatrix.makePerspective( camera.fov += 2, window.innerWidth / window.innerHeight, 1, 2000 );
282 }
283 else {
284 if (camera.fov > 30)
285 camera.projectionMatrix.makePerspective( camera.fov -= 2, window.innerWidth / window.innerHeight, 1, 2000 );
286 }
287 });
288
289 //Binding the enter key to add a hexagon
290 $(document).on('keypress', function (event) {
291 if (event.keyCode == 13) {
292 switch (currentTool) {
293 case TOOLS.remove:
294 removeHexagon();
295 break;
296 case TOOLS.add:
297 addHexagon();
298 break;
299 }
300 }
301 });
302
303 //Binding the collor picker
304 $('.picker').colorpicker({color: '#0066cc'})
305 .on('changeColor', function(ev) {
306 currentColor = parseInt(ev.color.toHex().replace('#', '0x'));
307 $('.picker polygon.color').css('fill', currentColor.toString(16));
308 $('.picker polygon.drop').css('fill', currentColor.toString(16));
309 });
310
311
312 //Tool tips
313 $('[data-toggle="tooltip"]').tooltip({
314 'placement': 'bottom'
315 });
316}
317
318//Handling mouse moves
319function onDocumentMouseMove( event ) {
320 //When the mouse is over the game, and not the color picker, hide color picker
321 $('.picker').colorpicker('hide');
322 event.preventDefault();
323 //Capture mouse location
324 mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
325 mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
326}
327
328//Routing mouse click events based on tool
329function onDocumentMouseClick() {
330 switch (currentTool) {
331 case TOOLS.remove:
332 removeHexagon();
333 break;
334 case TOOLS.add:
335 addHexagon();
336 break;
337 }
338}
339
340//Reset the 'floor' hexagons for a new scene
341function resetScene() {
342 for (var x = 0; x < board.width; x++) {
343 for (var z = 0; z < board.depth; z++) {
344 var hexagon = newFloorHexagon(SIZE, THICKNESS);
345 var mesh = new THREE.Mesh(hexagon, new THREE.MeshLambertMaterial(
346 { color: COLORS[Math.floor(Math.random()*6)], shading: THREE.FlatShading, ambient: 0xffffff, wireframe: false }));
347 mesh.castShadow = true;
348 mesh.receiveShadow = true;
349 mesh.boardLocation = {x: x, z: z, y: 0};
350 mesh.name = "floor";
351 board.setHexagon({x: x, z: z, y: 0}, mesh.material.color);
352 scene.add(mesh);
353 mesh.position = getHexagonPositionFromLocation(x, 0, z);
354 }
355 }
356}
357
358//Load a saved game
359function loadGame(tiles) {
360 clearScene();
361 resetScene();
362
363 //Add the hexagons one by one
364 for (var i = 0; i < tiles.length; i++) {
365 var hexagon = newHexagon(SIZE, THICKNESS);
366 var mesh = new THREE.Mesh(hexagon, new THREE.MeshLambertMaterial(
367 { color: tiles[i].color, shading: THREE.FlatShading, ambient: 0xffffff, wireframe: false }));
368 mesh.castShadow = true;
369 mesh.receiveShadow = true;
370 scene.add(mesh);
371 mesh.name = 'tile';
372 mesh.position = getHexagonPositionFromLocation(tiles[i].x, tiles[i].y, tiles[i].z);
373 mesh.boardLocation = {x: tiles[i].x, y: tiles[i].y, z:tiles[i].z};
374 board.setHexagon({x: tiles[i].x, y: tiles[i].y, z:tiles[i].z}, tiles[i].color);
375
376 updateHexcount(hexcount+1);
377 }
378}
379
380//Clears an entire scene, including the floor.
381function clearScene() {
382 board = new Board(xCount, zCount, yCount);
383 var removeList = [];
384 for (var i = 0; i < scene.children.length; i++) {
385 if (scene.children[i].name == 'tile' || scene.children[i].name == 'floor') {
386 removeList.push(scene.children[i])
387 }
388 }
389 for (var i = 0; i < removeList.length; i++) {
390 scene.remove(removeList[i]);
391 }
392 updateHexcount(0);
393}
394
395//Push the hexagon count to the HUD
396function updateHexcount(number) {
397 hexcount = number;
398 $('.hexcount').text(hexcount + ' hexagons');
399}
400
401//Remove the selected hexagon
402function removeHexagon() {
403 if ((INTERSECTED.name != 'ground' && INTERSECTED.name != 'floor') && intersects[1].object.name !== 'phantomHexagon') {
404 scene.remove(intersects[1].object);
405 phantomHexagon.material.opacity = 0;
406 phantomHexagon.position = new THREE.Vector3(-100, -100, -100);
407 board.unsetHexagon({x: currentHexagonPosition.x, y: currentHexagonPosition.y, z:currentHexagonPosition.z});
408 updateHexcount(hexcount-1);
409 }
410}
411
412//Add a hexagon at the appropriate location
413function addHexagon() {
414 if (INTERSECTED.name != 'ground') {
415 var hexagon = newHexagon(SIZE, THICKNESS);
416 var mesh = new THREE.Mesh(hexagon, new THREE.MeshLambertMaterial(
417 { color: currentColor, shading: THREE.FlatShading, ambient: 0xffffff, wireframe: false }));
418 mesh.castShadow = true;
419 mesh.receiveShadow = true;
420 scene.add(mesh);
421 mesh.name = 'tile';
422 mesh.position = getHexagonPositionFromLocation(currentHexagonPosition.x, currentHexagonPosition.y, currentHexagonPosition.z);
423 mesh.boardLocation = {x: currentHexagonPosition.x, y: currentHexagonPosition.y, z:currentHexagonPosition.z};
424 board.setHexagon({x: currentHexagonPosition.x, y: currentHexagonPosition.y, z:currentHexagonPosition.z}, currentColor);
425 phantomHexagon.position.y += THICKNESS;
426 currentHexagonPosition.y++;
427 updateHexcount(hexcount+1);
428 }
429}
430
431//Places the phantomHexagon at the appropriate location, and keeps track of the foremost intersected hexagon
432function selector() {
433 var mouseVector = new THREE.Vector3(mouse.x, mouse.y, 1);
434 projector.unprojectVector(mouseVector, camera);
435 raycaster.set(camera.position, mouseVector.sub(camera.position).normalize());
436 intersects = raycaster.intersectObjects(scene.children);
437 if (intersects.length > 0) {
438 INTERSECTED = intersects[0].object;
439 if (INTERSECTED.name != 'ground' && INTERSECTED.name != 'phantomHexagon') {
440 switch (currentTool) {
441 case TOOLS.remove:
442 phantomHexagon.material.opacity = 1;
443 setPhantomPosition(INTERSECTED.position.x, INTERSECTED.position.y, INTERSECTED.position.z);
444 currentHexagonPosition = INTERSECTED.boardLocation;
445 break;
446 case TOOLS.add:
447 var topY = board.topMostHexagon({x: INTERSECTED.boardLocation.x, y: INTERSECTED.boardLocation.y, z: INTERSECTED.boardLocation.z});
448 phantomHexagon.material.opacity = 0.5;
449 phantomHexagon.position = getHexagonPositionFromLocation(INTERSECTED.boardLocation.x, topY, INTERSECTED.boardLocation.z);
450 currentHexagonPosition = {x: INTERSECTED.boardLocation.x, y: topY, z:INTERSECTED.boardLocation.z};
451 break;
452 }
453 }
454 if (INTERSECTED.name == 'ground') {
455 phantomHexagon.material.opacity = 0;
456 }
457 } else {
458 INTERSECTED = null;
459 phantomHexagon.material.opacity = 0;
460 }
461}
462
463//Set the phantomHexagons board location
464function setPhantomPosition(x, y, z) {
465 phantomHexagon.position.x = x;
466 phantomHexagon.position.y = y;
467 phantomHexagon.position.z = z;
468}
469
470//Get a hexagons real-world position based upon its board location
471function getHexagonPositionFromLocation(x, y, z) {
472 var position = new THREE.Vector3();
473 if (z % 2 == 0) {
474 position.x = x*WIDTH;
475 position.z = z*0.75*HEIGHT;
476 } else {
477 position.x = x*WIDTH + 0.5*WIDTH;
478 position.z = z*0.75*HEIGHT;
479 }
480 position.y = y*THICKNESS;
481 return position;
482}
483
484//Ensures the user doesn't fly the camera below the board, or too far in any direction.
485function checkCameraBoundries() {
486 // //Y Boundries.
487 if (camera.position.y < 10) {
488 camera.position.y = 10;
489 } else {
490 if (camera.position.y > 720) {
491 camera.position.y = 720;
492 }
493 }
494 //X Boundries
495 if (camera.position.x < -350) {
496 camera.position.x = -350;
497 } else {
498 if (camera.position.x > 1300) {
499 camera.position.x = 1300;
500 }
501 }
502 //Z Boundries
503 if (camera.position.z < -350) {
504 camera.position.z = -350;
505 } else {
506 if (camera.position.z > 1300) {
507 camera.position.z = 1300;
508 }
509 }
510}
511
512//When the user resizes the window, we handle the camera's aspect ratio
513function onWindowResize() {
514 camera.aspect = window.innerWidth / window.innerHeight;
515 camera.updateProjectionMatrix();
516 renderer.setSize( window.innerWidth, window.innerHeight );
517}
518
519//Perform selector update, control update, camera boundry checks, and render
520function animate() {
521 selector();
522 delta = clock.getDelta();
523 controls.update(delta);
524 checkCameraBoundries();
525 requestAnimationFrame(animate);
526 render();
527}
528
529//Render
530function render() {
531 renderer.render(scene, camera);
532}