Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
npm-debug.log
49,315 changes: 49,315 additions & 0 deletions build/bundle.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions build/bundle.js.map

Large diffs are not rendered by default.

49,315 changes: 49,315 additions & 0 deletions bundle.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions bundle.js.map

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<title>BioCrowds </title>
<style>
html, body {
margin: 0;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
22 changes: 22 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"scripts": {
"start": "webpack-dev-server --hot --inline",
"deploy": "git push origin `git subtree split --prefix build master`:gh-pages --force",
"build": "webpack"
},
"dependencies": {
"babel-core": "^6.22.1",
"babel-loader": "^6.2.10",
"babel-preset-es2015": "^6.22.0",
"dat-gui": "^0.5.0",
"file-loader": "^0.10.0",
"stats-js": "^1.0.0-alpha1",
"three": "^0.84.0",
"three-effectcomposer": "0.0.1",
"three-obj-loader": "^1.0.2",
"three-orbit-controls": "^82.1.0",
"webpack": "^2.2.1",
"webpack-dev-server": "^2.3.0",
"webpack-glsl-loader": "^1.0.1"
}
}
87 changes: 87 additions & 0 deletions src/agent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
const THREE = require('three'); // older modules are imported like this. You shouldn't have to worry about this much

var NEIGHBORHOOD_RADIUS = 3; // minimum distance threshold for how close a marker must be to be considered by this agent in GRID/TAXICAB UNITS
var MAX_SPEED = 8;
export var AGENT_HEIGHT = 2;

export class Agent {
constructor(position, goalPos, geoColor) {
this.position = position;
this.planePosition = new THREE.Vector3(this.position.x, this.position.y - AGENT_HEIGHT / 2, this.position.z); // position in the ground plane

this.geometry = new THREE.CylinderGeometry(0.5, 0.5, AGENT_HEIGHT);
this.mesh = new THREE.Mesh(this.geometry, new THREE.MeshLambertMaterial( { color : geoColor } ));
this.mesh.position.set(this.position.x, this.position.y, this.position.z);

this.goalPos = goalPos;
this.goalDirection = new THREE.Vector3(0, 0, 0);
this.goalDirection = this.goalDirection.subVectors(goalPos, position).normalize();
this.direction = this.goalDirection;
this.nearbyMarkers = new Array();
}

addMarker(m) {
this.nearbyMarkers.push(m);
}

clearMarkers() {
this.nearbyMarkers = new Array();
}

setNearbyMarkers(grid, sceneAgents) {
this.nearbyMarkers = new Array();

var xIndex = Math.ceil(this.position.x) + 20;
var yIndex = Math.ceil(this.position.z) + 20;

for(var i = xIndex - NEIGHBORHOOD_RADIUS; i < xIndex + NEIGHBORHOOD_RADIUS; i++) {
for(var j = yIndex - NEIGHBORHOOD_RADIUS; j < yIndex + NEIGHBORHOOD_RADIUS; j++) {
if(i < 0) {
i = 0;
}
if(j < 0) {
j = 0;
}
var currentCell = grid[i][j];
for(var k = 0; k < currentCell.length; k++) {
var currentMarker = currentCell[k];
currentMarker.setOwner(sceneAgents);
}
}
}
}

computeNewDirection() {
var weightedDirection = new THREE.Vector3(0, 0, 0);
var totalContribution = 0;
for(var i = 0; i < this.nearbyMarkers.length; i++) {

// Get the displacement vector between the current marker and this agent
var currentMarker = this.nearbyMarkers[i];
var displacement = new THREE.Vector3(0, 0, 0);
displacement.subVectors(currentMarker.position, this.planePosition);
var displacementMagnitude = displacement.length();

displacement.normalize();

// Compute the weight for this vector
var cosTheta = displacement.dot(this.goalDirection);
var weight = (1 + cosTheta) / (1 + displacementMagnitude);

// Account for distance from the agent
var weightedDir = displacement.multiplyScalar(weight);
weightedDirection.add(weightedDir);
totalContribution += weight;
}

weightedDirection.multiplyScalar(1 / totalContribution);

// Cap the speed at max speed
if(weightedDirection.length() > MAX_SPEED) {
weightedDirection.setLength(MAX_SPEED);
}

this.direction = weightedDirection;
}

}
75 changes: 75 additions & 0 deletions src/framework.js
Original file line number Diff line number Diff line change
@@ -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;
controls.addEventListener('change', function() {
camera.hasMoved = true;
});

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);
}, false);

// 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
}
184 changes: 184 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
const THREE = require('three'); // older modules are imported like this. You shouldn't have to worry about this much
import Framework from './framework'
import Marker from './marker'
import {AGENT_HEIGHT, Agent} from './agent'

var sceneLoader = {};
var planeMesh;
var planeScale = 40;

// Uniform grid for acceleration
var grid = new Array();
// initialize the grid to be a 2d array
for(var i = 0; i < planeScale + 4; i++) {
grid.push(new Array());
for(var j = 0; j < planeScale + 4; j++) { //each 2d array index must contain a set of markers
grid[i][j] = new Array();
}
}

var sceneMarkers = new Array();
var sceneAgents = new Array();

// Timekeeping
var clock = new THREE.Clock();

// 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;

// Initialize a simple box and material
var light = new THREE.DirectionalLight( 0xffffff, 10 );
light.color.setHSL(0.1, 1, 0.95);
light.position.set(1 * 20, 10 * 20, 2 * 20);
scene.add(light);

// Add the ground plane
var planeGeometry = new THREE.PlaneGeometry(1, 1);
var planeMaterial = new THREE.MeshBasicMaterial( {color: 0x778899, side: THREE.DoubleSide} );
planeMesh = new THREE.Mesh( planeGeometry, planeMaterial );
planeMesh.rotateX(90 * Math.PI / 180);
planeMesh.scale.x = planeScale;
planeMesh.scale.y = planeScale;
scene.add(planeMesh);

// Place and add the markers to the scene using stratified sampling
var startPos = new THREE.Vector3(planeMesh.position.x, planeMesh.position.y, planeMesh.position.z);
startPos.x -= planeScale / 2 + 0.5;
startPos.z -= planeScale / 2 + 0.5;

var endPos = new THREE.Vector3(planeMesh.position.x, planeMesh.position.y, planeMesh.position.z);
endPos.x += planeScale / 2;
endPos.z += planeScale / 2;

var maxDist = endPos.length();

for(var i = startPos.x; i < endPos.x; i += 0.75) {
for(var j = startPos.z; j < endPos.z; j += 0.75) {
//var markerColor = lerpColor(new THREE.Color(0xCCCCFF), new THREE.Color(0x00FF00), (new THREE.Vector3(i, planeMesh.position.y, j)).length() / maxDist);
var pos = new THREE.Vector3(i + Math.random(), planeMesh.position.y, j + Math.random());
var currentMarker = new Marker(pos, 0xff0000);

var xIndex = Math.ceil(pos.x) + planeScale / 2;
var yIndex = Math.ceil(pos.z) + planeScale / 2;

grid[xIndex][yIndex].push(currentMarker);

sceneMarkers.push(currentMarker);
scene.add(currentMarker.mesh);
}
}

sceneLoader.framework = framework;
sceneLoader.startPos = startPos;
sceneLoader.endPos = endPos;

sceneLoader.loadScene2(framework, startPos, endPos);

// set camera position
camera.position.set(10, 24, 36);
camera.lookAt(new THREE.Vector3(0,0,0));

gui.add(camera, 'fov', 0, 180).onChange(function(newVal) {
camera.updateProjectionMatrix();
});

gui.add(sceneLoader, 'loadScene1');
gui.add(sceneLoader, 'loadScene2');

}

sceneLoader.loadScene1 = function() {
clearScene(this.framework);

// Add the agents to the scene (one in a circular fashion and the other along the edges of the plane) (need two scenarios)
for(var i = this.startPos.x; i < this.endPos.x; i += 5) {
var startPos1 = new THREE.Vector3(i, planeMesh.position.y + AGENT_HEIGHT / 2, this.endPos.z);
var goalPos1 = new THREE.Vector3(i, planeMesh.position.y, -this.endPos.z); // leave the goal position in the plane (to be aligned with the markers)
var currentAgent1 = new Agent(startPos1, goalPos1, new THREE.Color( Math.random(), Math.random(), Math.random() ));

var startPos2 = new THREE.Vector3(i, planeMesh.position.y + AGENT_HEIGHT / 2, -this.endPos.z);
var goalPos2 = new THREE.Vector3(i, planeMesh.position.y, this.endPos.z); // leave the goal position in the plane (to be aligned with the markers)
var currentAgent2 = new Agent(startPos2, goalPos2, new THREE.Color( Math.random(), Math.random(), Math.random() ));

sceneAgents.push(currentAgent1);
sceneAgents.push(currentAgent2);
this.framework.scene.add(currentAgent1.mesh);
this.framework.scene.add(currentAgent2.mesh);
}
}

sceneLoader.loadScene2 = function() {
clearScene(this.framework);

var radius = 10;
var incr = Math.PI / 6;
for(var theta = 0; theta < Math.PI - incr; theta += incr) {
var posX = Math.cos(theta) * radius;
var posY = Math.sin(theta) * radius;

var pos1 = new THREE.Vector3(posX, planeMesh.position.y + AGENT_HEIGHT / 2, posY);
var goalPos1 = new THREE.Vector3(-posX, planeMesh.position.y, -posY);
var currentAgent1 = new Agent(pos1, goalPos1, new THREE.Color( Math.random(), Math.random(), Math.random() ));

var pos2 = new THREE.Vector3(-posX, planeMesh.position.y + AGENT_HEIGHT / 2, -posY);
var goalPos2 = new THREE.Vector3(posX, planeMesh.position.y, posY);
var currentAgent2 = new Agent(pos2, goalPos2, new THREE.Color( Math.random(), Math.random(), Math.random() ));

sceneAgents.push(currentAgent1);
sceneAgents.push(currentAgent2);
this.framework.scene.add(currentAgent1.mesh);
this.framework.scene.add(currentAgent2.mesh);
}
}

function lerpColor(colorA, colorB, w) {
var r = (1 - w) * colorA.r + w * colorB.r;
var g = (1 - w) * colorA.g + w * colorB.g;
var b = (1 - w) * colorA.b + w * colorB.b;
return new THREE.Color(r, g, b);
}

// clears the scene by removing all geometries added by turtle.js
function clearScene(framework) {
sceneAgents = new Array();
for(var i = framework.scene.children.length - 1; i > sceneMarkers.length + 1; i--) {
var obj = framework.scene.children[i];
framework.scene.remove(obj);
}
}

// called on frame updates
function onUpdate(framework) {
var timestep = clock.getDelta();

// Clear the owner of every marker
for(var i = 0; i < sceneMarkers.length; i++) {
sceneMarkers[i].clearOwner();
}

// For each agent, compute the next direction the agent should move in
for(var i = 0; i < sceneAgents.length; i++) {
var currentAgent = sceneAgents[i];
currentAgent.setNearbyMarkers(grid, sceneAgents);
currentAgent.computeNewDirection();

var velocity = currentAgent.direction.clone();
velocity.multiplyScalar(timestep);
velocity.multiplyScalar(5);

currentAgent.position.add(velocity);
currentAgent.planePosition.add(velocity);
currentAgent.mesh.position.add(velocity);
currentAgent.goalDirection = currentAgent.goalDirection.subVectors(currentAgent.goalPos, currentAgent.position);
currentAgent.goalDirection.normalize();
currentAgent.clearMarkers();
}
}

// when the scene is done initializing, it will call onLoad, then on frame updates, call onUpdate
Framework.init(onLoad, onUpdate);
Loading