@@ -4,12 +4,19 @@ | |||||
<head> | <head> | ||||
<meta charset="utf-8"> | <meta charset="utf-8"> | ||||
<title>Title</title> | <title>Title</title> | ||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=1" /> | |||||
<link rel="stylesheet" href="main.css"> | <link rel="stylesheet" href="main.css"> | ||||
</head> | </head> | ||||
<body> | <body> | ||||
<script src="main.js"></script> | |||||
Soft console: | |||||
<pre id="soft_console"></pre> | |||||
<script type="text/javascript" src="three.js"></script> | |||||
<script type="text/javascript" src="libs.js"></script> | |||||
<script type="text/javascript" src="graph.js"></script> | |||||
<script type="text/javascript" src="main.js"></script> | |||||
<img id="ball" src="data:image/png;base64,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"/ | |||||
style=display:none;> | |||||
<div id=infoholder class=hidden> | |||||
</div> | |||||
</body> | </body> | ||||
</html> | </html> |
@@ -1,3 +1,19 @@ | |||||
body{ | |||||
font-family: sans-serif; | |||||
html, body { margin: 0; padding: 0; overflow: hidden; font-size: 10pt; font-family: "courier new"; } | |||||
#infoholder { | |||||
background: black; color:white; padding: 5px; margin:0px; position: absolute; top:5px; left:5px; font-family:"courier new"; text-align: center; font-size:10pt; width:200px; height:15px; | |||||
border-radius:3px; | |||||
} | |||||
.visible { | |||||
visibility: visible; | |||||
opacity: 1; | |||||
transform: scale(1); | |||||
transition: opacity .08s linear, transform .08s linear; | |||||
} | |||||
.hidden { | |||||
visibility: hidden; | |||||
opacity: 0; | |||||
transform: scale(.5); | |||||
transition: visibility .08s, opacity .08s linear, transform .08s linear; | |||||
} | } |
@@ -1,23 +1,172 @@ | |||||
var body; | |||||
var state; | |||||
// IE9 | |||||
if(typeof console === "undefined") { var console = { log: function (logMsg) { } }; } | |||||
function poll() { | |||||
var xhr = new XMLHttpRequest(); | |||||
var controls, renderer, raycaster, scene, info, nodeGeometry, selection; | |||||
var mouse = {"x":0, "y":0}; | |||||
var materials={}; | |||||
var curveProperties = {splineDensity: 30, curvature: 10}; | |||||
var camera; | |||||
xhr.onload=function() { | |||||
state = JSON.parse(xhr.response); | |||||
soft_console.innerHTML = "\n" + xhr.responseText; | |||||
}; | |||||
// Run on startup | |||||
window.onload=init; | |||||
xhr.onerror = function(e){ | |||||
soft_console.innerHTML = "\n" + "Lost connection to server"; | |||||
}; | |||||
// Add a curved edge between two points | |||||
function makeEdge(e) { | |||||
// Make the geometry of the curve | |||||
var a = new THREE.Vector3(e.start[0], e.start[1], e.start[2]); | |||||
var b = new THREE.Vector3(e.end[0], e.end[1], e.end[2]); | |||||
var length = new THREE.Vector3().subVectors(a, b).length(); | |||||
var bend = new THREE.Vector3(length/curveProperties.curvature, length/curveProperties.curvature, 0); | |||||
var mid = new THREE.Vector3().add(a).add(b).multiplyScalar(0.5).add(bend); | |||||
var spline = new THREE.CatmullRomCurve3([a, mid, b]); | |||||
var geometry = new THREE.Geometry(); | |||||
var splinePoints = spline.getPoints(curveProperties.splineDensity); | |||||
Array.prototype.push.apply(geometry.vertices, splinePoints); | |||||
xhr.open("GET", "/state", true); | |||||
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); | |||||
xhr.send(); | |||||
// Make the actual Object3d thing | |||||
var line = new THREE.Line(geometry, materials.edge); | |||||
return line; | |||||
} | } | ||||
window.onload = function () { | |||||
setInterval(poll, 1000); | |||||
// Clear the whole scene | |||||
function makeScene(){ | |||||
// Scene, controls, camera and so on | |||||
var myScene = new THREE.Scene(); | |||||
// Materials | |||||
var lineStyle = {color: "gray", transparent: false, linewidth:1}; | |||||
materials.edge = new THREE.LineBasicMaterial(lineStyle); | |||||
var pointStyle = { size: 0.2, map: materials.sprite, alphaTest: 0.5, | |||||
transparent: true, vertexColors:THREE.VertexColors}; | |||||
materials.point = new THREE.PointsMaterial(pointStyle); | |||||
// Build all the edges | |||||
//var edgeGroup = new THREE.Object3D(); | |||||
// Build all the nodes | |||||
nodeGeometry = new THREE.Geometry(); | |||||
nodeGeometry.labels = []; | |||||
nodeGeometry.colors = []; | |||||
for (var i=0; i < 10; ++i) { | |||||
for (var j=0; j < 10; ++j) { | |||||
var vertex = new THREE.Vector3(i-5, j-5, 0); | |||||
nodeGeometry.vertices.push(vertex); | |||||
nodeGeometry.colors.push(new THREE.Color(0.5, 0.5, 0.5)); | |||||
nodeGeometry.labels.push(i + " " + j + " "); | |||||
} | |||||
} | |||||
var particles = new THREE.Points(nodeGeometry, materials.point); | |||||
var grid = makeGrid(10, 10, "lightgray"); | |||||
myScene.add(grid); | |||||
// Add the above stuff into the scene and return | |||||
//myScene.add(edgeGroup); | |||||
myScene.add(particles); | |||||
return myScene; | |||||
} | |||||
// Gets a reference to the node nearest to the mouse cursor | |||||
function nearestNode() { | |||||
raycaster.setFromCamera(mouse, camera); | |||||
for (var i=0; i < nodeGeometry.vertices.length; ++i) { | |||||
if (raycaster.ray.distanceSqToPoint(nodeGeometry.vertices[i]) < 0.01){ return i;} | |||||
} | |||||
return undefined; | |||||
} | |||||
// Find out: what is the mouse pointing at? | |||||
function checkIntersections() { | |||||
var new_selection = nearestNode(); | |||||
if (new_selection != selection){ | |||||
selection = new_selection; | |||||
info.className = selection ? "visible" : "hidden"; | |||||
info.innerHTML = selection ? nodeGeometry.labels[new_selection] : info.innerHTML; | |||||
render(); | |||||
} | |||||
} | |||||
// Make a grid | |||||
function makeGrid(side, n, color){ | |||||
var markers = new THREE.Object3D(); | |||||
var gridStyle = { color: color, transparent: true, linewidth: 1, opacity:0.5}; | |||||
var material = new THREE.LineBasicMaterial(gridStyle); | |||||
for (var i=-n/2; i < n/2; ++i) { | |||||
var geometry = new THREE.Geometry(); | |||||
geometry.vertices.push(new THREE.Vector3(side*i/n, -side/2, 0)); | |||||
geometry.vertices.push(new THREE.Vector3(side*i/n, side/2, 0)); | |||||
var line = new THREE.Line(geometry, material); | |||||
var line90 = line.clone(); | |||||
line90.rotation.z=Math.PI/2; | |||||
markers.add(line); | |||||
markers.add(line90); | |||||
} | |||||
return markers; | |||||
} | |||||
// Handle mouse movement | |||||
function onMouseMove(event) { | |||||
mouse.wasClick = false; | |||||
mouse.absx = event.clientX; | |||||
mouse.absy = event.clientY; | |||||
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1; | |||||
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1; | |||||
w = 200; | |||||
h = 15; | |||||
info.style.top = mouse.absy-h-40+"px"; | |||||
info.style.left = mouse.absx-w/2+"px"; | |||||
checkIntersections(); | |||||
} | |||||
// Render the current frame to the screen | |||||
function render() { | |||||
renderer.render(scene, camera); | |||||
} | |||||
// This is the main control loop | |||||
function loopForever() { | |||||
controls.update(); | |||||
requestAnimationFrame(loopForever); | |||||
} | |||||
// This just organises kickoff | |||||
function startMainLoop() { | |||||
scene = makeScene(); | |||||
document.addEventListener("mousemove", onMouseMove, false ); | |||||
controls.addEventListener("change", render); | |||||
loopForever(); | |||||
} | } | ||||
// Called on startup | |||||
function init() { | |||||
// Measure things, get references | |||||
var width = window.innerWidth; | |||||
var height = window.innerHeight; | |||||
info = document.getElementById("infoholder"); | |||||
materials.sprite = new THREE.Texture(document.getElementById("ball")); | |||||
materials.sprite.needsUpdate = true; | |||||
// Renderer | |||||
renderer = new THREE.WebGLRenderer( { antialias: true }); | |||||
renderer.setSize(width, height); | |||||
renderer.setClearColor(0xffffff, 1); | |||||
document.querySelector("body").appendChild(renderer.domElement); | |||||
// Camera, controls, raycaster | |||||
camera = new THREE.PerspectiveCamera(45, width/height, 0.3, 100); | |||||
controls = new THREE.OrbitControls(camera); | |||||
raycaster = new THREE.Raycaster(); | |||||
// Center the camera | |||||
controls.center.set(0, 0, 0); | |||||
controls.rotateSpeed = 0.2; | |||||
camera.position.set(0, 0, 20); | |||||
// Run | |||||
startMainLoop(); | |||||
} | |||||
@@ -0,0 +1,23 @@ | |||||
var body; | |||||
var state; | |||||
function poll() { | |||||
var xhr = new XMLHttpRequest(); | |||||
xhr.onload=function() { | |||||
state = JSON.parse(xhr.response); | |||||
soft_console.innerHTML = "\n" + xhr.responseText; | |||||
}; | |||||
xhr.onerror = function(e){ | |||||
soft_console.innerHTML = "\n" + "Lost connection to server"; | |||||
}; | |||||
xhr.open("GET", "/state", true); | |||||
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); | |||||
xhr.send(); | |||||
} | |||||
window.onload = function () { | |||||
setInterval(poll, 1000); | |||||
} |