Source: systems/InteractionHelper.js

import {
    Vector2,
    Vector3,
    Raycaster
} from 'three';

/**
 * Object that allows you to connect Functions and Object if the camera are near the object
 */
class InteractionHelper {

    #registerdObjects;
    #registerdFunctions;
    #registerdEvents;
    #raycaster;
    #camera;
    #pointer;

    /**
     * @param {THREE.Camera} camera 
     */
    constructor(camera) {
        this.#registerdObjects = [];
        this.#registerdFunctions = {};
        this.#registerdEvents = {};
        this.#camera = camera;
        this.#raycaster = new Raycaster(new Vector3(), new Vector3(1, 0, 0), 0, 10);
        this.#pointer = new Vector2(0, 0);
    }

    /**
     * Add Object and Funktion to check
     * @param {THREE.MESH} object Mesh for Raycast
     * @param {function} func Executed if the camera is near the object
     * @param {String} event If set, add a Listener to the body with given name for example "keydown". Default to null.
     * @param {function} eventFunc Function of the Event to add. Default to null.
     */
    addInteraction(object, func, event = null, eventFunc = null) {
        this.#registerdObjects.push(object);
        this.#registerdFunctions[object.uuid] = func;
        this.#registerdEvents[object.uuid] = {
            "name": event,
            "func": eventFunc,
            "isSet": false
        };
    }

    /**
     * Remove Object to check
     * @param {THREE.MESH} object Mesh for Raycast
     */
    removeInteraction(object) {
        delete this.#registerdFunctions[object.uuid];
        delete this.#registerdEvents[object.uuid];

        const index = this.#registerdObjects.indexOf(object);
        if (index >= 0) {
            this.#registerdObjects.splice(index, 1);
        }
    }


    /**
     * Needs to be called to check if the camera is near the object
     * @see https://github.com/mrdoob/three.js/blob/f021ec0c9051eb11d110b0c2b93305bffd0942e0/examples/webgl_raycaster_sprite.html#L138
     */
    checkInteractions() {
        this.#raycaster.setFromCamera(this.#pointer, this.#camera);
        const intersects = this.#raycaster.intersectObjects(this.#registerdObjects, false);

        let div = document.getElementById("info");
        div.innerHTML = "⌖";

        // Remove all Eventlistener from all objects
        for (const key in this.#registerdEvents) {
            if (this.#registerdEvents[key]["isSet"]) {
                document.body.removeEventListener(this.#registerdEvents[key]["name"], this.#registerdEvents[key]["func"]);
                this.#registerdEvents[key]["isSet"] = false;
            }
        }

        // In range for intersection
        if (intersects.length > 0) {

            const res = intersects[0];

            if (res && res.object) {
                // Execute the function
                this.#registerdFunctions[res.object.uuid]();

                // Add Eventlistener if needed
                if (this.#registerdEvents[res.object.uuid]["name"] && !this.#registerdEvents[res.object.uuid]["isSet"]) {
                    document.body.addEventListener(this.#registerdEvents[res.object.uuid]["name"], this.#registerdEvents[res.object.uuid]["func"]);
                    this.#registerdEvents[res.object.uuid]["isSet"] = true;

                }
            }
        }

    }
}

export { InteractionHelper };