diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..3c3629e6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/.vscode/.browse.VC.db b/.vscode/.browse.VC.db
new file mode 100644
index 00000000..b95850ff
Binary files /dev/null and b/.vscode/.browse.VC.db differ
diff --git a/README.md b/README.md
index 2adc1c9e..8cc4e7f5 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,4 @@
-# FinalProject
\ No newline at end of file
+# CIS700 Procedural Graphics: Final Project
+
+Design Doc: `./writeups/design_doc.md`
+Milestone #1 Doc: `./writeups/milestone1_doc.md`
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100755
index 00000000..f775186a
--- /dev/null
+++ b/index.html
@@ -0,0 +1,19 @@
+
+
+
+ HW1: Noise
+
+
+
+
+
+
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..22581a96
--- /dev/null
+++ b/package.json
@@ -0,0 +1,32 @@
+{
+ "scripts": {
+ "start": "webpack-dev-server --hot --inline",
+ "build": "webpack",
+ "deploy": "gh-pages-deploy"
+ },
+ "gh-pages-deploy": {
+ "prep": [
+ "build"
+ ],
+ "noprompt": true
+ },
+ "dependencies": {
+ "chroma-js": "^1.3.3",
+ "dat-gui": "^0.5.0",
+ "gl-matrix": "^2.3.2",
+ "lodash": "^4.17.4",
+ "noisejs": "^2.1.0",
+ "stats-js": "^1.0.0-alpha1",
+ "three": "^0.82.1",
+ "three-orbit-controls": "^82.1.0"
+ },
+ "devDependencies": {
+ "babel-core": "^6.18.2",
+ "babel-loader": "^6.2.8",
+ "babel-preset-es2015": "^6.18.0",
+ "gh-pages-deploy": "^0.4.2",
+ "webpack": "^1.13.3",
+ "webpack-dev-server": "^1.16.2",
+ "webpack-glsl-loader": "^1.0.1"
+ }
+}
diff --git a/src/framework.js b/src/framework.js
new file mode 100755
index 00000000..9cfcd1b4
--- /dev/null
+++ b/src/framework.js
@@ -0,0 +1,75 @@
+
+const THREE = require('three');
+const OrbitControls = require('three-orbit-controls')(THREE)
+import Stats from 'stats-js'
+import DAT from 'dat-gui'
+
+// when the scene is done initializing, the function passed as `callback` will be executed
+// then, every frame, the function passed as `update` will be executed
+function init(callback, update) {
+ var stats = new Stats();
+ stats.setMode(1);
+ stats.domElement.style.position = 'absolute';
+ stats.domElement.style.left = '0px';
+ stats.domElement.style.top = '0px';
+ document.body.appendChild(stats.domElement);
+
+ var gui = new DAT.GUI();
+
+ var framework = {
+ gui: gui,
+ stats: stats
+ };
+
+ // run this function after the window loads
+ window.addEventListener('load', function() {
+
+ var scene = new THREE.Scene();
+ var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
+ var renderer = new THREE.WebGLRenderer( { antialias: true } );
+ renderer.setPixelRatio(window.devicePixelRatio);
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ renderer.setClearColor(0x020202, 0);
+
+ var controls = new OrbitControls(camera, renderer.domElement);
+ controls.enableDamping = true;
+ controls.enableZoom = true;
+ controls.target.set(0, 0, 0);
+ controls.rotateSpeed = 0.3;
+ controls.zoomSpeed = 1.0;
+ controls.panSpeed = 2.0;
+
+ document.body.appendChild(renderer.domElement);
+
+ // resize the canvas when the window changes
+ window.addEventListener('resize', function() {
+ camera.aspect = window.innerWidth / window.innerHeight;
+ camera.updateProjectionMatrix();
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ });
+
+ // assign THREE.js objects to the object we will return
+ framework.scene = scene;
+ framework.camera = camera;
+ framework.renderer = renderer;
+
+ // begin the animation loop
+ (function tick() {
+ stats.begin();
+ update(framework); // perform any requested updates
+ renderer.render(scene, camera); // render the scene
+ stats.end();
+ requestAnimationFrame(tick); // register to call this again when the browser renders a new frame
+ })();
+
+ // we will pass the scene, gui, renderer, camera, etc... to the callback function
+ return callback(framework);
+ });
+}
+
+export default {
+ init: init
+}
+
+export const PI = 3.14159265
+export const e = 2.7181718
\ No newline at end of file
diff --git a/src/geography-manager/geography-manager.js b/src/geography-manager/geography-manager.js
new file mode 100644
index 00000000..135e13bc
--- /dev/null
+++ b/src/geography-manager/geography-manager.js
@@ -0,0 +1,36 @@
+const THREE = require('three');
+const _ = require('lodash');
+const NOISEJS = require('noisejs');
+const CHROMA = require('chroma-js');
+
+export default class GeographyManager {
+ constructor(options, map) {
+ this.map = map;
+ }
+
+ generateElevationMap() {
+ var nodes = this.map.graphManager.nodes;
+ var cells = this.map.graphManager.cells;
+ var seed = Math.random();
+ var noise = new NOISEJS.Noise(seed);
+
+ nodes.forEach(function(node) {
+ var elevation = noise.simplex2(node.pos.x / 100, node.pos.y / 100);
+
+ var f = CHROMA.scale(['008ae5', 'yellow']).domain([-1, 1]);
+
+ node.color = f(elevation);
+ node.elevation = elevation;
+ });
+
+ cells.forEach(function(cell) {
+ var colors = [];
+
+ cell.corners.forEach(function(corner) {
+ colors.push(corner.color);
+ });
+
+ cell.color = CHROMA.average(colors);
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/graph-manager/cell.js b/src/graph-manager/cell.js
new file mode 100644
index 00000000..efa079ad
--- /dev/null
+++ b/src/graph-manager/cell.js
@@ -0,0 +1,17 @@
+export default class Cell {
+ constructor() {
+ this.center = {};
+ this.halfedges = [];
+ this.corners = [];
+ }
+
+ getElevation() {
+ var elevation = 0;
+
+ _.forEach(this.corners, function(corner) {
+ elevation += corner.elevation;
+ });
+
+ return elevation / this.corners.length;
+ }
+}
\ No newline at end of file
diff --git a/src/graph-manager/edge.js b/src/graph-manager/edge.js
new file mode 100644
index 00000000..6d8f01ad
--- /dev/null
+++ b/src/graph-manager/edge.js
@@ -0,0 +1,6 @@
+export default class Edge {
+ constructor(nodeA, nodeB) {
+ this.nodeA = nodeA;
+ this.nodeB = nodeB;
+ }
+}
\ No newline at end of file
diff --git a/src/graph-manager/graph-manager.js b/src/graph-manager/graph-manager.js
new file mode 100644
index 00000000..7d8ddad7
--- /dev/null
+++ b/src/graph-manager/graph-manager.js
@@ -0,0 +1,284 @@
+const _ = require('lodash');
+
+import Cell from './cell'
+import Edge from './edge'
+import Node from './node'
+import HalfEdge from './halfedge'
+
+export default class GraphManager {
+ constructor(options, map) {
+ this.numCells = options.numCells;
+ this.cellType = options.cellType;
+ this.map = map;
+ this.nodes = [];
+ this.edges = [];
+ this.cells = [];
+ }
+
+ generateGrid() {
+ if (this.cellType === 'square') {
+ this._generateFromSquareGrid();
+ } else if (this.cellType === 'hex') {
+ this._generateFromHexGrid();
+ } else {
+ // Unrecognized cell type
+ }
+ }
+
+ _generateFromSquareGrid() {
+ var nodesMap = {};
+ var edgesMap = {};
+
+ for (var x = 0; x <= this.numCells; x++) {
+ for (var y = 0; y <= this.numCells; y++) {
+ var node = new Node(x, y);
+ var nodesMapIndex = x + ' ' + y;
+
+ nodesMap[nodesMapIndex] = node;
+ }
+ }
+
+ for (var x = 0; x <= this.numCells; x++) {
+ for (var y = 0; y <= this.numCells; y++) {
+ var nodesMapIndex = x + ' ' + y;
+ var node = nodesMap[nodesMapIndex];
+ var adjCoors = [ [ x-1, y ], [ x+1, y ], [ x, y-1 ], [ x, y+1 ] ];
+
+ adjCoors.forEach(function(coors) {
+ var xAdj = coors[0];
+ var yAdj = coors[1];
+
+ if (xAdj >= 0 && xAdj <= this.numCells &&
+ yAdj >= 0 && yAdj <= this.numCells) {
+ var adjNodeMapIndex = xAdj + ' ' + yAdj;
+ var adjNode = nodesMap[adjNodeMapIndex];
+ var edgesMapIndex = nodesMapIndex + ' ' + adjNodeMapIndex;
+ var edgesMapOppositeIndex = adjNodeMapIndex + ' ' + nodesMapIndex;
+ var edgeInMap = edgesMap[edgesMapIndex] || edgesMap[edgesMapOppositeIndex];
+
+ if (!edgeInMap) {
+ var edge = new Edge(node, adjNode);
+
+ edgesMap[edgesMapIndex] = edge;
+ node.neighbors.push(adjNode);
+ adjNode.neighbors.push(node);
+ }
+ }
+ }, this);
+ }
+ }
+
+ for (var x = 0; x < this.numCells; x++) {
+ for (var y = 0; y < this.numCells; y++) {
+ var cell = new Cell();
+
+ var edgesMapIndexA = (x+1) + ' ' + y + ' ' + x + ' ' + y;
+ var edgesMapIndexB = x + ' ' + y + ' ' + x + ' ' + (y+1);
+ var edgesMapIndexC = x + ' ' + (y+1) + ' ' + (x+1) + ' ' + (y+1);
+ var edgesMapIndexD = (x+1) + ' ' + (y+1) + ' ' + (x+1) + ' ' + y;
+ var edgesMapIndices = [ edgesMapIndexA, edgesMapIndexB, edgesMapIndexC, edgesMapIndexD ];
+ var nodesMapIndices = [ (x+1) + ' ' + y, x + ' ' + y, x + ' ' + (y+1), (x+1) + ' ' + (y+1) ];
+
+ for (var i = 0; i < 4; i++) {
+ var edgesMapIndex = edgesMapIndices[i];
+ var nodesMapIndexA = nodesMapIndices[i];
+ var nodesMapIndexB = nodesMapIndices[(i+1) % 4];
+
+ var edge = edgesMap[edgesMapIndex];
+ var nodeA = nodesMap[nodesMapIndexA];
+ var nodeB = nodesMap[nodesMapIndexB];
+ var halfedge = new HalfEdge();
+
+ halfedge.cell = cell;
+ halfedge.edge = edge;
+ halfedge.nodeA = nodeA;
+ halfedge.nodeB = nodeB;
+ cell.halfedges.push(halfedge);
+ cell.corners.push(nodeA);
+ }
+
+ var numHalfEdges = cell.halfedges.length;
+
+ for (var i = 1; i <= numHalfEdges; i++) {
+ var halfedgeA = cell.halfedges[i-1];
+ var halfedgeB = cell.halfedges[i % numHalfEdges];
+
+ halfedgeA.next = halfedgeB;
+ }
+
+ cell.center.x = x+0.5;
+ cell.center.y = y+0.5;
+
+ this.cells.push(cell);
+ }
+ }
+
+ this.nodes = _.values(nodesMap);
+ this.edges = _.values(edgesMap);
+ }
+
+ _generateFromHexGrid() {
+ var nodesMap = {};
+ var edgesMap = {};
+
+ for (var x = 0; x < this.numCells; x++) {
+ for (var y = 0; y < this.numCells; y++) {
+ var hexCoors = [ [ 1.5, 0 ], [ 0.5, 0 ], [ 0, 1 ], [ 0.5, 2 ], [ 1.5, 2 ], [ 2, 1 ] ];
+
+ hexCoors.forEach(function(coor) {
+ var xCoor = coor[0] + (x * 2);
+ var yCoor = coor[1] + (y * 2);
+
+ if (x > 0) {
+ xCoor -= (0.5 * x);
+ }
+
+ if (x % 2) {
+ yCoor -= 1;
+ }
+
+ var nodesMapIndex = xCoor + ' ' + yCoor;
+
+ if (!nodesMap[nodesMapIndex]) {
+ var node = new Node(xCoor, yCoor);
+
+ nodesMap[nodesMapIndex] = node
+ }
+ });
+ }
+ }
+
+ for (var x = 0; x < this.numCells; x++) {
+ for (var y = 0; y < this.numCells; y++) {
+ var hexCoors = [ [ 1.5, 0 ], [ 0.5, 0 ], [ 0, 1 ], [ 0.5, 2 ], [ 1.5, 2 ], [ 2, 1 ] ];
+
+ hexCoors.forEach(function(coor) {
+ var xCoor = coor[0] + (x * 2);
+ var yCoor = coor[1] + (y * 2);
+
+ if (x > 0) {
+ xCoor -= (0.5 * x);
+ }
+
+ if (x % 2) {
+ yCoor -= 1;
+ }
+
+ var nodesMapIndex = xCoor + ' ' + yCoor;
+ var node = nodesMap[nodesMapIndex];
+ var adjCoors = [
+ [ xCoor-1, yCoor ],
+ [ xCoor+1, yCoor ],
+ [ xCoor+0.5, yCoor-1 ],
+ [ xCoor-0.5, yCoor-1 ],
+ [ xCoor+0.5, yCoor+1 ],
+ [ xCoor-0.5, yCoor+1 ]
+ ];
+
+ adjCoors.forEach(function(adjCoor) {
+ var xAdj = adjCoor[0];
+ var yAdj = adjCoor[1];
+
+ var adjNodeMapIndex = xAdj + ' ' + yAdj;
+ var adjNode = nodesMap[adjNodeMapIndex];
+
+ if (adjNode) {
+ var edgesMapIndex = nodesMapIndex + ' ' + adjNodeMapIndex;
+ var edgesMapOppositeIndex = adjNodeMapIndex + ' ' + nodesMapIndex;
+ var edgeInMap = edgesMap[edgesMapIndex] || edgesMap[edgesMapOppositeIndex];
+
+ if (!edgeInMap) {
+ var edge = new Edge(node, adjNode);
+
+ edgesMap[edgesMapIndex] = edge;
+ node.neighbors.push(adjNode);
+ adjNode.neighbors.push(node);
+ }
+ }
+ }, this);
+ });
+ }
+ }
+
+ for (var x = 0; x < this.numCells; x++) {
+ for (var y = 0; y < this.numCells; y++) {
+ var cell = new Cell();
+
+ var edgesMapIndexA = (1.5) + ' ' + (0) + ' ' + (0.5) + ' ' + (0);
+ var edgesMapIndexB = (0.5) + ' ' + (0) + ' ' + (0) + ' ' + (1);
+ var edgesMapIndexC = (0) + ' ' + (1) + ' ' + (0.5) + ' ' + (2);
+ var edgesMapIndexD = (0.5) + ' ' + (2) + ' ' + (1.5) + ' ' + (2);
+ var edgesMapIndexE = (1.5) + ' ' + (2) + ' ' + (2) + ' ' + (1);
+ var edgesMapIndexF = (2) + ' ' + (1) + ' ' + (1.5) + ' ' + (0);
+ var edgesMapIndices = [
+ edgesMapIndexA, edgesMapIndexB, edgesMapIndexC,
+ edgesMapIndexD, edgesMapIndexE, edgesMapIndexF
+ ];
+ var nodesMapIndices = [
+ (1.5) + ' ' + (0),
+ (0.5) + ' ' + (0),
+ (0) + ' ' + (1),
+ (0.5) + ' ' + (2),
+ (1.5) + ' ' + (2),
+ (2) + ' ' + (1)
+ ];
+
+ var xOffset = (x > 0) ? -0.5 * x : 0;
+ var yOffset = (x % 2) ? -1 : 0;
+
+ for (var i = 0; i < 6; i++) {
+ var edgesMapIndex = edgesMapIndices[i];
+ var nodesMapIndexA = nodesMapIndices[i];
+ var nodesMapIndexB = nodesMapIndices[(i+1) % 6];
+
+ var edgesMapIndexSplit = edgesMapIndex.split(' ');
+ var nodesMapIndexSplitA = nodesMapIndexA.split(' ');
+ var nodesMapIndexSplitB = nodesMapIndexB.split(' ');
+
+ edgesMapIndexSplit[0] = (Number(edgesMapIndexSplit[0]) + (x*2)) + xOffset;
+ edgesMapIndexSplit[1] = (Number(edgesMapIndexSplit[1]) + (y*2)) + yOffset;
+ edgesMapIndexSplit[2] = (Number(edgesMapIndexSplit[2]) + (x*2)) + xOffset;
+ edgesMapIndexSplit[3] = (Number(edgesMapIndexSplit[3]) + (y*2)) + yOffset;
+
+ nodesMapIndexSplitA[0] = (Number(nodesMapIndexSplitA[0]) + (x*2)) + xOffset;
+ nodesMapIndexSplitA[1] = (Number(nodesMapIndexSplitA[1]) + (y*2)) + yOffset;
+ nodesMapIndexSplitB[0] = (Number(nodesMapIndexSplitB[0]) + (x*2)) + xOffset;
+ nodesMapIndexSplitB[1] = (Number(nodesMapIndexSplitB[1]) + (y*2)) + yOffset;
+
+ edgesMapIndex = edgesMapIndexSplit.join(' ');
+ nodesMapIndexA = nodesMapIndexSplitA.join(' ');
+ nodesMapIndexB = nodesMapIndexSplitB.join(' ');
+
+ var edge = edgesMap[edgesMapIndex];
+ var nodeA = nodesMap[nodesMapIndexA];
+ var nodeB = nodesMap[nodesMapIndexB];
+ var halfedge = new HalfEdge();
+
+ halfedge.cell = cell;
+ halfedge.edge = edge;
+ halfedge.nodeA = nodeA;
+ halfedge.nodeB = nodeB;
+ cell.halfedges.push(halfedge);
+ cell.corners.push(nodeA);
+ }
+
+ var numHalfEdges = cell.halfedges.length;
+
+ for (var i = 1; i <= numHalfEdges; i++) {
+ var halfedgeA = cell.halfedges[i-1];
+ var halfedgeB = cell.halfedges[i % numHalfEdges];
+
+ halfedgeA.next = halfedgeB;
+ }
+
+ cell.center.x = (x*2)+1 + xOffset;
+ cell.center.y = (y*2)+1 + yOffset;
+
+ this.cells.push(cell);
+ }
+ }
+
+ this.nodes = _.values(nodesMap);
+ this.edges = _.values(edgesMap);
+ }
+}
\ No newline at end of file
diff --git a/src/graph-manager/halfedge.js b/src/graph-manager/halfedge.js
new file mode 100644
index 00000000..4fd71996
--- /dev/null
+++ b/src/graph-manager/halfedge.js
@@ -0,0 +1,5 @@
+export default class HalfEdge {
+ constructor() {
+
+ }
+}
\ No newline at end of file
diff --git a/src/graph-manager/node.js b/src/graph-manager/node.js
new file mode 100644
index 00000000..79c8c446
--- /dev/null
+++ b/src/graph-manager/node.js
@@ -0,0 +1,11 @@
+const THREE = require('three');
+
+export default class Node {
+ constructor(x, y, id) {
+ this.id = id;
+ this.x = x;
+ this.y = y;
+ this.pos = new THREE.Vector3(x, y, 0);
+ this.neighbors = [];
+ }
+}
\ No newline at end of file
diff --git a/src/main.js b/src/main.js
new file mode 100755
index 00000000..4a733643
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,53 @@
+const THREE = require('three');
+
+import Framework from './framework';
+import MapManager from './map';
+import GraphManager from './graph-manager/graph-manager';
+import GeographyManager from './geography-manager/geography-manager';
+import ViewManager from './view-manager/view-manager';
+
+function onLoad(framework) {
+ var { scene, camera, renderer, gui, stats } = framework;
+
+ var options = {
+ graphManager: {
+ cellType: 'hex', // 'square', 'hex'
+ numCells: 50
+ },
+ geographyManager: {},
+ viewManager: {
+ renderGraph: true,
+ renderElevation: true,
+ renderCoastline: true,
+ debugOcean: false,
+ debugShowNodes: false
+ }
+ };
+
+ var map = new Map();
+
+ var graphManager = new GraphManager(options.graphManager, map);
+ var geographyManager = new GeographyManager(options.geographyManager, map);
+ var viewManager = new ViewManager(options.viewManager, map, scene);
+
+ map.graphManager = graphManager;
+ map.geographyManager = geographyManager;
+ map.viewManager = viewManager;
+
+ graphManager.generateGrid()
+ geographyManager.generateElevationMap();
+ viewManager.renderMap();
+
+ camera.position.set(0, 0, 100);
+ camera.lookAt(new THREE.Vector3(0,0,0));
+
+ gui.add(camera, 'fov', 0, 180).onChange(function(newVal) {
+ camera.updateProjectionMatrix();
+ });
+}
+
+function onUpdate(framework) {
+ var { scene, camera, renderer, gui, stats } = framework;
+}
+
+Framework.init(onLoad, onUpdate);
\ No newline at end of file
diff --git a/src/map.js b/src/map.js
new file mode 100644
index 00000000..e21df2a8
--- /dev/null
+++ b/src/map.js
@@ -0,0 +1,5 @@
+export default class Map {
+ constructor() {
+
+ }
+}
\ No newline at end of file
diff --git a/src/shaders/part-frag.glsl b/src/shaders/part-frag.glsl
new file mode 100755
index 00000000..c8869c71
--- /dev/null
+++ b/src/shaders/part-frag.glsl
@@ -0,0 +1,9 @@
+varying vec3 vNormal;
+varying float noise;
+uniform sampler2D image;
+
+
+void main() {
+ vec3 color = vec3(0.0, 0.0, 0.0);
+ gl_FragColor = vec4( color, 1.0 );
+}
\ No newline at end of file
diff --git a/src/shaders/part-vert.glsl b/src/shaders/part-vert.glsl
new file mode 100755
index 00000000..e6f9a0c7
--- /dev/null
+++ b/src/shaders/part-vert.glsl
@@ -0,0 +1,6 @@
+varying vec3 vNormal;
+
+void main() {
+ vNormal = normal;
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
\ No newline at end of file
diff --git a/src/view-manager/view-manager.js b/src/view-manager/view-manager.js
new file mode 100644
index 00000000..1f57811e
--- /dev/null
+++ b/src/view-manager/view-manager.js
@@ -0,0 +1,228 @@
+const THREE = require('three');
+const _ = require('lodash');
+const CHROMA = require('chroma-js');
+
+export default class ViewManager {
+ constructor(options, map, scene) {
+ this.renderGraph = options.renderGraph;
+ this.renderElevation = options.renderElevation;
+ this.renderCoastline = options.renderCoastline;
+ this.debugOcean = options.debugOcean;
+ this.debugShowNodes = options.debugShowNodes;
+ this.map = map;
+ this.scene = scene;
+ }
+
+ renderMap() {
+ if (this.renderGraph) {
+ this._renderGraph();
+ }
+
+ if (this.renderElevation) {
+ this._renderElevation();
+ }
+
+ if (this.renderCoastline) {
+ this._renderCoastline();
+ }
+ }
+
+ _renderGraph() {
+ var edges = this.map.graphManager.edges;
+ var nodePairs = [];
+ var nodePairsColor = CHROMA('white');
+
+ edges.forEach(function(edge) {
+ nodePairs.push(edge.nodeA);
+ nodePairs.push(edge.nodeB);
+ }, this);
+
+ this._renderLineSegments(nodePairs, nodePairsColor);
+
+ if (this.debugShowNodes) {
+ var nodes = this.map.graphManager.nodes;
+ var nodesColor = CHROMA('white');
+
+ this._renderPoints(nodes, nodesColor);
+ }
+ }
+
+ _renderElevation() {
+ var cells = this.map.graphManager.cells;
+
+ cells.forEach(function(cell) {
+ var color = (this.debugOcean && cell.getElevation() <= 0) ? CHROMA('red') : cell.color;
+
+ this._renderCell(cell, color);
+ }, this);
+ }
+
+ _renderCoastline() {
+ var cells = this.map.graphManager.cells;
+
+ cells.forEach(function(cell) {
+ var corners = cell.corners;
+ var isCoastal = false;
+
+ corners.forEach(function(node) {
+ if (node.elevation <= 0) {
+ isCoastal = true;
+ }
+ });
+
+ if (isCoastal) {
+ corners.forEach(function(node) {
+ if (node.elevation > 0) {
+ node.isCoastal = true;
+ }
+ })
+ }
+ });
+
+ this._renderCoastlineHelper();
+ }
+
+ _render3D() {
+ var geometry = new THREE.Geometry();
+ var cells = this.map.graphManager.cells;
+ var positionsVisited = {};
+
+ cells.forEach(function(cell) {
+ var corners = cell.corners;
+ var faceIndices = [];
+
+ corners.forEach(function(node, i) {
+ var pos = node.pos.clone().setComponent(2, node.elevation * 10);
+ var posMapIndex = pos.x + ' ' + pos.y;
+ var id = positionsVisited[posMapIndex];
+
+ if (_.isUndefined(id)) {
+ id = geometry.vertices.length;
+ positionsVisited[posMapIndex] = id;
+ geometry.vertices.push(pos);
+ }
+
+ faceIndices.push(id);
+ });
+
+ var color = cell.color;
+ var normal = new THREE.Vector3(1, 1, 1);
+ var materialIndex = 0;
+
+ for (var i = 2; i < faceIndices.length; i++) {
+ var ia = faceIndices[0];
+ var ib = faceIndices[i - 1];
+ var ic = faceIndices[i];
+
+ var face = new THREE.Face3(ia, ib, ic, normal, color, materialIndex);
+
+ geometry.faces.push(face);
+ }
+ });
+
+
+
+ var material = new THREE.MeshStandardMaterial({ color: 0xffffff });
+ var mesh = new THREE.Mesh(geometry, material);
+
+ this.scene.add(mesh);
+
+ }
+
+ _renderCoastlineHelper() {
+ var nodes = this.map.graphManager.nodes;
+ var material = new THREE.LineBasicMaterial({
+ color: 0x000000,
+ linewidth: 2
+ });
+ var group = new THREE.Group();
+
+ nodes.forEach(function(node) {
+ if (node.isCoastal) {
+
+ var neighbors = node.neighbors;
+
+ neighbors.forEach(function(neighbor) {
+ if (neighbor.isCoastal) {
+ var geometry = new THREE.Geometry();
+
+ geometry.vertices.push(node.pos);
+ geometry.vertices.push(neighbor.pos);
+
+ var line = new THREE.Line(geometry, material);
+
+ group.add(line);
+ }
+ });
+ }
+ });
+
+ group.translateZ(0.2);
+
+ this.scene.add(group);
+ }
+
+ _renderPoints(nodes, color) {
+ var material = new THREE.PointsMaterial({ color: color });
+ var geometry = new THREE.Geometry();
+
+ _.forEach(nodes, function(node) {
+ geometry.vertices.push(node.pos);
+ })
+
+ var points = new THREE.Points(geometry, material);
+
+ points.translateZ(0.2);
+
+ this.scene.add(points);
+ }
+
+ _renderLine(nodes, color) {
+ var material = new THREE.LineBasicMaterial({ color: color });
+ var geometry = new THREE.Geometry();
+
+ nodes.forEach(function(node) {
+ geometry.vertices.push(node.pos);
+ });
+
+ var line = new THREE.Line(geometry, material);
+
+ line.translateZ(0.1);
+
+ this.scene.add(line);
+ }
+
+ _renderLineSegments(nodes, color) {
+ var material = new THREE.LineBasicMaterial({ color: color });
+ var geometry = new THREE.Geometry();
+
+ nodes.forEach(function(node) {
+ geometry.vertices.push(node.pos);
+ });
+
+ var line = new THREE.LineSegments(geometry, material);
+
+ line.translateZ(0.1);
+
+ this.scene.add(line);
+ }
+
+ _renderCell(cell, color) {
+ var halfedges = cell.halfedges;
+ var positions = [];
+
+ positions.push(halfedges[0].nodeA.pos);
+
+ _.forEach(cell.halfedges, function(halfedge) {
+ positions.push(halfedge.nodeB.pos);
+ });
+
+ var shape = new THREE.Shape(positions);
+ var geometry = new THREE.ShapeGeometry(shape);
+ var material = new THREE.MeshBasicMaterial({ color: color.hex() });
+ var mesh = new THREE.Mesh(geometry, material);
+
+ this.scene.add(mesh);
+ }
+
+}
\ No newline at end of file
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100755
index 00000000..57dce485
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,28 @@
+const path = require('path');
+
+module.exports = {
+ entry: path.join(__dirname, "src/main"),
+ output: {
+ filename: "./bundle.js"
+ },
+ module: {
+ loaders: [
+ {
+ test: /\.js$/,
+ exclude: /(node_modules|bower_components)/,
+ loader: 'babel',
+ query: {
+ presets: ['es2015']
+ }
+ },
+ {
+ test: /\.glsl$/,
+ loader: "webpack-glsl"
+ },
+ ]
+ },
+ devtool: 'source-map',
+ devServer: {
+ port: 7000
+ }
+}
\ No newline at end of file
diff --git a/writeups/design_doc.md b/writeups/design_doc.md
new file mode 100644
index 00000000..aaa864f6
--- /dev/null
+++ b/writeups/design_doc.md
@@ -0,0 +1,80 @@
+# Zany Maps: Procedurally Generated Maps
+
+Zack Elliott
+
+### Introduction:
+
+I read a lot of fantasy books when I was younger (Redwall, The 13½ Lives of Captain Bluebear, The Bartimaeus Trilogy, etc…) and always enjoyed flipping to the cover to look at the maps the author and illustrator had created. In an attempt to reignite my childhood passion for awesome fantasy maps, I thought it would be neat to build a web service that procedurally generates these maps.
+
+### Goal:
+
+Zany Maps is a service that procedurally generates realistic and interesting maps. Every aspect of these maps – including the geography, topography, and coloring – will be generated using procedural concepts I have learned in CIS 700.
+
+### Inspiration / Reference:
+
+Polygon Map Generation:
+ - Main demo: http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/demo.html
+ - Accompanying article: http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/
+
+
+
+Generating Fantasy Maps:
+ - Main demos and article: https://mewo2.com/notes/terrain/
+
+
+
+### Specification:
+
+Core features:
+ - Users can efficiently create and customize procedurally generated maps
+ - Users can select methods of generating the continent’s shape
+ - Points structure (e.g. Voronoi diagram, square grid, hexagon grid, etc…)
+ - Noise function
+ - Other minor parameters (e.g. number of points)
+ - Users can apply additional levels of detail onto the map
+ - Elevation
+ - Biomes
+ - Rivers and lakes
+ - Users can select to view the map in 3D
+ - Tessellation process transforms the 2D map along with its elevation to a 3D representation
+
+Additional features:
+ - Users can control erosion over time to transform the map
+ - Users can switch between various shader effects based upon map properties
+
+### Techniques:
+
+Below are a list of techniques and concepts I have learned in CIS 700. Below each concept are specific ways that use it in my project.
+ - Noise functions
+ - Generate and fine tune geography
+ - Generate and fine tune topography
+ - Distribute biomes
+ - Voronoi diagrams
+ - Generate underlying geography
+ - Traverse with traditional graph algorithms
+ - Gradient maps
+ - Color regions of the map by biome and topography
+ - Enable different gradient maps depending on the map view (elevation, moisture, etc…)
+ - Use shaders to efficiently color the map by various metrics
+ - 3D rendering
+ - Implement a polygonised 3D view of the generated map
+
+### Design:
+
+
+
+### Timeline:
+
+ - Week of 4/3
+ - Setup basic project framework (using HW base code) and user interface (DAT.GUI)
+ - Implement the Grid Manager (responsible for generating the base grid)
+ - Week of 4/10
+ - Implement the Continent Manager (responsible for defining the base coastlines and shape of the continent)
+ - Implement basic rendering
+ - Week of 4/17
+ - Implement components of the Geography Manager (elevation, biomes, rivers, lakes, etc…) and the View Manager in conjunction. This will be necessary as in order to debug aspects of the Geography Manager, I will need to be able to properly render them.
+ - Week of 4/24
+ - Continue with the week of 4/17 task
+ - Week of 5/1
+ - Fine tune and polish project
+ - If time, implement Erosion Manager (erodes basic continent & coastline)
diff --git a/writeups/images/image_1.PNG b/writeups/images/image_1.PNG
new file mode 100644
index 00000000..6ece0b53
Binary files /dev/null and b/writeups/images/image_1.PNG differ
diff --git a/writeups/images/image_2.PNG b/writeups/images/image_2.PNG
new file mode 100644
index 00000000..a0405800
Binary files /dev/null and b/writeups/images/image_2.PNG differ
diff --git a/writeups/images/image_3.PNG b/writeups/images/image_3.PNG
new file mode 100644
index 00000000..3cbf926d
Binary files /dev/null and b/writeups/images/image_3.PNG differ
diff --git a/writeups/images/milestone1_1.PNG b/writeups/images/milestone1_1.PNG
new file mode 100644
index 00000000..24192efa
Binary files /dev/null and b/writeups/images/milestone1_1.PNG differ
diff --git a/writeups/images/milestone1_2.png b/writeups/images/milestone1_2.png
new file mode 100644
index 00000000..b65d1e3e
Binary files /dev/null and b/writeups/images/milestone1_2.png differ
diff --git a/writeups/milestone1_doc.md b/writeups/milestone1_doc.md
new file mode 100644
index 00000000..3634f784
--- /dev/null
+++ b/writeups/milestone1_doc.md
@@ -0,0 +1,30 @@
+# Milestone 1
+
+### Work completed
+
+I implemented a number of components in the Zany Maps pipeline for this milestone. First, I designed the general pipeline flow. I begin by instantiating a Map object, which acts as the overall encapsulating object for my project. Then, I instantiate various pipeline components: GraphManager, GeographyManager, and ViewManager. Each of these manager components are instantiated with (1) a reference to the parent Map object (so that they have access to one another's data) and (2) a number of options parameters users can tweak via DAT.gui. Finally, I call various methods on each of these managers in sequence in order to construct and render the map one step at a time.
+
+The GraphManager holds all grid and graph instantiation and access. I implemented the methods `generateFromSquareGrid()` and `generateFromHexGrid()`, which allows you to generate different types of grids. In addition, GraphManager stores the grids as graphs via lists of nodes, edges, and cells, allowing for easy traversal of the grid. There is a list of Cells, each of which has a center, a list of half edges, and corners. Corners are equivalent to Nodes, each of which has an id, position, and a list of neighbors. Half edges are currently unused, although they will be useful in traversing the map during more advanced rendering (e.g. triangulation) and coastline manipulation (e.g. erosion). Finally, there is also a list of Edges, each of which has start and end nodes. This storing method is fairly quick to set up, and allows for easy access and traversal of the graph.
+
+The GeographyManager currently manages only generating elevation maps, via the aptly named method `generateElevationMap()`. This method essentially applies Perlin noise on the scale from -1 to 1 to each corner of each cell (i.e. each node in the graph). This noise is set as that node's elevation. Furthermore, each cell's elevation is the average of the elevations of its corners.
+
+The ViewManager handles all map rendering. The rendering is split up into disjoint methods in order to allow for various portions of the map to rendered or not rendered separately.
+ - `renderGraph()` simply renders the the grid itself.
+ - `renderElevation()` renders a gradient map for each cell that maps to each cell's elevation.
+ - `renderCoastline()` renders a thicker black line that denotes the coastline of the map based upon elevation. Any cell/node under sea level (i.e. an elevation of 0), is treated as ocean (and vice versa for land)
+
+Below are various screenshots the project's current state:
+
+
+
+Grid, elevation, and coastline rendered for a hexagon-cell grid
+
+
+
+Grid, elevation, and coastline rendered for a square-cell grid
+
+### To demo:
+
+ 1. Simply run `npm install` and `npm run start`
+ 2. Open up `http://localhost:7000`
+ 3. Go to `src/main.js` and experiment with the map paramters in the `options` object declared on line 12.