@@ -0,0 +1,32 @@ | |||||
from flask import Flask, request, redirect, url_for, make_response, render_template, Markup, send_from_directory, send_file | |||||
from flask_redis import FlaskRedis | |||||
import json, abp | |||||
app = Flask(__name__) | |||||
redis = FlaskRedis(app) | |||||
@app.route("/") | |||||
def index(): | |||||
return render_template("index.html") | |||||
@app.route("/graph", methods=["GET", "POST"]) | |||||
def graph(page): | |||||
if request.method == 'POST': | |||||
# Convert the data to a graph state | |||||
g = abp.GraphState() | |||||
g.from_json(json.loads(data)) | |||||
# Convert it back to JSON | |||||
data = json.dumps(g.to_json(stringify=True)) | |||||
# Insert into the database | |||||
redis.execute_command("SET", "graph", data) | |||||
# Return success | |||||
return "OK" | |||||
else: | |||||
# Get from the database | |||||
return redis.get("graph") | |||||
@@ -0,0 +1,91 @@ | |||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||||
<!-- Created with Inkscape (http://www.inkscape.org/) --> | |||||
<svg | |||||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||||
xmlns:cc="http://creativecommons.org/ns#" | |||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||||
xmlns:svg="http://www.w3.org/2000/svg" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
xmlns:xlink="http://www.w3.org/1999/xlink" | |||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||||
width="64" | |||||
height="64" | |||||
id="svg2" | |||||
version="1.1" | |||||
inkscape:version="0.48.4 r9939" | |||||
sodipodi:docname="New document 1"> | |||||
<defs | |||||
id="defs4"> | |||||
<linearGradient | |||||
id="linearGradient3763"> | |||||
<stop | |||||
style="stop-color:#ffffff;stop-opacity:1;" | |||||
offset="0" | |||||
id="stop3765" /> | |||||
<stop | |||||
style="stop-color:#000000;stop-opacity:1;" | |||||
offset="1" | |||||
id="stop3767" /> | |||||
</linearGradient> | |||||
<radialGradient | |||||
inkscape:collect="always" | |||||
xlink:href="#linearGradient3763" | |||||
id="radialGradient3771" | |||||
cx="38.780914" | |||||
cy="23.33404" | |||||
fx="38.780914" | |||||
fy="23.33404" | |||||
r="32.661938" | |||||
gradientUnits="userSpaceOnUse" | |||||
gradientTransform="matrix(1.1215552,0.13870084,-0.14202472,1.1484324,1.1779947,-9.189628)" /> | |||||
</defs> | |||||
<sodipodi:namedview | |||||
id="base" | |||||
pagecolor="#ffffff" | |||||
bordercolor="#666666" | |||||
borderopacity="1.0" | |||||
inkscape:pageopacity="0.0" | |||||
inkscape:pageshadow="2" | |||||
inkscape:zoom="3.959798" | |||||
inkscape:cx="-12.465613" | |||||
inkscape:cy="8.3918647" | |||||
inkscape:document-units="px" | |||||
inkscape:current-layer="layer1" | |||||
showgrid="false" | |||||
inkscape:snap-page="false" | |||||
inkscape:window-width="1366" | |||||
inkscape:window-height="721" | |||||
inkscape:window-x="0" | |||||
inkscape:window-y="0" | |||||
inkscape:window-maximized="1" /> | |||||
<metadata | |||||
id="metadata7"> | |||||
<rdf:RDF> | |||||
<cc:Work | |||||
rdf:about=""> | |||||
<dc:format>image/svg+xml</dc:format> | |||||
<dc:type | |||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||||
<dc:title></dc:title> | |||||
</cc:Work> | |||||
</rdf:RDF> | |||||
</metadata> | |||||
<g | |||||
inkscape:label="Layer 1" | |||||
inkscape:groupmode="layer" | |||||
id="layer1" | |||||
transform="translate(0,-988.36218)"> | |||||
<path | |||||
sodipodi:type="arc" | |||||
style="fill:url(#radialGradient3771);fill-opacity:1;stroke:#000000;stroke-width:1.32387710000000003;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" | |||||
id="path2993" | |||||
sodipodi:cx="32" | |||||
sodipodi:cy="32" | |||||
sodipodi:rx="32" | |||||
sodipodi:ry="32" | |||||
d="M 64,32 A 32,32 0 1 1 0,32 32,32 0 1 1 64,32 z" | |||||
transform="matrix(0.94419643,0,0,0.94419643,1.7857143,990.14789)" /> | |||||
</g> | |||||
</svg> |
@@ -0,0 +1,91 @@ | |||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||||
<!-- Created with Inkscape (http://www.inkscape.org/) --> | |||||
<svg | |||||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||||
xmlns:cc="http://creativecommons.org/ns#" | |||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||||
xmlns:svg="http://www.w3.org/2000/svg" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||||
width="64" | |||||
height="64" | |||||
id="svg2" | |||||
version="1.1" | |||||
inkscape:version="0.48.4 r9939" | |||||
sodipodi:docname="tip.svg"> | |||||
<defs | |||||
id="defs4"> | |||||
<linearGradient | |||||
id="linearGradient3763"> | |||||
<stop | |||||
style="stop-color:#ffffff;stop-opacity:1;" | |||||
offset="0" | |||||
id="stop3765" /> | |||||
<stop | |||||
style="stop-color:#000000;stop-opacity:1;" | |||||
offset="1" | |||||
id="stop3767" /> | |||||
</linearGradient> | |||||
</defs> | |||||
<sodipodi:namedview | |||||
id="base" | |||||
pagecolor="#ffffff" | |||||
bordercolor="#666666" | |||||
borderopacity="1.0" | |||||
inkscape:pageopacity="0.0" | |||||
inkscape:pageshadow="2" | |||||
inkscape:zoom="3.959798" | |||||
inkscape:cx="5.4453989" | |||||
inkscape:cy="4.9448826" | |||||
inkscape:document-units="px" | |||||
inkscape:current-layer="layer1" | |||||
showgrid="false" | |||||
inkscape:snap-page="true" | |||||
inkscape:window-width="1366" | |||||
inkscape:window-height="721" | |||||
inkscape:window-x="0" | |||||
inkscape:window-y="0" | |||||
inkscape:window-maximized="1" | |||||
inkscape:snap-center="true" | |||||
inkscape:object-nodes="true" /> | |||||
<metadata | |||||
id="metadata7"> | |||||
<rdf:RDF> | |||||
<cc:Work | |||||
rdf:about=""> | |||||
<dc:format>image/svg+xml</dc:format> | |||||
<dc:type | |||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||||
<dc:title></dc:title> | |||||
</cc:Work> | |||||
</rdf:RDF> | |||||
</metadata> | |||||
<g | |||||
inkscape:label="Layer 1" | |||||
inkscape:groupmode="layer" | |||||
id="layer1" | |||||
transform="translate(0,-988.36218)"> | |||||
<path | |||||
sodipodi:type="arc" | |||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.32387710000000003;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;opacity:0.29302326" | |||||
id="path2993" | |||||
sodipodi:cx="32" | |||||
sodipodi:cy="32" | |||||
sodipodi:rx="32" | |||||
sodipodi:ry="32" | |||||
d="M 64,32 A 32,32 0 1 1 0,32 32,32 0 1 1 64,32 z" | |||||
transform="matrix(0.94419643,0,0,0.94419643,1.7857143,990.14789)" /> | |||||
<path | |||||
style="color:#000000;fill:none;stroke:#000000;stroke-width:9.65;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" | |||||
d="m 32,996.21932 0,48.28568" | |||||
id="path3811" | |||||
inkscape:connector-curvature="0" /> | |||||
<path | |||||
style="color:#000000;fill:none;stroke:#000000;stroke-width:9.65;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" | |||||
d="m 7.8571427,1020.3622 48.2857143,0" | |||||
id="path3813" | |||||
inkscape:connector-curvature="0" /> | |||||
</g> | |||||
</svg> |
@@ -0,0 +1,92 @@ | |||||
html, body { margin: 0; padding: 0; overflow: hidden; font-size: 10pt; font-family: monospace; } | |||||
#node_info { | |||||
background: rgba(0, 0, 0, .8); | |||||
color:white; | |||||
padding: 5px; | |||||
margin:0px; | |||||
position: absolute; | |||||
top:5px; | |||||
left:5px; | |||||
font-family: monospace; | |||||
text-align: center; | |||||
font-size:9pt; | |||||
/*height:15px;*/ | |||||
border-radius:3px; | |||||
pointer-events: none; | |||||
} | |||||
#server_info { | |||||
background-color: black; | |||||
color:white; | |||||
padding: 10px; | |||||
font-family: monospace; | |||||
position: absolute; | |||||
top: 10px; | |||||
right: 10px; | |||||
font-size: 9pt; | |||||
} | |||||
#node_name { | |||||
font-size: 12pt; | |||||
} | |||||
#node_data { | |||||
background-color: black; | |||||
color:white; | |||||
padding: 10px; | |||||
font-family: monospace; | |||||
position: absolute; | |||||
top: 10px; | |||||
left: 10px; | |||||
font-size: 9pt; | |||||
} | |||||
#version { | |||||
color:black; | |||||
padding: 10px; | |||||
font-family: monospace; | |||||
position: absolute; | |||||
bottom: 10px; | |||||
left: 10px; | |||||
font-size: 9pt; | |||||
} | |||||
ul { | |||||
list-style-type: none; | |||||
padding: 0px; | |||||
margin: 0px; | |||||
} | |||||
li{ | |||||
padding:3px; | |||||
padding-left: 0em; | |||||
} | |||||
.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; | |||||
} | |||||
a { | |||||
color: yellow; | |||||
} | |||||
h3 { | |||||
padding-top: 0px; | |||||
padding-bottom: 0px; | |||||
margin-top: 2px; | |||||
margin-bottom: 2px; | |||||
} |
@@ -0,0 +1 @@ | |||||
qcengine |
@@ -0,0 +1,73 @@ | |||||
var abj = {}; | |||||
abj.node = {}; | |||||
abj.adj = {}; | |||||
abj.add_node = function(node, meta) { | |||||
if (meta === undefined){meta = {};} | |||||
abj.adj[node] = {}; | |||||
abj.node[node] = {}; | |||||
abj.node[node].vop = tables.clifford.hadamard; | |||||
Object.assign(abj.node[node], meta); | |||||
}; | |||||
abj.add_nodes = function(nodes) { | |||||
nodes.forEach(add_node); | |||||
}; | |||||
abj.del_node = function(node) { | |||||
for (var i in abj.adj[node]) { | |||||
abj.del_edge(node, i); | |||||
} | |||||
delete abj.node[node]; | |||||
}; | |||||
abj.add_edge = function(a, b) { | |||||
abj.adj[a][b] = {}; | |||||
abj.adj[b][a] = {}; | |||||
}; | |||||
abj.add_edges = function(edges) { | |||||
edges.forEach(function(e) { | |||||
add_edge(e[0], e[1]); | |||||
}); | |||||
}; | |||||
abj.del_edge = function(a, b) { | |||||
delete abj.adj[a][b]; | |||||
delete abj.adj[b][a]; | |||||
}; | |||||
abj.has_edge = function(a, b) { | |||||
return Object.prototype.hasOwnProperty.call(abj.adj[a], b); | |||||
}; | |||||
abj.is_sole_member = function(group, node) { | |||||
// TODO: this is slow as heck | |||||
var keys = Object.keys(group); | |||||
return keys.length == 1 && keys[0] == node; | |||||
}; | |||||
abj.update = function(newState) { | |||||
abj.node = newState.node; | |||||
abj.adj = newState.adj; | |||||
}; | |||||
abj.order = function(){ | |||||
return Object.keys(abj.node).length; | |||||
}; | |||||
abj.edgelist = function() { | |||||
var seen = {}; | |||||
var output = []; | |||||
for (var i in abj.adj) { | |||||
for (var j in abj.adj[i]) { | |||||
if (!Object.prototype.hasOwnProperty.call(seen, j)) { | |||||
output.push([i, j]); | |||||
} | |||||
} | |||||
seen[i] = true; | |||||
} | |||||
return output; | |||||
}; | |||||
@@ -0,0 +1,190 @@ | |||||
var editor = {}; | |||||
var pi2 = Math.PI / 2; | |||||
editor.selection = undefined; | |||||
editor.mouseOver = undefined; | |||||
editor.orientations = [ | |||||
new THREE.Euler(pi2, 0, 0), | |||||
new THREE.Euler(0, 0, 0), | |||||
new THREE.Euler(pi2, 0, pi2), | |||||
]; | |||||
editor.onFreeMove = function() { | |||||
var found = editor.findNodeOnRay(mouse.ray); | |||||
if (editor.mouseOver !== found) { | |||||
editor.mouseOver = found; | |||||
if (found) { | |||||
var n = abj.node[found]; | |||||
var s = "Node " + found + "<br/> "; | |||||
for (var i in n) { | |||||
if (i!="position"){ | |||||
s += i + ":" + n[i] + " "; | |||||
} | |||||
} | |||||
s += ""; | |||||
gui.nodeMessage(s); | |||||
} else { | |||||
gui.hideNodeMessage(); | |||||
} | |||||
} | |||||
}; | |||||
editor.focus = function(node) { | |||||
gui.hideNodeMessage(); | |||||
editor.selection = node; | |||||
//gui.serverMessage("Selected node " + node + "."); | |||||
}; | |||||
editor.addQubitAtPoint = function(point) { | |||||
if (point === null) { | |||||
return; | |||||
} | |||||
point.round(); | |||||
var new_node = Math.floor(point.x) + "," + Math.floor(point.y) + "," + Math.floor(point.z); | |||||
if (Object.prototype.hasOwnProperty.call(abj.node, new_node)) { | |||||
gui.serverMessage("Node " + new_node +" already exists."); | |||||
return; | |||||
} | |||||
websocket.edit({action:"create", name:new_node, position: point}); | |||||
editor.focus(new_node); | |||||
gui.serverMessage("Created node " + new_node +"."); | |||||
}; | |||||
editor.onClick = function() { | |||||
var found = editor.findNodeOnRay(mouse.ray); | |||||
if (found) { | |||||
editor.focus(found); | |||||
var node=found; | |||||
editor.grid.position.copy(abj.node[node].position); | |||||
gui.controls.target.copy(abj.node[node].position); | |||||
node_name.innerHTML = "Node " + node; | |||||
node_data.className = "visible"; | |||||
node_vop.innerHTML = "VOP: " + abj.node[node].vop; | |||||
} else { | |||||
var intersection = mouse.ray.intersectPlane(editor.plane); | |||||
if (intersection !== null) { | |||||
editor.addQubitAtPoint(intersection); | |||||
} | |||||
} | |||||
}; | |||||
editor.onShiftClick = function() { | |||||
var found = editor.findNodeOnRay(mouse.ray); | |||||
if (found === undefined){ return; } | |||||
if (editor.selection === undefined){ return; } | |||||
if (found === editor.selection){ return; } | |||||
//abj.act_cz(found, editor.selection); | |||||
websocket.edit({action:"cz", start:found, end:editor.selection}); | |||||
gui.serverMessage("Acted CZ between " + found + " & " + editor.selection + "."); | |||||
editor.focus(found); | |||||
}; | |||||
editor.onCtrlClick = function() { | |||||
var found = editor.findNodeOnRay(mouse.ray); | |||||
if (found === undefined){ return; } | |||||
if (editor.selection === undefined){ return; } | |||||
editor.focus(found); | |||||
websocket.edit({action:"hadamard", node:found}); | |||||
gui.serverMessage("Acted H on node " + found + "."); | |||||
}; | |||||
editor.prepare = function() { | |||||
mouse.onFreeMove = editor.onFreeMove; | |||||
mouse.onClick = editor.onClick; | |||||
mouse.onShiftClick = editor.onShiftClick; | |||||
mouse.onCtrlClick = editor.onCtrlClick; | |||||
document.addEventListener("keydown", editor.onKey, false); | |||||
editor.makeGrid(); | |||||
}; | |||||
editor.onKey = function(evt) { | |||||
if (evt.keyCode === 32) { | |||||
editor.setOrientation((editor.orientation + 1) % 3); | |||||
} | |||||
if (evt.keyCode === 46 || evt.keyCode === 68) { | |||||
editor.deleteNode(); | |||||
} | |||||
}; | |||||
editor.setOrientation = function(orientation) { | |||||
editor.orientation = orientation; | |||||
var rotation = editor.orientations[orientation]; | |||||
var normal = new THREE.Vector3(0, 1, 0); | |||||
normal.applyEuler(rotation); | |||||
editor.grid.rotation.copy(rotation); | |||||
editor.plane = new THREE.Plane(); | |||||
editor.plane.setFromNormalAndCoplanarPoint(normal, editor.grid.position); | |||||
gui.render(); | |||||
}; | |||||
editor.makeGrid = function() { | |||||
editor.grid = new THREE.GridHelper(10, 1); | |||||
editor.grid.setColors(0xbbbbbb, 0xeeeeee); | |||||
editor.grid.renderOrder = 1000; | |||||
editor.setOrientation(0); | |||||
gui.scene.add(editor.grid); | |||||
gui.scene.children[0].renderOrder = -3000; | |||||
}; | |||||
editor.update = function() {}; | |||||
// Gets a reference to the node nearest to the mouse cursor | |||||
editor.findNodeOnRay = function(ray) { | |||||
for (var n in abj.node) { | |||||
if (ray.distanceSqToPoint(abj.node[n].position) < 0.012) { | |||||
return n; | |||||
} | |||||
} | |||||
return undefined; | |||||
}; | |||||
editor.deleteNode = function() { | |||||
if (editor.selection === undefined){ return; } | |||||
websocket.edit({action:"delete", node:editor.selection}); | |||||
gui.serverMessage("Deleted node " + editor.selection + "."); | |||||
editor.selection = undefined; | |||||
node_data.className = "hidden"; | |||||
}; | |||||
//TODO: loadsa space for DRY here | |||||
editor.hadamard = function() { | |||||
if (editor.selection === undefined){ return; } | |||||
websocket.edit({action:"hadamard", node:editor.selection}); | |||||
gui.serverMessage("Acted Hadamard on node " + editor.selection + "."); | |||||
}; | |||||
editor.phase = function() { | |||||
if (editor.selection === undefined){ return; } | |||||
websocket.edit({action:"phase", node:editor.selection}); | |||||
gui.serverMessage("Acted phase on node " + editor.selection + "."); | |||||
}; | |||||
editor.measureX = function() { | |||||
if (editor.selection === undefined){ return; } | |||||
websocket.edit({action:"measure", node:editor.selection, basis:"x"}); | |||||
gui.serverMessage("Measured node " + editor.selection + " in X."); | |||||
}; | |||||
editor.measureY = function() { | |||||
if (editor.selection === undefined){ return; } | |||||
websocket.edit({action:"measure", node:editor.selection, basis:"y"}); | |||||
gui.serverMessage("Measured node " + editor.selection + " in Y."); | |||||
}; | |||||
editor.measureZ = function() { | |||||
if (editor.selection === undefined){ return; } | |||||
websocket.edit({action:"measure", node:editor.selection, basis:"z"}); | |||||
gui.serverMessage("Measured node " + editor.selection + " in z."); | |||||
}; | |||||
editor.localComplementation = function() { | |||||
if (editor.selection === undefined){ return; } | |||||
websocket.edit({action:"localcomplementation", node:editor.selection}); | |||||
abj.local_complementation(editor.selection); | |||||
gui.serverMessage("Inverted neighbourhood of " + editor.selection + "."); | |||||
}; |
@@ -0,0 +1,76 @@ | |||||
var graph = {}; | |||||
graph.colors = ["red", "green"]; | |||||
graph.prepare = function() { | |||||
materials.prepare(); | |||||
websocket.connect(graph.update); | |||||
}; | |||||
graph.center = function() { | |||||
var middle = new THREE.Vector3(0, 0, 0); | |||||
for (var i in abj.node) { | |||||
middle = middle.add(abj.node[i].position); | |||||
} | |||||
middle = middle.multiplyScalar(1.0/abj.order()); | |||||
return middle; | |||||
}; | |||||
graph.update = function(newState) { | |||||
if (newState){abj.update(newState);} | |||||
var gs = gui.scene.getObjectByName("graphstate"); | |||||
if (gs){ gui.scene.remove(gs); } | |||||
var geometry = new THREE.Geometry(); | |||||
geometry.colors = []; | |||||
for (var i in abj.node) { | |||||
var color = graph.colors[abj.node[i].vop % graph.colors.length]; | |||||
if (abj.node[i].color !== undefined){ | |||||
color = abj.node[i].color; | |||||
} | |||||
geometry.vertices.push(abj.node[i].position); | |||||
geometry.colors.push(new THREE.Color(color)); | |||||
} | |||||
graph.center(); | |||||
gui.controls.target.copy(graph.center()); | |||||
var edges = new THREE.Object3D(); | |||||
var my_edges = abj.edgelist(); | |||||
for (i = 0; i < my_edges.length; ++i) { | |||||
var edge = my_edges[i]; | |||||
var start = abj.node[edge[0]].position; | |||||
var startpos = new THREE.Vector3(start.x, start.y, start.z); | |||||
var end = abj.node[edge[1]].position; | |||||
var endpos = new THREE.Vector3(end.x, end.y, end.z); | |||||
var newEdge = materials.makeCurve(startpos, endpos); | |||||
edges.add(newEdge); | |||||
} | |||||
if (editor.selection) { | |||||
console.log(editor.selection); | |||||
var node = editor.selection; | |||||
if (Object.prototype.hasOwnProperty.call(abj.node, node)) { | |||||
editor.grid.position.copy(abj.node[node].position); | |||||
gui.controls.target.copy(abj.node[node].position); | |||||
node_name.innerHTML = "Node " + node; | |||||
node_data.className = "visible"; | |||||
node_vop.innerHTML = "VOP: " + abj.node[node].vop; | |||||
} else { | |||||
editor.selection = undefined; | |||||
node_data.className = "hidden"; | |||||
} | |||||
} else { | |||||
node_data.className = "hidden"; | |||||
} | |||||
var particles = new THREE.Points(geometry, materials.qubit); | |||||
var object = new THREE.Object3D(); | |||||
object.name = "graphstate"; | |||||
object.add(particles); | |||||
object.add(edges); | |||||
gui.scene.add(object); | |||||
gui.render(); | |||||
}; | |||||
@@ -0,0 +1,80 @@ | |||||
var gui = {}; | |||||
gui.prepare = function() { | |||||
gui.renderer = new THREE.WebGLRenderer({ | |||||
"antialias": true | |||||
}); | |||||
gui.renderer.setSize(window.innerWidth, window.innerHeight); | |||||
gui.renderer.setClearColor(0xffffff, 1); | |||||
document.querySelector("body").appendChild(gui.renderer.domElement); | |||||
window.addEventListener("resize", gui.onWindowResize, false); | |||||
gui.makeScene(); | |||||
gui.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.3, 1000); | |||||
gui.controls = new THREE.OrbitControls(gui.camera); | |||||
gui.controls.addEventListener("change", gui.render); | |||||
gui.controls.center.set(0, 0, 0); | |||||
gui.controls.target.set(0, 0, 0); | |||||
gui.controls.rotateSpeed = 0.2; | |||||
gui.controls.userPanSpeed = 0.1; | |||||
gui.camera.position.set(0, 0, 10); | |||||
gui.controls.autoRotate = true; | |||||
gui.controls.autoRotateSpeed = 0.1; | |||||
}; | |||||
// Someone resized the window | |||||
gui.onWindowResize = function(evt) { | |||||
gui.camera.aspect = window.innerWidth / window.innerHeight; | |||||
gui.camera.updateProjectionMatrix(); | |||||
gui.renderer.setSize(window.innerWidth, window.innerHeight); | |||||
gui.render(); | |||||
}; | |||||
// Render the current frame to the screen | |||||
gui.render = function() { | |||||
requestAnimationFrame(function() { | |||||
gui.renderer.render(gui.scene, gui.camera); | |||||
}); | |||||
}; | |||||
// Make the extra bits of gui | |||||
gui.makeScene = function() { | |||||
gui.scene = new THREE.Scene(); | |||||
}; | |||||
// Put an HTML message to the screen | |||||
// TODO: write a generic messaging class? | |||||
gui.serverMessage = function(msgtext, persist) { | |||||
if (persist === undefined) {persist = false;} | |||||
server_info.innerHTML = msgtext; | |||||
server_info.className = "visible"; | |||||
clearInterval(gui.ki); | |||||
if (!persist){ | |||||
gui.ki = setInterval(function(){server_info.className="hidden";}, 3000); | |||||
} | |||||
}; | |||||
gui.nodeMessage = function(msgtext) { | |||||
node_info.innerHTML = msgtext; | |||||
node_info.className = "visible"; | |||||
}; | |||||
gui.hideNodeMessage = function(){ | |||||
node_info.className = "hidden"; | |||||
}; | |||||
// Set the position of the info popup | |||||
gui.setInfoPosition = function(position){ | |||||
w = node_info.offsetWidth; | |||||
h = node_info.offsetHeight; | |||||
node_info.style.left = position.x - w/2 + "px"; | |||||
node_info.style.top = position.y - h -10 + "px"; | |||||
}; | |||||
// The main loop | |||||
gui.loop = function() { | |||||
gui.controls.update(); | |||||
editor.update(); | |||||
requestAnimationFrame(gui.loop); | |||||
}; | |||||
@@ -0,0 +1,58 @@ | |||||
var mouse = {}; | |||||
var interaction = {}; | |||||
interaction.raycaster = new THREE.Raycaster(); | |||||
interaction.xyplane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0); | |||||
// Gets a reference to the node nearest to the mouse cursor | |||||
interaction.nearestNode = function() { | |||||
this.raycaster.setFromCamera(mouse, camera); | |||||
for (var i = 0; i < nodeGeometry.vertices.length; ++i) { | |||||
var v = nodeGeometry.vertices[i]; | |||||
if (this.raycaster.ray.distanceSqToPoint(v) < 0.01) { | |||||
return i; | |||||
} | |||||
} | |||||
return undefined; | |||||
}; | |||||
// Find out: what is the mouse pointing at? | |||||
interaction.checkIntersections = function() { | |||||
var new_selection = nearestNode(); | |||||
if (new_selection != this.selection) { | |||||
this.selection = new_selection; | |||||
info.className = this.selection ? "visible" : "hidden"; | |||||
info.innerHTML = this.selection ? nodeGeometry.labels[new_selection] : info.innerHTML; | |||||
render(); | |||||
} | |||||
}; | |||||
// Update the mouse position tracker | |||||
interaction.onMouseMove = function(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(); | |||||
}; | |||||
// Try to add a qubit at the current mouse position | |||||
interaction.addQubitAtMouse = function(event) { | |||||
this.raycaster.setFromCamera(mouse, camera); | |||||
var intersection = this.raycaster.ray.intersectPlane(this.plane); | |||||
intersection.x = Math.round(intersection.x); | |||||
intersection.y = Math.round(intersection.y); | |||||
abj.add_node(Object.keys(vops).length, { | |||||
"position": intersection | |||||
}); | |||||
updateScene(); | |||||
} | |||||
interaction.bind = function() { | |||||
var el = renderer.domElement; | |||||
el.addEventListener("mousedown", this.onMouseDown); | |||||
el.addEventListener("mouseup", this.onMouseDown); | |||||
el.addEventListener("mousemove", this.onMouseMove); | |||||
}; |
@@ -0,0 +1,10 @@ | |||||
console.log("abp v0.4.27"); | |||||
window.onload = function() { | |||||
graph.prepare(); | |||||
materials.prepare(); | |||||
gui.prepare(); | |||||
mouse.prepare(); | |||||
editor.prepare(); | |||||
gui.loop(); | |||||
}; |
@@ -0,0 +1,38 @@ | |||||
var materials = {}; | |||||
var curveProperties = { | |||||
splineDensity: 10, | |||||
curvature: 20 | |||||
}; | |||||
// Is called on boot | |||||
materials.prepare = function() { | |||||
var ballSprite = new THREE.Texture(document.getElementById("ball")); | |||||
ballSprite.needsUpdate = true; | |||||
materials.edge = new THREE.LineBasicMaterial({ | |||||
color: "gray", | |||||
transparent: false, | |||||
linewidth: 3 | |||||
}); | |||||
materials.edge.depthTest = false; | |||||
materials.qubit = new THREE.PointsMaterial({ | |||||
size: 0.6, | |||||
map: ballSprite, | |||||
alphaTest: 0.5, | |||||
transparent: true, | |||||
vertexColors: THREE.VertexColors | |||||
}); | |||||
}; | |||||
materials.makeCurve = function(a, b) { | |||||
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); | |||||
return new THREE.Line(geometry, materials.edge); | |||||
}; | |||||
@@ -0,0 +1,73 @@ | |||||
var mouse = {}; | |||||
mouse.wasClick = true; | |||||
mouse.pressed = false; | |||||
mouse.leniency = 4; | |||||
mouse.raycaster = new THREE.Raycaster(); | |||||
mouse.onFreeMove = function() { | |||||
console.log("Free move"); | |||||
}; | |||||
mouse.onDrag = function() { | |||||
//console.log("Drag"); | |||||
}; | |||||
mouse.onClick = function() { | |||||
console.log("Click"); | |||||
}; | |||||
mouse.onCtrlClick = function() { | |||||
console.log("Ctrl-click"); | |||||
}; | |||||
mouse.onShiftClick = function() { | |||||
console.log("Shift-click"); | |||||
}; | |||||
mouse.prepare = function() { | |||||
var el = gui.renderer.domElement; | |||||
el.addEventListener("mousedown", mouse.onDown); | |||||
el.addEventListener("mouseup", mouse.onUp); | |||||
el.addEventListener("mousemove", mouse.onMove); | |||||
}; | |||||
mouse.onDown = function(event) { | |||||
mouse.wasClick = true; | |||||
mouse.pressed = true; | |||||
mouse.startX = event.clientX; | |||||
mouse.startY = event.clientY; | |||||
}; | |||||
mouse.onUp = function(event) { | |||||
mouse.pressed = false; | |||||
if (!mouse.wasClick) { | |||||
return; | |||||
} | |||||
if (event.ctrlKey) { | |||||
mouse.onCtrlClick(); | |||||
} else if (event.shiftKey) { | |||||
mouse.onShiftClick(); | |||||
} else { | |||||
mouse.onClick(); | |||||
} | |||||
}; | |||||
mouse.onMove = function(event) { | |||||
// TODO: wasclick sux | |||||
if (Math.abs(event.clientX - mouse.startX)>mouse.leniency || Math.abs(event.clientY - mouse.startY)>mouse.leniency){ | |||||
mouse.wasClick = false; | |||||
} | |||||
mouse.position_absolute = { | |||||
x: event.clientX, | |||||
y: event.clientY | |||||
}; | |||||
mouse.position_relative = { | |||||
x: (event.clientX / window.innerWidth) * 2 - 1, | |||||
y: -(event.clientY / window.innerHeight) * 2 + 1 | |||||
}; | |||||
gui.setInfoPosition(mouse.position_absolute); | |||||
mouse.raycaster.setFromCamera(mouse.position_relative, gui.camera); | |||||
mouse.ray = mouse.raycaster.ray; | |||||
if (mouse.pressed) { | |||||
mouse.onDrag(); | |||||
} else { | |||||
mouse.onFreeMove(); | |||||
} | |||||
}; |
@@ -0,0 +1,412 @@ | |||||
// three.js - http://github.com/mrdoob/three.js | |||||
/** | |||||
* @author qiao / https://github.com/qiao | |||||
* @author mrdoob / http://mrdoob.com | |||||
* @author alteredq / http://alteredqualia.com/ | |||||
* @author WestLangley / http://github.com/WestLangley | |||||
*/ | |||||
THREE.OrbitControls = function ( object, domElement ) { | |||||
this.object = object; | |||||
this.domElement = ( domElement !== undefined ) ? domElement : document; | |||||
// API | |||||
this.enabled = true; | |||||
this.center = new THREE.Vector3(); | |||||
this.target = new THREE.Vector3(); | |||||
this.userZoom = true; | |||||
this.userZoomSpeed = 1.0; | |||||
this.userRotate = true; | |||||
this.userRotateSpeed = 1.0; | |||||
this.userPan = true; | |||||
this.userPanSpeed = 0.1; | |||||
this.autoRotate = false; | |||||
this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 | |||||
this.minPolarAngle = 0; // radians | |||||
this.maxPolarAngle = Math.PI; // radians | |||||
this.minDistance = 0; | |||||
this.maxDistance = Infinity; | |||||
// 65 /*A*/, 83 /*S*/, 68 /*D*/ | |||||
this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40, ROTATE: 65, ZOOM: 83, PAN: 68 }; | |||||
// internals | |||||
var scope = this; | |||||
var EPS = 0.000001; | |||||
var PIXELS_PER_ROUND = 1800; | |||||
var rotateStart = new THREE.Vector2(); | |||||
var rotateEnd = new THREE.Vector2(); | |||||
var rotateDelta = new THREE.Vector2(); | |||||
var zoomStart = new THREE.Vector2(); | |||||
var zoomEnd = new THREE.Vector2(); | |||||
var zoomDelta = new THREE.Vector2(); | |||||
var phiDelta = 0; | |||||
var thetaDelta = 0; | |||||
var scale = 1; | |||||
var lastPosition = new THREE.Vector3(); | |||||
var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2 }; | |||||
var state = STATE.NONE; | |||||
// events | |||||
var changeEvent = { type: 'change' }; | |||||
this.rotateLeft = function ( angle ) { | |||||
if ( angle === undefined ) { | |||||
angle = getAutoRotationAngle(); | |||||
} | |||||
thetaDelta -= angle; | |||||
}; | |||||
this.rotateRight = function ( angle ) { | |||||
if ( angle === undefined ) { | |||||
angle = getAutoRotationAngle(); | |||||
} | |||||
thetaDelta += angle; | |||||
}; | |||||
this.rotateUp = function ( angle ) { | |||||
if ( angle === undefined ) { | |||||
angle = getAutoRotationAngle(); | |||||
} | |||||
phiDelta -= angle; | |||||
}; | |||||
this.rotateDown = function ( angle ) { | |||||
if ( angle === undefined ) { | |||||
angle = getAutoRotationAngle(); | |||||
} | |||||
phiDelta += angle; | |||||
}; | |||||
this.zoomIn = function ( zoomScale ) { | |||||
if ( zoomScale === undefined ) { | |||||
zoomScale = getZoomScale(); | |||||
} | |||||
scale /= zoomScale; | |||||
}; | |||||
this.zoomOut = function ( zoomScale ) { | |||||
if ( zoomScale === undefined ) { | |||||
zoomScale = getZoomScale(); | |||||
} | |||||
scale *= zoomScale; | |||||
}; | |||||
this.pan = function ( distance ) { | |||||
distance.transformDirection( this.object.matrix ); | |||||
distance.multiplyScalar( scope.userPanSpeed ); | |||||
this.object.position.add( distance ); | |||||
this.center.add( distance ); | |||||
this.target.add( distance ); | |||||
}; | |||||
this.update = function () { | |||||
var position = this.object.position; | |||||
var offset = position.clone().sub( this.center ); | |||||
var diff = this.center.clone().sub( this.target ).multiplyScalar(0.2); | |||||
this.center.sub(diff); | |||||
// angle from z-axis around y-axis | |||||
var theta = Math.atan2( offset.x, offset.z ); | |||||
// angle from y-axis | |||||
var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); | |||||
if ( this.autoRotate ) { | |||||
this.rotateLeft( getAutoRotationAngle() ); | |||||
} | |||||
theta += thetaDelta; | |||||
phi += phiDelta; | |||||
// restrict phi to be between desired limits | |||||
phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); | |||||
// restrict phi to be betwee EPS and PI-EPS | |||||
phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); | |||||
var radius = offset.length() * scale; | |||||
// restrict radius to be between desired limits | |||||
radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); | |||||
offset.x = radius * Math.sin( phi ) * Math.sin( theta ); | |||||
offset.y = radius * Math.cos( phi ); | |||||
offset.z = radius * Math.sin( phi ) * Math.cos( theta ); | |||||
position.copy( this.center ).add( offset ); | |||||
this.object.lookAt( this.center ); | |||||
thetaDelta /= 1.5; | |||||
phiDelta /= 1.5; | |||||
scale = 1; | |||||
if ( lastPosition.distanceTo( this.object.position ) > 0.01 ) { | |||||
this.dispatchEvent( changeEvent ); | |||||
lastPosition.copy( this.object.position ); | |||||
} | |||||
}; | |||||
function getAutoRotationAngle() { | |||||
return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; | |||||
} | |||||
function getZoomScale() { | |||||
return Math.pow( 0.95, scope.userZoomSpeed ); | |||||
} | |||||
function onMouseDown( event ) { | |||||
if ( scope.enabled === false ) return; | |||||
if ( scope.userRotate === false ) return; | |||||
event.preventDefault(); | |||||
if ( state === STATE.NONE ) | |||||
{ | |||||
if ( event.button === 0 ) | |||||
state = STATE.ROTATE; | |||||
if ( event.button === 1 ) | |||||
state = STATE.ZOOM; | |||||
if ( event.button === 2 ) | |||||
state = STATE.PAN; | |||||
} | |||||
if ( state === STATE.ROTATE ) { | |||||
//state = STATE.ROTATE; | |||||
rotateStart.set( event.clientX, event.clientY ); | |||||
} else if ( state === STATE.ZOOM ) { | |||||
//state = STATE.ZOOM; | |||||
zoomStart.set( event.clientX, event.clientY ); | |||||
} else if ( state === STATE.PAN ) { | |||||
//state = STATE.PAN; | |||||
} | |||||
document.addEventListener( 'mousemove', onMouseMove, false ); | |||||
document.addEventListener( 'mouseup', onMouseUp, false ); | |||||
} | |||||
function onMouseMove( event ) { | |||||
if ( scope.enabled === false ) return; | |||||
event.preventDefault(); | |||||
if ( state === STATE.ROTATE ) { | |||||
rotateEnd.set( event.clientX, event.clientY ); | |||||
rotateDelta.subVectors( rotateEnd, rotateStart ); | |||||
scope.rotateLeft( 2 * Math.PI * rotateDelta.x / PIXELS_PER_ROUND * scope.userRotateSpeed ); | |||||
scope.rotateUp( 2 * Math.PI * rotateDelta.y / PIXELS_PER_ROUND * scope.userRotateSpeed ); | |||||
rotateStart.copy( rotateEnd ); | |||||
} else if ( state === STATE.ZOOM ) { | |||||
zoomEnd.set( event.clientX, event.clientY ); | |||||
zoomDelta.subVectors( zoomEnd, zoomStart ); | |||||
if ( zoomDelta.y > 0 ) { | |||||
scope.zoomIn(); | |||||
} else { | |||||
scope.zoomOut(); | |||||
} | |||||
zoomStart.copy( zoomEnd ); | |||||
} else if ( state === STATE.PAN ) { | |||||
var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0; | |||||
var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0; | |||||
scope.pan( new THREE.Vector3( - movementX, movementY, 0 ) ); | |||||
} | |||||
} | |||||
function onMouseUp( event ) { | |||||
if ( scope.enabled === false ) return; | |||||
if ( scope.userRotate === false ) return; | |||||
document.removeEventListener( 'mousemove', onMouseMove, false ); | |||||
document.removeEventListener( 'mouseup', onMouseUp, false ); | |||||
state = STATE.NONE; | |||||
} | |||||
function onMouseWheel( event ) { | |||||
if ( scope.enabled === false ) return; | |||||
if ( scope.userZoom === false ) return; | |||||
var delta = 0; | |||||
if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9 | |||||
delta = event.wheelDelta; | |||||
} else if ( event.detail ) { // Firefox | |||||
delta = - event.detail; | |||||
} | |||||
if ( delta > 0 ) { | |||||
scope.zoomOut(); | |||||
} else { | |||||
scope.zoomIn(); | |||||
} | |||||
} | |||||
function onKeyDown( event ) { | |||||
if ( scope.enabled === false ) return; | |||||
if ( scope.userPan === false ) return; | |||||
switch ( event.keyCode ) { | |||||
/*case scope.keys.UP: | |||||
scope.pan( new THREE.Vector3( 0, 1, 0 ) ); | |||||
break; | |||||
case scope.keys.BOTTOM: | |||||
scope.pan( new THREE.Vector3( 0, - 1, 0 ) ); | |||||
break; | |||||
case scope.keys.LEFT: | |||||
scope.pan( new THREE.Vector3( - 1, 0, 0 ) ); | |||||
break; | |||||
case scope.keys.RIGHT: | |||||
scope.pan( new THREE.Vector3( 1, 0, 0 ) ); | |||||
break; | |||||
*/ | |||||
case scope.keys.ROTATE: | |||||
state = STATE.ROTATE; | |||||
break; | |||||
case scope.keys.ZOOM: | |||||
state = STATE.ZOOM; | |||||
break; | |||||
case scope.keys.PAN: | |||||
state = STATE.PAN; | |||||
break; | |||||
} | |||||
} | |||||
function onKeyUp( event ) { | |||||
switch ( event.keyCode ) { | |||||
case scope.keys.ROTATE: | |||||
case scope.keys.ZOOM: | |||||
case scope.keys.PAN: | |||||
state = STATE.NONE; | |||||
break; | |||||
} | |||||
} | |||||
this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); | |||||
this.domElement.addEventListener( 'mousedown', onMouseDown, false ); | |||||
this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); | |||||
this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox | |||||
window.addEventListener( 'keydown', onKeyDown, false ); | |||||
window.addEventListener( 'keyup', onKeyUp, false ); | |||||
}; | |||||
THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); |
@@ -0,0 +1,36 @@ | |||||
var websocket = {}; | |||||
websocket.update = undefined; | |||||
websocket.connect = function(update) { | |||||
websocket.ws = new WebSocket("ws://localhost:5000"); | |||||
if (update){ | |||||
websocket.update = update; | |||||
} | |||||
websocket.ws.onopen = function(evt) { | |||||
gui.serverMessage("Connected to server."); | |||||
}; | |||||
websocket.ws.onerror = function(err) { | |||||
gui.serverMessage("Could not connect to server."); | |||||
}; | |||||
websocket.ws.onmessage = function(evt) { | |||||
json = JSON.parse(evt.data); | |||||
for (var i in json.node) { | |||||
var pos = json.node[i].position; | |||||
json.node[i].position = new THREE.Vector3(pos.x, pos.y, pos.z); | |||||
if (json.node[i].vop === undefined){ | |||||
json.node[i].vop = 0; | |||||
} | |||||
} | |||||
websocket.update(json); | |||||
}; | |||||
websocket.ws.onclose = function(evt) { | |||||
gui.serverMessage("No connection to server. <a href='#' onclick='javascript:websocket.connect()'>Reconnect</a>.", true); | |||||
}; | |||||
}; | |||||
websocket.edit = function (data) { | |||||
websocket.ws.send("edit:"+JSON.stringify(data)); | |||||
}; |
@@ -0,0 +1,47 @@ | |||||
<!DOCTYPE html> | |||||
<html lang="en"> | |||||
<head> | |||||
<title>abp</title> | |||||
<meta charset="utf-8"> | |||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=1" /> | |||||
<link rel="stylesheet" href="{{ url_for("static", filename="main.css") }}"> | |||||
<script src="{{ url_for("static", filename="scripts/three.js") }}"></script> | |||||
<script src="{{ url_for("static", filename="scripts/orbitcontrols.js") }}"></script> | |||||
<script src="{{ url_for("static", filename="scripts/anders_briegel.js") }}"></script> | |||||
<script src="{{ url_for("static", filename="scripts/mouse.js") }}"></script> | |||||
<script src="{{ url_for("static", filename="scripts/materials.js") }}"></script> | |||||
<script src="{{ url_for("static", filename="scripts/graph.js") }}"></script> | |||||
<script src="{{ url_for("static", filename="scripts/gui.js") }}"></script> | |||||
<script src="{{ url_for("static", filename="scripts/editor.js") }}"></script> | |||||
<script src="{{ url_for("static", filename="scripts/websocket.js") }}"></script> | |||||
<script src="{{ url_for("static", filename="scripts/main.js") }}"></script> | |||||
</head> | |||||
<body> | |||||
<img id=ball src="img/ball.png" style=display:none;> | |||||
<img id=tip src="img/tip.png" style=display:none;> | |||||
<div id=node_info class=hidden> nothing </div> | |||||
<div id=server_info class=hidden> </div> | |||||
<div id=version>Version 0.4.27</div> | |||||
<div id=node_data class=hidden> | |||||
<div id=node_name></div> | |||||
<ul> | |||||
<li id=node_vop></li> | |||||
<li>Measure in | |||||
<a href="#" onclick="editor.measureX()">X</a> / | |||||
<a href="#" onclick="editor.measureY()">Y</a> / | |||||
<a href="#" onclick="editor.measureZ()">Z</a></li> | |||||
<li>Act | |||||
<a href="#" onclick="editor.hadamard()">Hadamard</a> / | |||||
<a href="#" onclick="editor.phase()">Phase</a></li> | |||||
<li><a href="#" onclick="editor.localComplementation()">Invert neighbourhood</a></li> | |||||
<li><a href="#" onclick="editor.deleteNode()">Delete</a></li> | |||||
</ul> | |||||
</div> | |||||
</body> | |||||
</html> |