Reference Source

src/viewer/scene/CameraControl/lib/handlers/TouchPickHandler.js

import {math} from "../../../math/math.js";

const TAP_INTERVAL = 150;
const DBL_TAP_INTERVAL = 325;
const TAP_DISTANCE_THRESHOLD = 4;

const getCanvasPosFromEvent = function (event, canvasPos) {
    if (!event) {
        event = window.event;
        canvasPos[0] = event.x;
        canvasPos[1] = event.y;
    } else {
        let element = event.target;
        let totalOffsetLeft = 0;
        let totalOffsetTop = 0;
        while (element.offsetParent) {
            totalOffsetLeft += element.offsetLeft;
            totalOffsetTop += element.offsetTop;
            element = element.offsetParent;
        }
        canvasPos[0] = event.pageX - totalOffsetLeft;
        canvasPos[1] = event.pageY - totalOffsetTop;
    }
    return canvasPos;
};

/**
 * @private
 */
class TouchPickHandler {

    constructor(scene, controllers, configs, states, updates) {

        this._scene = scene;

        const pickController = controllers.pickController;
        const cameraControl = controllers.cameraControl;

        let touchStartTime;
        const activeTouches = [];
        const tapStartPos = new Float32Array(2);
        let tapStartTime = -1;
        let lastTapTime = -1;

        const canvas = this._scene.canvas.canvas;

        const flyCameraTo = (pickResult) => {
            let pos;
            if (pickResult && pickResult.worldPos) {
                pos = pickResult.worldPos
            }
            const aabb = pickResult ? pickResult.entity.aabb : scene.aabb;
            if (pos) { // Fly to look at point, don't change eye->look dist
                const camera = scene.camera;
                const diff = math.subVec3(camera.eye, camera.look, []);
                controllers.cameraFlight.flyTo({
                    aabb: aabb
                });
                // TODO: Option to back off to fit AABB in view
            } else {// Fly to fit target boundary in view
                controllers.cameraFlight.flyTo({
                    aabb: aabb
                });
            }
        };

        canvas.addEventListener("touchstart", this._canvasTouchStartHandler = (e) => {

            if (!(configs.active && configs.pointerEnabled)) {
                return;
            }

            if (states.longTouchTimeout !== null) {
                clearTimeout(states.longTouchTimeout);
                states.longTouchTimeout = null;
            }

            const touches = e.touches;
            const changedTouches = e.changedTouches;

            touchStartTime = Date.now();

            if (touches.length === 1 && changedTouches.length === 1) {
                tapStartTime = touchStartTime;

                getCanvasPosFromEvent(touches[0], tapStartPos);

                const rightClickClientX = tapStartPos[0];
                const rightClickClientY = tapStartPos[1];

                const rightClickPageX = touches[0].pageX;
                const rightClickPageY = touches[0].pageY;

                states.longTouchTimeout = setTimeout(() => {
                    controllers.cameraControl.fire("rightClick", { // For context menus
                        pagePos: [Math.round(rightClickPageX), Math.round(rightClickPageY)],
                        canvasPos: [Math.round(rightClickClientX), Math.round(rightClickClientY)],
                        event: e
                    }, true);

                    states.longTouchTimeout = null;
                }, configs.longTapTimeout);

            } else {
                tapStartTime = -1;
            }

            while (activeTouches.length < touches.length) {
                activeTouches.push(new Float32Array(2))
            }

            for (let i = 0, len = touches.length; i < len; ++i) {
                getCanvasPosFromEvent(touches[i], activeTouches[i]);
            }

            activeTouches.length = touches.length;

        }, {passive: true});


        canvas.addEventListener("touchend", this._canvasTouchEndHandler = (e) => {

            if (!(configs.active && configs.pointerEnabled)) {
                return;
            }

            const currentTime = Date.now();
            const touches = e.touches;
            const changedTouches = e.changedTouches;

            const pickedSurfaceSubs = cameraControl.hasSubs("pickedSurface");

            if (states.longTouchTimeout !== null) {
                clearTimeout(states.longTouchTimeout);
                states.longTouchTimeout = null;
            }

            // process tap

            if (touches.length === 0 && changedTouches.length === 1) {

                if (tapStartTime > -1 && currentTime - tapStartTime < TAP_INTERVAL) {

                    if (lastTapTime > -1 && tapStartTime - lastTapTime < DBL_TAP_INTERVAL) {

                        // Double-tap

                        getCanvasPosFromEvent(changedTouches[0], pickController.pickCursorPos);
                        pickController.schedulePickEntity = true;
                        pickController.schedulePickSurface = pickedSurfaceSubs;

                        pickController.update();

                        if (pickController.pickResult) {

                            pickController.pickResult.touchInput = true;

                            cameraControl.fire("doublePicked", pickController.pickResult);

                            if (pickController.pickedSurface) {
                                cameraControl.fire("doublePickedSurface", pickController.pickResult);
                            }

                            if (configs.doublePickFlyTo) {
                                flyCameraTo(pickController.pickResult);
                            }
                        } else {
                            cameraControl.fire("doublePickedNothing");
                            if (configs.doublePickFlyTo) {
                                flyCameraTo();
                            }
                        }

                        lastTapTime = -1;

                    } else if (math.distVec2(activeTouches[0], tapStartPos) < TAP_DISTANCE_THRESHOLD) {

                        // Single-tap

                        getCanvasPosFromEvent(changedTouches[0], pickController.pickCursorPos);
                        pickController.schedulePickEntity = true;
                        pickController.schedulePickSurface = pickedSurfaceSubs;

                        pickController.update();

                        if (pickController.pickResult) {

                            pickController.pickResult.touchInput = true;

                            cameraControl.fire("picked", pickController.pickResult);

                            if (pickController.pickedSurface) {
                                cameraControl.fire("pickedSurface", pickController.pickResult);
                            }

                        } else {
                            cameraControl.fire("pickedNothing");
                        }

                        lastTapTime = currentTime;
                    }

                    tapStartTime = -1
                }
            }

            activeTouches.length = touches.length;

            for (let i = 0, len = touches.length; i < len; ++i) {
                activeTouches[i][0] = touches[i].pageX;
                activeTouches[i][1] = touches[i].pageY;
            }

          //  e.stopPropagation();

        }, {passive: true});

    }

    reset() {
        // TODO
        // tapStartTime = -1;
        // lastTapTime = -1;

    }

    destroy() {
        const canvas = this._scene.canvas.canvas;
        canvas.removeEventListener("touchstart", this._canvasTouchStartHandler);
        canvas.removeEventListener("touchend", this._canvasTouchEndHandler);
    }
}


export {TouchPickHandler};