Search code examples
three.jsdraggabledragreact-three-fiberreact-three-drei

Three.js drag a model on x and z axis. React three fiber


I am trying to make models draggable in three.js. I want my model to follow my mouse when I move it. This is what I am trying to accomplish. I am using react-three-fiber and @use-gesture/react

What I am trying to accomplish

Here is how my program looks

My program

The difference is quite noticeable. On the good example, the model follows the mouse wherever it goes. On my program, that is not the case.

Here is my code for the cube

const BasicOrangeBox = ({setControlsDisabled, startPosition} : BasicOrangeBoxType) => {
    const { camera } = useThree();
    const [boxPosition, setBoxPosition] = useState(startPosition)    

    const bind = useGesture({
        onDrag: ({movement: [x, y]}) => {
            setControlsDisabled(true);
            setBoxPosition( (prev) => {
                const newObj = {...prev};
                newObj.x = newObj.x0 + (x/100);
                newObj.z = newObj.z0 + (y/100);
                return newObj;
            } )
        },
        onDragEnd: () => {
            setControlsDisabled(false);
            setBoxPosition( (prev) => {
                const newObj = {...prev};
                newObj.x0 = newObj.x;
                newObj.z0 = newObj.z;
                return newObj;
            } )
        }
    })

    return (
        <mesh 
            {...bind() }
            position={[boxPosition.x, boxPosition.y, boxPosition.z]}
        >
            <boxGeometry />
            <meshBasicMaterial color={"orange"} />
        </mesh>
    )
}


Solution

  • Here is how I made a mesh cube draggable only on x and z axis like in a video.

    Needed packages:

    • three
    • react-three/fiber
    • use-gesture/react

    First, I created a plane that spanned across my whole viewport and assigned it to a useRef

    <mesh rotation={[MathUtils.degToRad(90), 0, 0]} ref={planeRef} position={[0, -0.01, 0]}>
                <planeGeometry args={[innerWidth, innerHeight]} />
                <meshBasicMaterial color={0xfffffff} side={DoubleSide} />
    </mesh>
    

    Then I added that ref to a useContext so I can use it in different components.

    Next, I imported raycaster from useThree hook and planeRef from aforementioned useContext.

    Then I used useGesture onDrag and onDragEnd to enable and disable my OrbitControls

    Inside the onDrag, I used raycaster's intersectsObject method and added an array of only one element, my plane, as a parameter. This gave me x, y, z coordinates where my mouse intersects with the plane. (Y is always 0)

    Then I updated my box position.

    Here is the full code snippet

    const BasicOrangeBox = ({setControlsDisabled, startPosition} : BasicRedBoxType) => {
        const { raycaster } = useThree();
        const [boxPosition, setBoxPosition] = useState(startPosition);
        const planeRef = useContext(PlaneContext);    
    
        const bind = useGesture({
            onDrag: () => {
                const intersects = raycaster.intersectObjects([planeRef]);
                if (intersects.length > 0){
                    const intersection = intersects[0];
                    console.log(intersection.point.x);
                    setBoxPosition({
                        x: intersection.point.x,
                        y: intersection.point.y,
                        z: intersection.point.z,
                    })
                }
                setControlsDisabled(true);
            },
            onDragEnd: () => {
                setControlsDisabled(false);
            }
        })
    
        return ( //@ts-ignore Ignores type error on next line 
            <mesh 
                {...bind() }
                position={[boxPosition.x, boxPosition.y, boxPosition.z]}
            >
                <boxGeometry />
                <meshBasicMaterial color={"orange"} />
            </mesh>
        )
    }