0

I'm building a 3D configurator for sofa modules and had a question about my snapping function.

In the scene, I drag models around to place them using use-gesture's useDrag hook, and when they approach they snap onto eachother.

However: They seem to love eachother a bit to much, so it's getting hard to get them off while dragging. Any idea how I can fix this?

I already tried delaying the snap function on "first" so you have a second to pull it off, but that glitched.

import { useAppStore } from "@/stores/appStore";
import { useDrag } from "@use-gesture/react";
import { Box3, Vector3 } from "three";

const useModelDrag = (id) => {
 //all variable declarations

 /************************ Drag function ************************/

 const bind = useDrag(({ last, first, event }) => {
   if (first) {
     update({ dragging: true });
   }
   if (last) {
     update({ dragging: false });
   }
   event.ray.intersectPlane(floorPlane, planeIntersectPoint);

   draggingModule(id, planeIntersectPoint, last);
 });

 /************************ Actions while dragging (snapping) ************************/

 const draggingModule = (id, dragPosition, last) => {

   if (dragging === false) return;

   const currentModule = active[id];
   currentBox.setFromObject(currentModule.ref);
   currentBox.getCenter(currentCenter);
   currentBox.getSize(currentSize);

   if (
     dragPosition.x < room.width / 2 - currentSize.x / 2 + 10 &&
     dragPosition.x > -room.width / 2 + currentSize.x / 2 - 10 &&
     dragPosition.z < room.depth / 2 - currentSize.z / 2 + 10 &&
     dragPosition.z > -room.depth / 2 + currentSize.z / 2 - 10
   )
     currentModule.ref.position.set(dragPosition.x, dragPosition.y, dragPosition.z);

   // start comparison when more than two models

   if (active.length >= 2) {
     const distances = [];

     for (let i = 0; i < active.length; i++) {
       if (i === id) continue;
       const module = active[i];

       moduleBox.setFromObject(module.ref);

       const modelDistance = moduleBox.distanceToPoint(currentCenter);
       distances.push({ modelDistance, id: i });
     }

     distances.sort((a, b) => {
       return a.modelDistance - b.modelDistance;
     });

     // calculating the closest module

     const smallestDistance = distances[0];
     const closestModule = active[smallestDistance.id];

     if (closestModule === undefined) return;
     if (smallestDistance.modelDistance > 80) return;

     closestBox.setFromObject(closestModule.ref);
     closestBox.getCenter(closestCenter);
     closestBox.getSize(closestSize);

     if (!closestBox) return;

     if (currentBottom.z !== currentBox.max.z) { //extra check to not execute unnecessary code
       closestRight.x = closestBox.max.x;
       closestLeft.x = closestBox.min.x;
       closestTop.z = closestBox.min.z;
       closestBottom.z = closestBox.max.z;
       currentRight.x = currentBox.max.x;
       currentLeft.x = currentBox.min.x;
       currentTop.z = currentBox.min.z;
       currentBottom.z = currentBox.max.z;
     }

     sideDistances = [
       { name: "right", dist: closestRight.distanceTo(currentLeft) },
       { name: "left", dist: closestLeft.distanceTo(currentRight) },
       { name: "top", dist: closestTop.distanceTo(currentBottom) },
       { name: "bottom", dist: closestBottom.distanceTo(currentTop) },
     ];

     sideDistances.sort((a, b) => {
       return a.dist - b.dist;
     });

     const closestSide = sideDistances[0];

     // snapping conditions based on model sides
     switch (closestSide.name) {
       case "left":
         currentModule.ref.position.x = closestBox.min.x - currentSize.x / 2 + 11;
         break;
       case "right":
         currentModule.ref.position.x = closestBox.max.x + currentSize.x / 2 - 11;
         break;
       case "top":
         currentModule.ref.position.z = closestBox.min.z - currentSize.z / 2 + 11;
         break;
       case "bottom":
         currentModule.ref.position.z = closestBox.max.z + currentSize.z / 2 - 11;
         break;
       default:
         break;
     }

     // extra check to snap to corner after dragging
     if (last) {
       const closestModuleSnapPoints = closestModule.ref.children[0].children;
       const currentModuleSnapPoints = currentModule.ref.children[0].children;

       for (let i = 0; i < currentModuleSnapPoints.length; i++) {
         for (let j = 0; j < closestModuleSnapPoints.length; j++) {
           currentModuleSnapPoints[i].getWorldPosition(positionA);
           closestModuleSnapPoints[j].getWorldPosition(positionB);

           const pDist = positionA.distanceTo(positionB);

           pointDistances.push({ pDist, pointAIndex: i, pointBIndex: j });
         }
       }
       pointDistances.sort((a, b) => {
         return a.pDist - b.pDist;
       });

       const closestPair = pointDistances[0];

       // conditions for snapping to model corners
       if (closestPair.pDist > 15) return;
      
       switch (true) {
         case (closestPair.pointBIndex === 0 && closestPair.pointAIndex === 3) ||
           (closestPair.pointBIndex === 3 && closestPair.pointAIndex === 0):
           currentModule.ref.position.z = closestBox.min.z + currentSize.z / 2;
           break;
         case (closestPair.pointBIndex === 1 && closestPair.pointAIndex === 2) ||
           (closestPair.pointBIndex === 2 && closestPair.pointAIndex === 1):
           currentModule.ref.position.z = closestBox.max.z - currentSize.z / 2;
           break;
         case (closestPair.pointBIndex === 3 && closestPair.pointAIndex === 2) ||
           (closestPair.pointBIndex === 2 && closestPair.pointAIndex === 3):
           currentModule.ref.position.x = closestBox.min.x + currentSize.x / 2;
           break;
         case (closestPair.pointBIndex === 1 && closestPair.pointAIndex === 0) ||
           (closestPair.pointBIndex === 0 && closestPair.pointAIndex === 1):
           currentModule.ref.position.x = closestBox.max.x - currentSize.x / 2;
           break;
         default:
           break;
       }
     }
   }
 };
 return [bind];
};

export default useModelDrag;

The snappoints are configured like this:

      const useSnapPoints = (id, ref, position = [0, 0, 0]) => {
  let [snapPoints, setSnappoints] = useState([]);
  const initiateModule = useAppStore((state) => state.initiateModule);
  const activeModules = useAppStore((state) => state.activeModules);
  const initialRun = useRef();

  useEffect(() => {
    if (ref.current) {
      initiateModule(id, ref.current);

      const modelBox = new Box3().setFromObject(ref.current.children[1]);
      const wp = new Vector3();
      ref.current.getWorldPosition(wp);

      snapPoints = [
        {
          position: [modelBox.max.x - wp.x, 10, modelBox.min.z - wp.z],
          class: "back",
          // color: "orange",
        },
        {
          position: [modelBox.max.x - wp.x, 10, modelBox.max.z - wp.z],
          class: "front",
          // color: "red",
        },
        {
          position: [modelBox.min.x - wp.x, 10, modelBox.max.z - wp.z],
          class: "front",
          // color: "purple",
        },
        {
          position: [modelBox.min.x - wp.x, 10, modelBox.min.z - wp.z],
          class: "back",
          // color: "green",
        },
      ];

      if (!initialRun?.current) {
        ref.current.position.set(position[0], position[1], position[2]);
        initialRun.current = { ran: true };
      }
      setSnappoints(snapPoints);
    }
  }, [ref.current, activeModules[id].rotationIndex]);
  return [snapPoints];
};

export default useSnapPoints;


isherwood
  • 58,414
  • 16
  • 114
  • 157
Rix11
  • 23
  • 4

0 Answers0