| @@ -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> | |||