Source: interactionControls.js

/**
 * Handles the canvas interaction such as zooming, panning and clicking.
 * @module InteractionControls
 */

var prevMousePos = { x: 0, y: 0 };
var mouseDown = false;

var aspectRatio = window.innerWidth / window.innerHeight;
var maxWindowSize = Math.max(window.innerWidth, window.innerHeight);

var cameraSize = 5;
var cameraZPos = 5;
var cameraMovementSpeed;
var cameraZoomSpeed = 2;
var camera = new THREE.OrthographicCamera(cameraSize * aspectRatio / -2, cameraSize * aspectRatio / 2, cameraSize / 2, cameraSize / -2, 1, 10);

var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();

/**
 * sets the camera to its initial position and defines all the interaction events
 */
function setupCamera() {
    cameraMovementSpeed = cameraSize;
    raycaster.linePrecision = edgeDistance / 2;
    camera.position.set(0, 0, cameraZPos);
    camera.zoom = 1.0;
    camera.updateProjectionMatrix();

    canvas.onmousedown = function (evt) {
        mouseDown = true;
        prevMousePos.x = evt.x;
        prevMousePos.y = evt.y
        mouse.x = (evt.clientX / window.innerWidth) * 2 - 1;
        mouse.y = - (evt.clientY / window.innerHeight) * 2 + 1;
        raycast(mouse);
    }

    canvas.onmouseup = function () {
        mouseDown = false;
    }

    canvas.onmousemove = function (evt) {
        if (mouseDown) {
            // scale movement speed according to current zoom level
            let x = (evt.x - prevMousePos.x) / maxWindowSize * cameraMovementSpeed / camera.zoom;
            let y = (evt.y - prevMousePos.y) / maxWindowSize * cameraMovementSpeed / camera.zoom;
            prevMousePos.x = evt.x;
            prevMousePos.y = evt.y;
            if (!rendering) {
                camera.translateX(-x);
                camera.translateY(y);
                renderScene();
            }
        }
        // scale mouse pos to range [-1,1] for raycast
        mouse.x = (evt.clientX / window.innerWidth) * 2 - 1;
        mouse.y = - (evt.clientY / window.innerHeight) * 2 + 1;
    }

    canvas.addEventListener("wheel", function (e) {
        let dir = Math.sign(e.deltaY);
        if (!rendering) {
            if (dir < 0)
                camera.zoom += camera.zoom * cameraZoomSpeed * 0.1;
            else
                camera.zoom -= camera.zoom * cameraZoomSpeed * 0.1;
            camera.zoom = Math.max(0.5, Math.min(Math.max(30, cameraSize / 5), camera.zoom));
            camera.updateProjectionMatrix();
            renderScene();
        }
    });

    window.addEventListener('resize', () => {
        aspectRatio = window.innerWidth / window.innerHeight;
        updateCamera(cameraSize, camera.zoom, false);
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderScene();
    });
}

/**
 * updates the camera based on the specified parameters
 * @param {float} size of the camera frustum
 * @param {float} zoom level of the camera (1 = default)
 * @param {boolean} centerOriginFlag sets the camera back to the 0 origin if true
 */
function updateCamera(size, zoom, centerOriginFlag) {
    if (centerOriginFlag)
        camera.position.set(0, 0, cameraZPos);
    cameraSize = size;
    cameraMovementSpeed = cameraSize;
    cameraZoomSpeed = 2;
    camera.zoom = zoom;
    camera.aspect = aspectRatio;
    camera.left = cameraSize * aspectRatio / -2;
    camera.right = cameraSize * aspectRatio / 2;
    camera.top = cameraSize / 2;
    camera.bottom = cameraSize / -2;
    camera.updateProjectionMatrix();
}

/**
 * spawns a ray and checks for intersections
 * in detailLOD: checks for intersections with nodes and edges
 * in lowDetailLOD: checks for intersections with supernodes
 * if an intersection occurs the further processing is forwarded to the render logic and the GUI
 * @param {vec2} mousePos where the click occured, both coordinates in range [-1,1]
 */
function raycast(mousePos) {
    raycaster.setFromCamera(mousePos, camera);
    var intersects = raycaster.intersectObjects(nodeGroup.children);
    if (intersects.length == 1) {
        // node or supernode clicked
        if (detailLODEnabled) {
            let node = nodes[intersects[0].object.geometry.nodeId];
            selectNode(node);
            guiUpdateSelectedNode(node);
        }
        else {
            let supernode = allSupernodes[intersects[0].object.geometry.supernodeId];
            selectSupernode(supernode);
        }
    }
    else if (detailLODEnabled) {
        intersects = raycaster.intersectObjects(linesGroup.children);
        if (intersects.length != 0) {
            // edge clicked
            // divide by two because one line has two points!
            let lineIndex = Math.floor(intersects[0].index / 2);
            selectEdge(lines[lineIndex].renderedEdge);
            guiUpdateSelectedEdge(lines[lineIndex].renderedEdge.edge);
        }
    }
}