Home Reference Source

viewer/cameracontrol.js

import * as mat4 from "./glmatrix/mat4.js";
import * as vec3 from "./glmatrix/vec3.js";
import * as vec2 from "./glmatrix/vec2.js";

export const DRAG_ORBIT = 0xfe01;
export const DRAG_PAN = 0xfe02;
export const DRAG_SECTION = 0xfe03;

/**
 Controls the camera with user input.
 */
export class CameraControl {

    constructor(viewer) {

        this.viewer = viewer;

        this.mousePanSensitivity = 0.5;
        this.mouseOrbitSensitivity = 0.5;
        this.canvasPickTolerance = 4;

        this.canvas = viewer.canvas;
        this.camera = viewer.camera;

        this.mousePos = vec2.create();
        this.mouseDownPos = vec2.create();
        this.over = false; // True when mouse over canvas
        this.lastX = 0; // Last canvas pos while dragging
        this.lastY = 0;

        this.mouseDown = false;
        this.dragMode = DRAG_ORBIT;

        this.canvas.oncontextmenu = (e) => {
            e.preventDefault();
        };

        this.canvas.addEventListener("keydown", this.keyDownHandler = (e) => {
        	this.keyEvent(e, "down");
        });

        this.canvas.addEventListener("keyup", this.keyUpHandler = (e) => {
        	this.keyEvent(e, "up");
        });

        this.canvas.addEventListener("mousedown", this.canvasMouseDownHandler = (e) => {
        	this.canvasMouseDown(e);
        });

        this.canvas.addEventListener("mouseup", this.canvasMouseUpHandler = (e) => {
        	this.canvasMouseUp(e);
        });

        this.documentMouseUpHandler = (e) => {
        	this.documentMouseUp(e);
        };
        document.addEventListener("mouseup", this.documentMouseUpHandler);

        this.canvas.addEventListener("mouseenter", this.canvasMouseEnterHandler = (e) => {
            this.over = true;
            e.preventDefault();
        });

        this.canvas.addEventListener("mouseleave", this.canvasMouseLeaveHandler = (e) => {
            this.over = false;
            e.preventDefault();
        });

        this.canvas.addEventListener("mousemove", this.canvasMouseMoveHandler = (e) => {
        	this.canvasMouseMove(e);
        });

        this.canvas.addEventListener("wheel", this.canvasMouseWheelHandler = (e) => {
        	this.canvasWheel(e);
        });
    }

    /**
     * @private
     */
    getCanvasPosFromEvent(event, canvasPos) {
        if (!event) {
            event = window.event;
            canvasPos[0] = event.x;
            canvasPos[1] = event.y;
        } else {
//            var element = event.target;
            var totalOffsetLeft = 0;
            var totalOffsetTop = 0;
//            while (element.offsetParent) {
//                totalOffsetLeft += element.offsetLeft;
//                totalOffsetTop += element.offsetTop;
//                element = element.offsetParent;
//            }
            
            var rect = event.target.getBoundingClientRect();
            totalOffsetLeft = rect.left;
            totalOffsetTop = rect.top;
            canvasPos[0] = event.pageX - totalOffsetLeft;
            canvasPos[1] = event.pageY - totalOffsetTop;
        }
        return canvasPos;
    }

    /**
     * @private
     */
    getZoomRate() {
        var modelBounds = this.viewer.modelBounds;
        if (modelBounds) {
            var xsize = modelBounds[3] - modelBounds[0];
            var ysize = modelBounds[4] - modelBounds[1];
            var zsize = modelBounds[5] - modelBounds[2];
            var max = (xsize > ysize ? xsize : ysize);
            max = (zsize > max ? zsize : max);
            return max / 20;
        } else {
            return 1;
        }
    }

    keyEvent(e, state) {
        if (e.key == "Control") {
            if (state === "down") {
                if (this.viewer.sectionPlaneIsDisabled) {
                    this.viewer.positionSectionPlaneWidget({canvasPos: [this.lastX, this.lastY]});
                }
            } else {
                this.viewer.removeSectionPlaneWidget();
            }            
        }
    }

    /**
     * @private
     */
    canvasMouseDown(e) {
        this.getCanvasPosFromEvent(e, this.mousePos);

        this.lastX = this.mousePos[0];
        this.lastY = this.mousePos[1];

        this.mouseDown = true;
        this.mouseDownTime = e.timeStamp;
        this.mouseDownPos.set(this.mousePos);

        switch (e.which) {
            case 1:                
                if (e.ctrlKey) {
                    this.mouseDownTime = 0;
                    if (this.viewer.enableSectionPlane({canvasPos:[this.lastX, this.lastY]})) {
                        this.dragMode = DRAG_SECTION;
                    } else if (!this.viewer.sectionPlaneIsDisabled){
                        this.viewer.disableSectionPlane();
                        this.dragMode = DRAG_ORBIT;
                    }
                    this.viewer.removeSectionPlaneWidget();
                } else {
                    this.dragMode = DRAG_ORBIT;
                    let picked = this.viewer.pick({canvasPos:[this.lastX, this.lastY], select:false});
                    if (picked && picked.coordinates && picked.object) {
                        this.viewer.camera.center = picked.coordinates;
                    } else {
                        // Check if we can 'see' the previous center. If not, pick
                        // a new point.
                        let center_vp = vec3.transformMat4(vec3.create(), this.viewer.camera.center, this.viewer.camera.viewProjMatrix);

                        let isv = true;
                        for (let i = 0; i < 3; ++i) {
                            if (center_vp[i] < -1. || center_vp[i] > 1.) {
                                isv = false;
                                break;
                            }
                        }

                        if (!isv) {
                            let [x,y] = this.mousePos;
                            vec3.set(center_vp, x / this.viewer.width * 2 - 1, - y / this.viewer.height * 2 + 1, 1.);
                            vec3.transformMat4(center_vp, center_vp, this.camera.viewProjMatrixInverted);
                            vec3.subtract(center_vp, center_vp, this.camera.eye);
                            vec3.normalize(center_vp, center_vp);
                            vec3.scale(center_vp, center_vp, this.getZoomRate() * 10.);
                            vec3.add(center_vp, center_vp, this.camera.eye);
                            console.log("new center", center_vp);
                            this.viewer.camera.center = center_vp;
                        }
                    }
                }
                break;
            case 2:
                this.dragMode = DRAG_PAN; 
                break;
            default:
                break;
        }
        this.over = true;
        if (this.dragMode == DRAG_PAN || e.shiftKey) {
        	e.preventDefault();
        }
    }

    /**
     * @private
     */
    canvasMouseUp(e) {
        this.camera.orbitting = false;
        this.viewer.overlay.update();
        this.getCanvasPosFromEvent(e, this.mousePos);

        let dt = e.timeStamp - this.mouseDownTime;
        this.mouseDown = false;

        switch (e.which) {
            case 1:
            	if (dt < 500. && this.closeEnoughCanvas(this.mouseDownPos, this.mousePos)) {
                    var viewObject = this.viewer.pick({
                        canvasPos: this.mousePos,
                        shiftKey: e.shiftKey
                    });
                    if (viewObject && viewObject.object) {
                        console.log("Picked", viewObject.object);
                    }
                    this.viewer.drawScene();
                }
                break;
        }
        e.preventDefault();
    }

    /**
     * @private
     */
    canvasWheel(e) {
        this.getCanvasPosFromEvent(e, this.mousePos);
        var delta = Math.max(-1, Math.min(1, -e.deltaY * 40));
        if (delta === 0) {
            return;
        }
        var d = delta / Math.abs(delta);
        var zoom = -d * this.getZoomRate() * this.mousePanSensitivity;
        this.camera.zoom(zoom, this.mousePos);
        e.preventDefault();
    }

    /**
     * @private
     */
    closeEnoughCanvas(p, q) {
        return p[0] >= (q[0] - this.canvasPickTolerance) &&
            p[0] <= (q[0] + this.canvasPickTolerance) &&
            p[1] >= (q[1] - this.canvasPickTolerance) &&
            p[1] <= (q[1] + this.canvasPickTolerance);
    }

    /**
     * @private
     */
    canvasMouseMove(e) {
        if (!this.over) {
            return;
        }
        if (this.mouseDown || e.ctrlKey) {
            this.getCanvasPosFromEvent(e, this.mousePos);
            if (this.dragMode == DRAG_SECTION) {
                this.viewer.moveSectionPlane({canvasPos: this.mousePos});
            } else if (e.ctrlKey) {
                this.viewer.positionSectionPlaneWidget({canvasPos: this.mousePos});
            } else {
                var x = this.mousePos[0];
                var y = this.mousePos[1];
                var xDelta = (x - this.lastX);
                var yDelta = (y - this.lastY);
                this.lastX = x;
                this.lastY = y;
                if (this.dragMode == DRAG_ORBIT) {
                    let f = 0.5;
                    if (xDelta !== 0) {
                        this.camera.orbitYaw(-xDelta * this.mouseOrbitSensitivity * f);
                    }
                    if (yDelta !== 0) {
                        this.camera.orbitPitch(yDelta * this.mouseOrbitSensitivity * f);
                    }
                    this.camera.orbitting = true;
                } else if (this.dragMode == DRAG_PAN) {
                    var f = this.getEyeLookDist() / 600;
                    this.camera.pan([xDelta * f, yDelta * this.mousePanSensitivity * f, 0.0]);
                }
            }
        }
        e.preventDefault();
    }

    /**
     * @private
     */
    documentMouseUp(e) {
        this.mouseDown = false;
    	// Potential end-of-pan
        if (this.dragMode == DRAG_PAN) {
        	this.camera.updateLowVolumeListeners();
        }
        this.dragMode = DRAG_ORBIT;
    }

    getEyeLookDist() {
        var vec = vec3.create();
        return vec3.length(vec3.subtract(vec, this.viewer.camera.target, this.viewer.camera.eye));
    }

    /**
     * @private
     */
    cleanup() {
        var canvas = this.canvas;
    	document.removeEventListener("mouseup", this.documentMouseUpHandler);
        canvas.removeEventListener("mousedown", this.canvasMouseDownHandler);
        canvas.removeEventListener("mouseup", this.canvasMouseUpHandler);
        document.removeEventListener("mouseup", this.documentMouseUpHandler);
        canvas.removeEventListener("mouseenter", this.canvasMouseEnterHandler);
        canvas.removeEventListener("mouseleave", this.canvasMouseLeaveHandler);
        canvas.removeEventListener("mousemove", this.canvasMouseMoveHandler);
        canvas.removeEventListener("wheel", this.canvasMouseWheelHandler);
    }
}