Skip to content

Commit

Permalink
Merge branch 'release/alpha1'
Browse files Browse the repository at this point in the history
  • Loading branch information
marcusrettig committed Apr 26, 2017
2 parents a7ddc89 + 05ea210 commit dabf7c5
Show file tree
Hide file tree
Showing 23 changed files with 1,983 additions and 64 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
# data-universe

3D visualization of job market data with VR controls.

## Setup

`npm install` will install dependencies.

`npm start` will run the project using webpack-development-server.

`node controls-server.js` runs the Xbox control server, the webpack instance must be running before this starts.

Connect your phone with Safari or Chrome to port 8080 (if your firewall does not allow this setup a Wifi hotspot with another phone).
1,203 changes: 1,202 additions & 1 deletion assets/100.json

Large diffs are not rendered by default.

79 changes: 79 additions & 0 deletions controls-server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
const XboxController = require('xbox-controller');
const WebSocket = require('ws');

const xbox = new XboxController();
const server = new WebSocket.Server({ port: 8081 });

server.on('connection', () => {
/* eslint no-console: "allow" */
console.log('Client connected.');
});

server.on('connection', (socket) => {
socket.on('message', (packet) => {
const message = JSON.parse(packet);
switch (message.type) {
case 'selected':
console.log(message);
broadcast(message);
break;
}
});
});

xbox.on('left:move', (position) => {
const message = {
type: 'left:move',
x: position.x,
y: position.y,
};
broadcast(message);
});

xbox.on('right:move', (position) => {
const message = {
type: 'right:move',
x: position.x,
y: position.y,
};
broadcast(message);
});

xbox.on('lefttrigger', (position) => {
const message = {
type: 'lefttrigger',
x: position,
};
broadcast(message);
});

xbox.on('righttrigger', (position) => {
const message = {
type: 'righttrigger',
x: position,
};
broadcast(message);
});

xbox.on('a:release', (key) => {
const message = {
type: 'a:release',
};
broadcast(message);
});

xbox.on('start:release', (key) => {
const message = {
type: 'start:release',
};
broadcast(message);
});

function broadcast(message) {
const packet = JSON.stringify(message);
server.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(packet);
}
});
}
21 changes: 21 additions & 0 deletions loaders/device-orientation-controls-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module.exports = function threeLoader(content) {
if (this.cacheable) this.cacheable();
return `
import { Euler } from 'three/math/Euler';
import { Vector3 } from 'three/math/Vector3';
import { Quaternion } from 'three/math/Quaternion';
import { _Math } from 'three/math/Math';
const THREE = {
Euler,
Vector3,
Quaternion,
Math: _Math
};
${content}
const DeviceOrientationControls = THREE.DeviceOrientationControls;
export { DeviceOrientationControls };
`;
};
15 changes: 15 additions & 0 deletions loaders/stereo-effect-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module.exports = function threeLoader(content) {
if (this.cacheable) this.cacheable();
return `
import { StereoCamera } from 'three/cameras/StereoCamera';
const THREE = {
StereoCamera,
};
${content}
const StereoEffect = THREE.StereoEffect;
export { StereoEffect };
`;
};
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@
},
"scripts": {
"build": "node_modules/.bin/webpack",
"start": "node_modules/.bin/webpack-dev-server --open"
"start": "node_modules/.bin/webpack-dev-server --host 0.0.0.0 --port 8080 --open"
},
"homepage": "https://github.com/marcusrettig/data-universe#readme",
"devDependencies": {
"copy-webpack-plugin": "^4.0.1",
"eslint": "^3.16.0",
"eslint-config-airbnb-base": "^11.1.0",
"eslint-import-resolver-webpack": "^0.8.1",
Expand All @@ -28,6 +29,8 @@
"webpack-glsl-loader": "^1.0.1"
},
"dependencies": {
"three": "^0.84.0"
"three": "^0.84.0",
"ws": "^2.2.3",
"xbox-controller": "^0.7.0"
}
}
63 changes: 63 additions & 0 deletions src/XboxRemoteControls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Vector2 } from 'three/math/Vector2';
import { Vector3 } from 'three/math/Vector3';
import { Quaternion } from 'three/math/Quaternion';

const joystickMagnitude = 32767.0;
const triggerMagnitude = 255.0;

export function XboxRemoteControls(object) {
this.object = object;

this.movementSpeed = 10.0;
this.rotationSpeed = 0.5;

this.movement = new Vector3(0, 0, 0);
this.rotation = new Vector2(0, 0);

this.rotationQuaternion = new Quaternion();

this.controls = {
movement: { x: 0, y: 0 },
rotation: { x: 0, y: 0 },
elevate: { up: 0, down: 0 },
};

this.onMessage = (message) => {
switch (message.type) {
case 'left:move':
this.controls.movement.x = message.x;
this.controls.movement.y = message.y;
break;
case 'right:move':
this.controls.rotation.x = message.x;
this.controls.rotation.y = message.y;
break;
case 'righttrigger':
this.controls.elevate.up = message.x;
break;
case 'lefttrigger':
this.controls.elevate.down = message.x;
break;
default:
break;
}
};

this.update = (delta) => {
const movementMultiplier = (delta * this.movementSpeed);
this.movement.x = this.controls.movement.x / joystickMagnitude;
this.movement.y = (this.controls.elevate.up - this.controls.elevate.down) / triggerMagnitude;
this.movement.z = this.controls.movement.y / joystickMagnitude;
this.movement.multiplyScalar(movementMultiplier);

this.object.translateX(this.movement.x);
this.object.translateY(this.movement.y);
this.object.translateZ(this.movement.z);

// const rotationMultiplier = (delta * this.rotationSpeed) / joystickMagnitude;
// this.rotation.set(this.controls.rotation.x, this.controls.rotation.y);
// this.rotation.multiplyScalar(rotationMultiplier);
// this.rotationQuaternion.set(-this.rotation.y, -this.rotation.x, 0, 1);
// this.object.quaternion.multiply(this.rotationQuaternion);
};
}
133 changes: 133 additions & 0 deletions src/billboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { Object3D } from 'three/core/Object3D';
import { Texture } from 'three/textures/Texture';
import { SpriteMaterial } from 'three/materials/SpriteMaterial';
import { Sprite } from 'three/objects/Sprite';

export function createBillboard(text, subtext, height) {
const billboard = new Object3D();
const sprite = createSprite(text, subtext);
const y = height + 0.5;
sprite.position.set(0, y, 0);
billboard.add(sprite);
billboard.isBillboard = true;
return billboard;
}

export function updateBillboard(billboard, camera) {
billboard.quaternion.copy(camera.quaternion);
}

function createSprite(text, subtext) {
const texture = createTexture(text, subtext);
texture.needsUpdate = true;

const material = new SpriteMaterial({ map: texture, fog: true });
const sprite = new Sprite(material);
return sprite;
}

function createTexture(text, subtext) {
// These constants can be tweaked to change the styling.
const fontFace = 'Arial';
const headerFontSize = 24;
const infoFontSize = 20;
const backgroundColor = 'rgba(50,75,75,0.5)';
const headerTextColor = 'rgba(255,255,255,0.75)';
const infoTextColor = 'rgba(255,255,255,0.60)';
const padding = 10;
const spacing = 18;
const boxWidth = 256; // Must be a power of 2.
// Removes some height from the bottom of the box to make the text look more centered.
const heightReduction = 0.1 * headerFontSize;

//Calculate the height of a line
const headerLineHeight = 1.4 * headerFontSize;
const infoLineHeight = 1.4 * infoFontSize;

// Setup Canvas
const canvas = document.createElement('canvas');
canvas.width = boxWidth;
canvas.height = boxWidth;
const context = canvas.getContext('2d');

// Calculate properties and wrap text
const contentWidth = boxWidth - (2 * padding);
context.font = `Normal ${headerFontSize}px ${fontFace}`;
const headerLines = wrapText(context, text, contentWidth);
context.font = `Normal ${infoFontSize}px ${fontFace}`;
const infoLines = wrapText(context, subtext, contentWidth);

// Calculate the heights of each segment
const headerTotalLineHeight = (headerLines.length * headerLineHeight);
const infoHeight = (infoLines.length * infoLineHeight);
const totalLineHeight = headerTotalLineHeight + infoHeight;

// Calculate the start of info text segment
const infoTextStart = spacing + (headerTotalLineHeight + padding);

const contentHeight = (totalLineHeight + spacing) - heightReduction;
const boxHeight = contentHeight + (2 * padding);
const yOffset = canvas.height - boxHeight;

// Render box.
context.fillStyle = backgroundColor;
context.fillRect(0, yOffset, boxWidth, boxHeight);

// Render header text.
context.textBaseline = 'top';
context.font = `Normal ${headerFontSize}px ${fontFace}`;
context.fillStyle = headerTextColor;

headerLines.forEach((line, i) => {
const lineWidth = context.measureText(line).width;
const lineX = padding + ((contentWidth - lineWidth) / 2);
const lineY = padding + (i * headerLineHeight) + yOffset;
context.fillText(line, lineX, lineY, contentWidth);
});

// Draws line divider
context.beginPath();
context.moveTo(0, ((headerTotalLineHeight + padding) + yOffset) + (spacing / 2));
context.lineTo(boxWidth, ((headerTotalLineHeight + padding) + yOffset) + (spacing / 2));
context.strokeStyle = 'rgba(50,75,75,0.8)';
context.stroke();

// Render info text
context.font = `Normal ${infoFontSize}px ${fontFace}`;
context.fillStyle = infoTextColor;

infoLines.forEach((line, i) => {
const lineWidth = context.measureText(line).width;
const lineX = padding + ((contentWidth - lineWidth) / 2);
const lineY = infoTextStart + (i * infoLineHeight) + yOffset;
context.fillText(line, lineX, lineY, contentWidth);
});

return new Texture(canvas);
}

function wrapText(context, text, maxWidth) {
const words = text.split(' ');
if (words.length <= 1) {
return [text];
}

const lines = [];
let line = words[0];

for (let i = 1; i < words.length; i += 1) {
const word = words[i];
const testLine = `${line} ${word}`;
const testWidth = context.measureText(testLine).width;
if (testWidth >= maxWidth) {
lines.push(line);
line = word;
}
else {
line = testLine;
}
}
lines.push(line);

return lines;
}
6 changes: 2 additions & 4 deletions src/camera.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { PerspectiveCamera } from 'three/cameras/PerspectiveCamera';

function createCamera() {
export function createCamera() {
const camera = new PerspectiveCamera(25, window.innerWidth / window.innerHeight, 0.1, 1e7);
camera.position.z = 5;
return camera;
}

function resizeCamera(camera) {
export function resizeCamera(camera) {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
}

export { createCamera, resizeCamera };
12 changes: 9 additions & 3 deletions src/controls.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { FlyControls } from 'three_examples/controls/FlyControls';
import { DeviceOrientationControls } from 'three_examples/controls/DeviceOrientationControls';

function createControls(camera, container) {
export function createFlyControls(camera, container) {
const controls = new FlyControls(camera, container);

controls.movementSpeed = 2.0;
controls.movementSpeed = 10.0;
controls.rollSpeed = Math.PI / 24;
controls.autoForward = false;
controls.dragToLook = false;

return controls;
}

export { createControls };
export function createVRControls(camera, container) {
const controls = new DeviceOrientationControls(camera, container);
controls.connect();

return controls;
}

0 comments on commit dabf7c5

Please sign in to comment.