[Solved] How to get spheres in selected area?

Here I get a 3D model with a sphere as shown in the figure. My goal is to use a selection area to turn the spheres in this area red. However, while doing this, I cannot paint the spheres in the exact area I selected.

Spheres in straight alignment in the area I have chosen should be painted. In other words, if the x,y axis matches, all spheres in the z axis must be red.

image

As can be seen in this image, the area I selected from above decreases downwards. However, it should land straight and every sphere within the area should be red.
Camera:

 const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000); 

This is how to create select area box and color spheres:

    // Select area and spheres
    const raycaster = new THREE.Raycaster();
    const mouse = new THREE.Vector2();
    var selectedMeshes = [];
    var selectionBox = document.getElementById("sel_box");
    let isOrbitControlEnabled = true;
    document.getElementById('forceareaadd').addEventListener('click', onPickAreaClick, false);
    var mouseDownCoords = { x: 0, y: 0 };
    var mouseUpCoords = { x: 0, y: 0 };

    function onPickAreaClick() {
        isOrbitControlEnabled = !isOrbitControlEnabled;
        controls.enabled = isOrbitControlEnabled;
        if (!isOrbitControlEnabled) {

        }
    }

    document.getElementById('clearselectedsphere').addEventListener('click', clearSelectedSpheres, false);

    // Function to clear selected spheres
    function clearSelectedSpheres() {
        // Iterate through selectedMeshes and reset their colors
        selectedMeshes.forEach((sphere) => {
            sphere.material.color.set(0x00ff00);
        });

        // Clear the selectedMeshes array
        selectedMeshes = [];
        console.log("selected meshes after clear: " + selectedMeshes);
    }

    function getCursorPosition(e) {
        e = e || window.event;
        if (e) {
            if (e.pageX || e.pageX == 0) return [e.pageX, e.pageY];
                var dE = document.documentElement || {};
                var dB = document.body || {};
            if ((e.clientX || e.clientX == 0) && ((dB.scrollLeft || dB.scrollLeft == 0) || (dE.clientLeft || dE.clientLeft == 0))) return [e.clientX + (dE.scrollLeft || dB.scrollLeft || 0) - (dE.clientLeft || 0), e.clientY + (dE.scrollTop || dB.scrollTop || 0) - (dE.clientTop || 0)];
        }
        return null;
    }

    function mousedown(e) {
        if (!isOrbitControlEnabled) {
            var mxy = getCursorPosition(e);
            var box = document.getElementById("sel_box");
            mouseDownCoords.x = mxy[0];
            mouseDownCoords.y = mxy[1];
            box.orig_x = mxy[0];
            box.orig_y = mxy[1];
            box.style.left = mxy[0] + "px";
            box.style.top = mxy[1] + "px";
            box.style.display = "block";
            document.onmousemove = mousemove;
            document.onmouseup = mouseup;
            console.log("down x y: " + mouseDownCoords.x + " " + mouseDownCoords.y);
        }
    }

    function mousemove(e) {
        if (!isOrbitControlEnabled) {
            var mxy = getCursorPosition(e);
            var box = document.getElementById("sel_box");
            if (mxy[0] - box.orig_x < 0) {
                box.style.left = mxy[0] + "px";

            }
            if (mxy[1] - box.orig_y < 0) {
                box.style.top = mxy[1] + "px";
            }
            box.style.width = Math.abs(mxy[0] - box.orig_x) + "px";
            box.style.height = Math.abs(mxy[1] - box.orig_y) + "px";
        }
    }

    function mouseup(e) {
        if (!isOrbitControlEnabled) {
            var box = document.getElementById("sel_box");
            var mxy = getCursorPosition(e);
            box.style.display = "none";
            box.style.width = "0";
            box.style.height = "0";
            document.onmousemove = null;
            document.onmouseup = null;
    
            mouseUpCoords.x = mxy[0];
            mouseUpCoords.y = mxy[1];
            console.log("up x y: " + mouseUpCoords.x + " " + mouseUpCoords.y);
    
            const designPartContainer = document.querySelector('.design-part');
            const containerRect = designPartContainer.getBoundingClientRect();
    
            // Account for potential scroll offsets
            const scrollX = window.scrollX || window.pageXOffset;
            const scrollY = window.scrollY || window.pageYOffset;
    
            // Calculate the depth range based on the camera's near and far planes
            const near = camera.near;
            const far = camera.far;
    
            spheres.forEach((sphere) => {
                const spherePosition = sphere.position.clone();
                const screenPosition = spherePosition.clone().project(camera);
    
                // Convert screen position to DOM coordinates within the design-part class
                const domX = (screenPosition.x + 1) / 2 * containerRect.width + containerRect.left + scrollX;
                const domY = (-screenPosition.y + 1) / 2 * containerRect.height + containerRect.top + scrollY;
    
                // Check if the sphere is within the selection box and within the depth range
                const isInsideSelectionBox =
                    domX >= Math.min(mouseDownCoords.x, mouseUpCoords.x) &&
                    domX <= Math.max(mouseDownCoords.x, mouseUpCoords.x) &&
                    domY >= Math.min(mouseDownCoords.y, mouseUpCoords.y) &&
                    domY <= Math.max(mouseDownCoords.y, mouseUpCoords.y);
    
                const isWithinDepthRange = screenPosition.z >= 0 && screenPosition.z <= 1;
    
                if (isInsideSelectionBox && isWithinDepthRange) {
                    console.log("sphere x y: " + domX + " " + domY);
                    sphere.material.color.set(0xff0000); // Set to red color
                    selectedMeshes.push(sphere);
                }
            });
    
            // Center the camera on the selected area
            const centerX = (mouseDownCoords.x + mouseUpCoords.x) / 2;
            const centerY = (mouseDownCoords.y + mouseUpCoords.y) / 2;
    
            const centerScreenPosition = new THREE.Vector3(
                (centerX - containerRect.left - scrollX) / containerRect.width * 2 - 1,
                -(centerY - containerRect.top - scrollY) / containerRect.height * 2 + 1,
                0.5 // Adjust this value based on the depth range of your scene
            );
    
            const centerWorldPosition = centerScreenPosition.unproject(camera);
            camera.lookAt(centerWorldPosition);
        }
    }
    
    document.onmousedown = mousedown;

Box Selection, could be a good starting point! If it’s not the exact behavior you’re looking for, you can always customize it.

3 Likes

Thank you so much, it looks useful for my case. I’m gonna try it.

I worked on with this example. It is better but not enough for my case. I have the same problem here. I think, using raycaster is not work for this case. Any option except raycaster?

I finally solved the problem. I’ve changed camera type PerspectiveCamera() to OrthographicCamera(). After that, i can get all spheres that in the selected box area. And this is really helped me: three-mesh-bvh - Geometry Collect Triangles

1 Like