Selection.js 7.41 KB
import { EventDispatcher, Line, Float32BufferAttribute, Vector3, Raycaster, Vector2 } from 'three';
import { detectIntersectionByShape } from '@/utility/jsm/meshEditor/meshDetection';

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 Selection extends EventDispatcher {
    constructor(editor) {
        super();
        const { camera, scene, domElement, control } = editor;

        this._camera = camera;
        this._scene = scene;
        this._control = control;
        this._domElement = domElement;

        this.addEvents();

        // handle building lasso shape
        this.startX = -Infinity;
        this.startY = -Infinity;

        this.prevX = -Infinity;
        this.prevY = -Infinity;

        this.dragging = false;
        this.isPressing = false;

        this.selectionShapeNeedsUpdate = false;

        this.selectionShape = null;

        this.selectedMeshs = [];
        this.lassoPoints = [];
        this.selectionPoints = [];

        this.raycaster = new Raycaster();
        this.enabled = true;
        this.ismoving = false;

        this.init();
    }

    init = () => {
        if (!this._camera) return;
        // selection shape
        const selectionShape = new Line();
        selectionShape.material.color.set(0xff9800).convertSRGBToLinear();
        selectionShape.renderOrder = 1;
        selectionShape.position.z = -0.2;
        selectionShape.depthTest = false;
        selectionShape.scale.setScalar(1);
        this.selectionShape = selectionShape;
        this._camera.add(selectionShape);
    };

    addEvents = () => {
        this._domElement.addEventListener('pointerdown', this.onPointerdown);
        document.addEventListener('pointerup', this.onPointerup);
        this._domElement.addEventListener('pointermove', this.onPointermove);
    };

    removeEvents = () => {
        this._domElement.removeEventListener('pointerdown', this.onPointerdown);
        document.removeEventListener('pointerup', this.onPointerup);
        this._domElement.removeEventListener('pointermove', this.onPointermove);
    };

    dispose = () => {
        this.removeEvents();
        if (this.selectionShape) {
            this.selectionShape.geometry.dispose();
            this.selectionShape.material.dispose();
        }
    };

    onPointerdown = (e) => {
        if (!this.enabled) return;
        if (e.button !== 0) return;
        this.prevX = e.clientX;
        this.prevY = e.clientY;

        this.selectionPoints.length = 0;
        this.isPressing = true;

        const vec2d = getMousePosition(e, this._domElement);
        const mouse = new Vector2(vec2d.x, vec2d.y);

        this.startX = vec2d.x;
        this.startY = vec2d.y;

        this.raycaster.firstHitOnly = true;
        this._camera.near = 1;
        this.raycaster.setFromCamera(mouse, this._camera);

        const objects = this._scene.children.filter(
            (el) => el.name.includes('active:') && el.isMesh === true && el.visible
        );
        const res = this.raycaster.intersectObjects(objects);

        this.isPressing = !(res.length > 0);
        this.dispatchEvent({ type: 'drawSelect-start' });
    };

    onPointerup = (e) => {
        this.isPressing = false;
        if (!this.isDragging) return;
        if (this.ismoving) return;
        if (this.selectionShape) this.selectionShape.visible = false;
        this.isDragging = false;
        if (this.selectionPoints.length) {
            this.updateBoxSelection(e);
            this.lassoPoints = [];
        }
        this.dispatchEvent({
            type: 'drawSelect-end',
            message: {
                selected: this.selectedMeshs.length > 0,
                selectedMeshs: this.selectedMeshs
            }
        });
    };

    onPointermove = (e) => {
        if (this.ismoving) return;
        if (!this.enabled) return;
        // If the left mouse button is not pressed
        if ((1 & e.buttons) === 0) {
            return;
        }
        if (!this.isPressing) return;
        this.isDragging = true;

        const { startX, startY } = this;
        const ex = e.clientX;
        const ey = e.clientY;

        const vec2d = getMousePosition(e, this._domElement);
        const nx = vec2d.x;
        const ny = vec2d.y;

        this.lassoPoints.push(nx, ny, 0);

        // set points for the corner of the box
        this.selectionPoints.length = 3 * 5;

        this.selectionPoints[0] = startX;
        this.selectionPoints[1] = startY;
        this.selectionPoints[2] = 0;

        this.selectionPoints[3] = nx;
        this.selectionPoints[4] = startY;
        this.selectionPoints[5] = 0;

        this.selectionPoints[6] = nx;
        this.selectionPoints[7] = ny;
        this.selectionPoints[8] = 0;

        this.selectionPoints[9] = startX;
        this.selectionPoints[10] = ny;
        this.selectionPoints[11] = 0;

        this.selectionPoints[12] = startX;
        this.selectionPoints[13] = startY;
        this.selectionPoints[14] = 0;

        if (ex !== this.prevX || ey !== this.prevY) {
            this.selectionShapeNeedsUpdate = true;
        }

        this.prevX = ex;
        this.prevY = ey;

        if (this.selectionShape) this.selectionShape.visible = true;

        this.lassoPoints = [...this.selectionPoints];

        this.updateSelectionShape();
        this.dispatchEvent({ type: 'drawSelect-change' });
    };

    updateSelectionShape = () => {
        const camera = this._camera;
        const selectionVectors = [];
        for (let i = 0; i < this.selectionPoints.length; i += 3) {
            selectionVectors.push(
                new Vector3(this.selectionPoints[i], this.selectionPoints[i + 1], this.selectionPoints[i + 2])
            );
        }
        selectionVectors.forEach((vector) => {
            vector.applyMatrix4(camera.projectionMatrixInverse);
        });
        for (let i = 0; i < selectionVectors.length; i++) {
            this.selectionPoints[3 * i] = selectionVectors[i].x;
            this.selectionPoints[3 * i + 1] = selectionVectors[i].y;
            this.selectionPoints[3 * i + 2] = selectionVectors[i].z;
        }
        if (this.selectionShapeNeedsUpdate) {
            this.selectionShape.geometry.setAttribute(
                'position',
                new Float32BufferAttribute(this.selectionPoints, 3, false)
            );
            this.selectionShape.frustumCulled = false;
            this.selectionShapeNeedsUpdate = false;
        }
    };

    updateBoxSelection = (e) => {
        if (!this._camera) return;
        const selectedMeshs = [];
        const objects = this._scene.children.filter(
            (el) => el.name.includes('active:') && el.isMesh === true && el.visible
        );
        objects.forEach((element) => {
            const intersection = detectIntersectionByShape(this._camera, element, this.lassoPoints, true);
            if (intersection.size > 0) {
                selectedMeshs.push(element);
            }
        });
        this.selectedMeshs = selectedMeshs;
        this.dispatchEvent({
            type: 'selected',
            message: {
                selectedMeshs,
                button: e.button
            }
        });
    };

    setObjectsControlIsMoving = (val) => {
        this.ismoving = val;
    };
}

export default Selection;