DragControl.js 5.5 KB
import * as THREE from 'three';

const getMousePosition = (event, container) => {
    const rect = container.getBoundingClientRect();
    const mouse = { x: 0, y: 0 };
    mouse.x = ((event.clientX - rect.left) / (rect.right - rect.left)) * 2 - 1;
    mouse.y = -((event.clientY - rect.top) / (rect.bottom - rect.top)) * 2 + 1;
    return mouse;
};

class DragControl extends THREE.EventDispatcher {
    constructor(camera, scene, container, rootObject = []) {
        super();
        this._raycaster = new THREE.Raycaster();
        this._container = container;
        this._camera = camera;
        this._scene = scene;
        this._rootObjects = rootObject;

        this._insecPlane = new THREE.Plane();
        this._insecPoint = new THREE.Vector3();
        this._offset = new THREE.Vector3();
        this._worldPosition = new THREE.Vector3();

        this._selectObject = null;
        this._hovered = null;

        this._hoversize = 1.4;
        this._enabled = true;
        this.moving = false;

        this._addEvent();
    }

    get rootsObject() {
        return this._rootObjects;
    }

    get enabled() {
        return this._enabled;
    }

    set enabled(enabled) {
        this._enabled = enabled;
    }

    dispose = () => {
        this.removeEvent();
        this._selectObject = null;
        this._hovered = null;
        this._container = null;
        this._camera = null;
        this._rootObjects = [];
    };

    setRootsObjects = (rootObject) => {
        this._rootObjects = rootObject;
    };

    _addEvent = () => {
        if (!this._container) return;
        this._container.addEventListener('pointermove', this.onPointermove);
        this._container.addEventListener('pointerdown', this.onPointerdown);
        this._container.addEventListener('pointerup', this.onPointerup);
    };

    removeEvent = () => {
        if (!this._container) return;
        this._container.removeEventListener('pointermove', this.onPointermove);
        this._container.removeEventListener('pointerdown', this.onPointerdown);
        this._container.removeEventListener('pointerup', this.onPointerup);
    };

    onPointerdown = (event) => {
        if (!this._enabled) return;
        if (!this._camera) return;
        if (!this._container) return;
        if (this.moving) return;
        if (event.button !== 0) return;

        const vector2D = getMousePosition(event, this._container);
        const mouse = new THREE.Vector2(vector2D.x, vector2D.y);

        this._raycaster.firstHitOnly = false;
        this._camera.near = 1; // raycaster only can work on camera near plane is positive
        this._raycaster.setFromCamera(mouse, this._camera);

        const objects = this._scene.children.filter(
            (el) => el.name.includes('active:') && el.isMesh === true && el.visible
        );
        const detects = this._rootObjects.length === 0 ? objects : this._rootObjects;
        const res = this._raycaster.intersectObjects(detects);

        if (res.length > 0) {
            this.moving = true;
            this._selectObject = res[0].object;
            if (this._raycaster.ray.intersectPlane(this._insecPlane, this._insecPoint)) {
                if (this._selectObject.parent) {
                    this._offset
                        .copy(this._insecPoint)
                        .sub(this._worldPosition.setFromMatrixPosition(this._selectObject.matrix));
                }
                this.dispatchEvent({ type: 'dragMousedown', object: this._selectObject });
            }
        } else {
            this._selectObject = null;
        }
    };

    onPointermove = (event) => {
        // event.preventDefault();
        this.updatePlane();
        if (!this._enabled) return;
        if (!this._camera) return;
        if (!this._container) return;
        if (!this.moving) return;
        const vector2D = getMousePosition(event, this._container);
        const mouse = new THREE.Vector2(vector2D.x, vector2D.y);

        this._camera.near = 1; // raycaster only can work on camera near plane is positive
        this._raycaster.setFromCamera(mouse, this._camera);
        if (this._selectObject) {
            if (this._raycaster.ray.intersectPlane(this._insecPlane, this._insecPoint)) {
                const position = new THREE.Vector3().copy(this._insecPoint.sub(this._offset));
                this._selectObject.position.set(position.x, position.y, position.z);
                this.dispatchEvent({
                    type: 'dragMousemove',
                    object: this._selectObject,
                    position,
                    event
                });
            }
        }
        this._camera.near = -10000;
    };

    updatePlane = () => {
        const camera = this._camera;
        let vector = new THREE.Vector3(0, 0, 1);

        const x = Math.abs(camera.position.x) < 0.000001;
        const y = Math.abs(camera.position.y) < 0.000001;
        const z = Math.abs(camera.position.z) < 0.000001;

        if (!x && !y && !z) {
            vector = new THREE.Vector3(0, 0, 1);
        } else if (!y && x && z) {
            vector = new THREE.Vector3(0, 1, 0);
        } else if (!x && y && z) {
            vector = new THREE.Vector3(1, 0, 0);
        }

        this._insecPlane.setFromNormalAndCoplanarPoint(vector, new THREE.Vector3());
    };

    onPointerup = () => {
        if (!this.moving) return;
        if (!this._container) return;
        if (this._selectObject) this._selectObject = null;
        this.moving = false;
        this.dispatchEvent({ type: 'dragMouseup' });
    };
}

export default DragControl;