diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..8d4aaf40
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+*.map
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..4d59ebb8
--- /dev/null
+++ b/index.html
@@ -0,0 +1,19 @@
+
+
+
+ HW7: BioCrowds
+
+
+
+
+
+
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..370fadd3
--- /dev/null
+++ b/package.json
@@ -0,0 +1,29 @@
+{
+ "scripts": {
+ "start": "webpack-dev-server --hot --inline",
+ "build": "webpack",
+ "deploy": "gh-pages-deploy"
+ },
+ "gh-pages-deploy": {
+ "prep": [
+ "build"
+ ],
+ "noprompt": true
+ },
+ "dependencies": {
+ "dat-gui": "^0.5.0",
+ "gl-matrix": "^2.3.2",
+ "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/agent.js b/src/agent.js
new file mode 100644
index 00000000..94783eb5
--- /dev/null
+++ b/src/agent.js
@@ -0,0 +1,105 @@
+const THREE = require('three');
+
+export default class Agent {
+
+ // Position, velocity, goal, orientation, size, markers
+ constructor(name, pos, vel, goal, size, markers) {
+ this.init(name, pos, vel, goal, size, markers);
+ }
+
+ init(name, pos, vel, goal, size, markers) {
+ this.name = name;
+ this.pos = pos;
+ this.vel = vel;
+ this.goal = goal;
+ this.size = size;
+ this.markers = markers;
+ }
+
+ clearMarkers() {
+ this.markers = [];
+ }
+
+ addMarker(marker) {
+ this.markers.push(marker);
+ // console.log(this.markers);
+ }
+
+ removeMarker(marker) {
+ var idx = this.markers.indexOf(marker);
+ if (idx > -1) {
+ this.markers = this.markers.splice(idx, 1);
+ }
+ }
+
+ setSize(size) {
+ this.size = size;
+ }
+
+ getMarkers() {
+ this.markers;
+ }
+
+ getName() {
+ return this.name;
+ }
+
+ getPos() {
+ return this.pos;
+ }
+
+ getSize() {
+ return this.size;
+ }
+
+ getDistToGoal() {
+ return Math.pow(this.pos.x - this.goal.x, 2) + Math.pow(this.pos.y - this.goal.y, 2)
+ }
+
+ getGoalDir() {
+ return new THREE.Vector2(this.goal.x - this.pos.x, this.goal.y - this.pos.y);
+ }
+
+ update() {
+ this.vel = new THREE.Vector2(0,0);
+
+ // console.log(this.getDistToGoal())
+ // if (this.getDistToGoal() < 1) {return;}
+
+ var goalDir = this.getGoalDir();
+ var cumWeight = 0;
+
+ // console.log(this.name + " " + this.markers.length)
+
+ for (var x = 0; x < this.markers.length; x++) {
+ var marker = this.markers[x];
+ var mPos = marker.getPos();
+ var markerDir = new THREE.Vector2(mPos.x - this.pos.x, mPos.y - this.pos.y);
+
+ // Get weight for this marker
+ var numerator = goalDir.normalize().dot(markerDir.normalize()) + 1;
+ var denominator = Math.sqrt(Math.pow(this.pos.x - mPos.x, 2) + Math.pow(this.pos.y - mPos.y, 2)) + 1;
+ var weight = numerator / denominator;
+
+ // Update velocity
+ this.vel.x += markerDir.x * weight;
+ this.vel.y += markerDir.y * weight;
+ cumWeight += weight;
+
+ }
+ // console.log(this.vel);
+
+ this.vel.x /= cumWeight;
+ this.vel.y /= cumWeight;
+
+ this.vel.x = this.vel.x ? this.vel.x : 0;
+ this.vel.y = this.vel.y ? this.vel.y : 0;
+ // console.log(this.vel);
+ }
+
+ updatePos(delta) {
+ this.pos.x += this.vel.x * delta;
+ this.pos.y += this.vel.y * delta;
+ // console.log(this.vel);
+ }
+}
\ No newline at end of file
diff --git a/src/framework.js b/src/framework.js
new file mode 100644
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/main.js b/src/main.js
new file mode 100644
index 00000000..8e4c7d30
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,233 @@
+
+const THREE = require('three'); // older modules are imported like this. You shouldn't have to worry about this much
+import Framework from './framework'
+import Agent from './agent'
+import Marker from './marker'
+
+
+var goals = {
+ options : ['corner', 'center'],
+ corner : function(i) {
+ var d = Math.pow(-1, i) * (fieldSize / 3)
+ return new THREE.Vector2(d, d);
+ },
+ center : function(i) {
+ return new THREE.Vector2(0, 0);
+ }
+}
+
+var start = {
+ options : ['random', 'circle'],
+ random : function() {
+ return new THREE.Vector2((Math.random() - 0.5) * fieldSize, (Math.random() - 0.5) * fieldSize);
+ },
+ circle : function() {
+ var angle = Math.random() * Math.PI * 2;
+ var x = Math.cos(angle) * fieldSize/3;
+ var y = Math.sin(angle) * fieldSize/3;
+ return new THREE.Vector2(x,y);
+ }
+}
+
+var fieldSize = 100;
+var numMarkers = 5000;
+var numAgents = 50;
+var maxAgentSize = 5;
+var distBuffer = 30;
+var speed = 0.5;
+
+var markers = [];
+var agents = [];
+var grid = {};
+
+var config = {
+ goal : "corner",
+ start : "circle"
+}
+
+function createMarkers() {
+ for (var i = 0; i < numMarkers; i++) {
+ var pos = new THREE.Vector2((Math.random() - 0.5) * fieldSize, (Math.random() - 0.5) * fieldSize);
+ var m = new Marker(pos);
+
+ markers.push(m);
+
+ var xG = Math.floor(pos.x / maxAgentSize);
+ var yG = Math.floor(pos.y / maxAgentSize);
+ var gridIdx = xG + '-' + yG;
+ if (!grid[gridIdx]) {
+ grid[gridIdx] = [];
+ }
+ grid[gridIdx].push(m);
+ }
+}
+
+function createAgents() {
+ agents = [];
+ for (var i = 0; i < numAgents; i++) {
+ var name = "agent" + i;
+
+ var posFunction = start[config.start];
+ var pos = posFunction();
+ var vel = new THREE.Vector2(0, 0);
+
+
+ var goalFunction = goals[config.goal];
+ var goal = goalFunction(i);
+ var size = 1;
+
+ var agent = new Agent(name, pos, vel, goal, size, []);
+ agents.push(agent);
+ }
+}
+
+// called after the scene loads
+function onLoad(framework) {
+ var scene = framework.scene;
+ var camera = framework.camera;
+ var renderer = framework.renderer;
+ var gui = framework.gui;
+ var stats = framework.stats;
+
+ // set camera position
+ camera.position.set(1, 1, 100);
+ camera.lookAt(new THREE.Vector3(0,0,0));
+
+ // Create plane
+ var squareGeometry = new THREE.PlaneGeometry(fieldSize, fieldSize);
+ var squareMaterial = new THREE.MeshBasicMaterial({
+ color:0xFFFFFF,
+ side:THREE.DoubleSide
+ });
+
+ var squareMesh = new THREE.Mesh(squareGeometry, squareMaterial);
+ squareMesh.position.set(0,0,0);
+ scene.add(squareMesh);
+
+ // Create markers and agents
+ createMarkers();
+ createAgents();
+
+ console.log(grid)
+
+ // // Add markers to scene
+ // for (var i = 0; i < numMarkers; i++) {
+ // var markerGeom = new THREE.CircleGeometry(1,8);
+ // var markerMaterial = new THREE.MeshBasicMaterial({
+ // color:0x000000,
+ // side:THREE.DoubleSide
+ // });
+
+ // var markerMesh = new THREE.Mesh(markerGeom, markerMaterial);
+ // var markerPos = markers[i].getPos();
+ // markerMesh.position.set(markerPos.x, markerPos.y, 1);
+ // scene.add(markerMesh);
+ // }
+
+ // Add agents to scene
+ for (var i = 0; i < numAgents; i++) {
+ var agent = agents[i];
+
+
+ var agentGeom = new THREE.CircleGeometry(agent.getSize(),8);
+ var agentMaterial = new THREE.MeshBasicMaterial({
+ color:0x00FF00,
+ side:THREE.DoubleSide
+ });
+
+ var agentMesh = new THREE.Mesh(agentGeom, agentMaterial);
+ var agentPos = agents[i].getPos();
+ agentMesh.position.set(agentPos.x, agentPos.y, 1);
+ agentMesh.name = agent.getName();
+ scene.add(agentMesh);
+ }
+
+ // edit params and listen to changes like this
+ // more information here: https://workshop.chromeexperiments.com/examples/gui/#1--Basic-Usage
+ gui.add(camera, 'fov', 0, 180).onChange(function(newVal) {
+ camera.updateProjectionMatrix();
+ });
+
+ gui.add(config, 'goal', goals.options).onChange(function(newVal){
+ config.goal = newVal;
+ createAgents();
+ });
+
+ gui.add(config, 'start', start.options).onChange(function(newVal){
+ config.start = newVal;
+ createAgents();
+ });
+}
+
+// called on frame updates
+function onUpdate(framework) {
+ if (markers.length == 0 || agents.length == 0) {
+ return;
+ }
+
+ for (var x = 0; x < markers.length; x++) {
+ markers[x].reset();
+ }
+
+ // Assign markers to agents
+ for (var x = 0; x < agents.length; x++) {
+ var agent = agents[x];
+ agent.clearMarkers();
+
+ var pos = agent.getPos();
+ var xG = Math.max(Math.floor(pos.x / maxAgentSize) - 1, -Math.floor(fieldSize / 2 / maxAgentSize));
+ var yG = Math.max(Math.floor(pos.y / maxAgentSize) - 1, -Math.floor(fieldSize / 2 / maxAgentSize));
+
+ for (var i = 0; i < 3; i++) {
+ for (var j = 0; j < 3; j++) {
+ var gridIdx = (xG + i) + '-' + (yG + j);
+ if (!grid[gridIdx]) {break;}
+
+ if (grid[gridIdx]) {
+ for (var y = 0; y < grid[gridIdx].length; y++) {
+ var marker = grid[gridIdx][y];
+ var mPos = marker.getPos();
+
+ var dist = (Math.pow(pos.x - mPos.x, 2) + Math.pow(pos.y - mPos.y, 2));
+ // console.log(dist);
+ if (dist < distBuffer) {
+
+ // Check case where marker is already assigned
+ if (!marker.getAgent()) {
+ agent.addMarker(marker);
+ marker.setAgent(agent);
+ marker.setDist(dist);
+ } else if (marker.getDist() > dist) {
+ // var oldAgent = marker.getAgent();
+ // oldAgent.removeMarker(marker);
+ agent.addMarker(marker);
+ marker.setAgent(agent);
+ marker.setDist(dist);
+ }
+
+
+
+ }
+ }
+ }
+ }
+ }
+ agents[x] = agent;
+ }
+
+ // Compute velocities and update scene
+ for (var x = 0; x < agents.length; x++) {
+ agents[x].update();
+ agents[x].updatePos(speed);
+
+ var mesh = framework.scene.getObjectByName(agents[x].getName());
+ if (mesh) {
+ var pos = agents[x].getPos();
+ mesh.position.set(pos.x, pos.y, 1);
+ mesh.needsUpdate = true;
+ }
+ }
+}
+
+// when the scene is done initializing, it will call onLoad, then on frame updates, call onUpdate
+Framework.init(onLoad, onUpdate);
\ No newline at end of file
diff --git a/src/marker.js b/src/marker.js
new file mode 100644
index 00000000..bb3d6417
--- /dev/null
+++ b/src/marker.js
@@ -0,0 +1,33 @@
+export default class Marker {
+
+ // Position, velocity, goal, orientation, size, markers
+ constructor(pos) {
+ this.pos = pos;
+ }
+
+ setAgent(agent) {
+ this.agent = agent;
+ }
+
+ setDist(dist) {
+ this.dist = dist;
+ }
+
+ getDist() {
+ return this.dist;
+ }
+
+ getAgent() {
+ return this.agent;
+ }
+
+ getPos() {
+ return this.pos;
+ }
+
+ reset() {
+ this.agent = null;
+ this.dist = null;
+ }
+
+}
\ No newline at end of file
diff --git a/src/shaders/adam-frag.glsl b/src/shaders/adam-frag.glsl
new file mode 100644
index 00000000..dd579bf4
--- /dev/null
+++ b/src/shaders/adam-frag.glsl
@@ -0,0 +1,16 @@
+varying vec2 vUv;
+varying vec3 nor;
+varying float noise;
+varying float mus;
+
+uniform sampler2D image;
+uniform vec3 colorMult;
+
+
+void main() {
+
+ vec4 color = texture2D( image, vUv );
+
+ gl_FragColor = vec4( (1. - noise) * nor.rgb + noise * colorMult, 1.0 );
+ // gl_FragColor = vec4( mus * color.rgb, 1.0 );
+}
\ No newline at end of file
diff --git a/src/shaders/adam-vert.glsl b/src/shaders/adam-vert.glsl
new file mode 100644
index 00000000..b821a35c
--- /dev/null
+++ b/src/shaders/adam-vert.glsl
@@ -0,0 +1,82 @@
+
+varying vec2 vUv;
+varying vec3 nor;
+varying float noise;
+varying float mus;
+
+uniform float inv_persistence;
+uniform float time;
+uniform float music;
+uniform float music2;
+
+#define M_PI 3.14159265
+const int N_OCTAVES = 5;
+
+float sampleNoise(vec3 pos) {
+ float x = fract(sin(dot(pos, vec3(134.9235, 63.5879, 218.9542))) * 27495.2467);
+ return x;
+}
+
+float interpolate(float a, float b, float t) {
+ float cos_t = (1. - cos(t * M_PI)) * 0.5;
+ return a * (1. - cos_t) + b * cos_t;
+}
+
+float interpNoise(vec3 pos, float f) {
+
+ // Calculate the min/max positions of cube
+ vec3 p0 = floor(pos * f) / f;
+ vec3 p1 = p0 + 1. / f;
+ vec3 t = (pos - p0) * f;
+
+ // Find noise values at corners of cube
+ float A = sampleNoise(vec3(p0.x, p0.y, p0.z));
+ float E = sampleNoise(vec3(p1.x, p0.y, p0.z));
+
+ float B = sampleNoise(vec3(p0.x, p1.y, p0.z));
+ float F = sampleNoise(vec3(p1.x, p1.y, p0.z));
+
+ float C = sampleNoise(vec3(p0.x, p0.y, p1.z));
+ float G = sampleNoise(vec3(p1.x, p0.y, p1.z));
+
+ float D = sampleNoise(vec3(p0.x, p1.y, p1.z));
+ float H = sampleNoise(vec3(p1.x, p1.y, p1.z));
+
+ // First pass of interpolation
+ float interpLi_AE = interpolate(A, E, t.x);
+ float interpLi_BF = interpolate(B, F, t.x);
+ float interpLi_CG = interpolate(C, G, t.x);
+ float interpLi_DH = interpolate(D, H, t.x);
+
+ // Second pass of interpolation
+ float interpBi_12 = interpolate(interpLi_AE, interpLi_BF, t.y);
+ float interpBi_34 = interpolate(interpLi_CG, interpLi_DH, t.y);
+
+ // Third pass
+ return interpolate(interpBi_12, interpBi_34, t.z);
+}
+
+float multiOctaveNoise(float offset) {
+
+ float total = 0.;
+ float persistence = 1. / inv_persistence;
+
+ for (int i = 0; i < N_OCTAVES; i++) {
+
+ float frequency = pow(2., float(i));
+ float amplitude = pow(persistence, float(i));
+ total += interpNoise(position + offset, frequency) * amplitude;
+ }
+
+ return total;
+}
+
+void main() {
+ vUv = uv;
+ mus = music;
+
+ noise = multiOctaveNoise(time);
+ nor = vec3(projectionMatrix * modelViewMatrix * vec4(normal, 0.));
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position + noise * nor * music, 1.);
+}
+
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
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