Use three in Vue JS create a 3D conference room drag and rotate

Posted by BizLab on Mon, 14 Feb 2022 10:21:40 +0100

Chapter III


Although the basic models have been created now, the main purpose of our 3D conference room is to place the items in the conference room and set the layout by ourselves. Therefore, we need to be able to drag the position and rotate the angle of the model in the conference room to achieve this highly free effect.

drag

DragControls

When it comes to drag effect, we must talk about three JS drag object DragControls, which is three JS provides convenient drag interaction
Explain the code directly

initDragControls(objects, info) {
      let that = this;
      // Initialize drag control
      this.dragControls = new DragControls(
        objects,
        this.camera,
        this.renderer.domElement
      );
      this.dragControls.transformGroup = true;
      // Start dragging
      this.dragControls.addEventListener("dragstart", this.setDragstart);
      this.dragControls.addEventListener("drag", function (e) {
        if (e.object.position.y < -60) {
           e.object.position.y = -60;
         }
      });
      // Drag end
      this.dragControls.addEventListener("dragend", this.setDragend);
    },
	// Named drag start function
    setDragstart() {
      this.rotateBtnShow = false;
      this.editWellObject = false;
      this.controls.enabled = false;
    },
    // Named drag end function
    setDragend() {
      this.controls.enabled = true;
    },

First, don't forget to import {dragcontrols} from "three / examples / JSM / controls / dragcontrols";
After registering the DragControls object, five events are provided for us
dragstart: triggered when the user starts dragging 3D Objects;
drag: triggered when the user drags 3D Objects;
Drawn: triggered when the user starts to complete 3D Objects;
hoveron: triggered when the pointer moves to a 3D Object or one of its children;
hoveroff: triggered when the pointer moves out of a 3D Object. Dran
It should be noted that when we register events, we can use named functions if we can, because this can remove the registered events when the page is destroyed and optimize the performance.
In this project, in order to achieve the gravity effect, that is, objects such as tables and chairs cannot leave the ground when dragging, so I added the restriction on the y-axis of the current operation model in the drag to achieve the following effects:

For multiple 3D models, we need to register multiple DragControls objects, because each drag object will correspond to the corresponding model. If only one DragControls object is registered, no matter which model is moved, it will only be the scene of the first model moving

rotate

Because three JS does not directly provide the object to rotate the 3D model, so we divided it into two parts in order to achieve the effect of rotation

Get the 3D model selected by the mouse

To select an object, we mainly rely on Raycaster, which is mainly used for mouse picking, that is, to calculate which objects the mouse touches when moving in three-dimensional space. The principle is that a ray will be emitted at the position pointed by the mouse, a vertical mouse, a ray perpendicular to the computer screen, three JS will get the objects that the ray passes through in turn.
In fact, we can understand this ray in another way, such as browser developer mode, as shown in the following figure

Click the element button in the page below to view the element code

getIntersects(event) {
      event.preventDefault();

      let raycaster = new THREE.Raycaster();
      let mouse = new THREE.Vector2();

      mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

      //The ray position is calculated through the mouse click position (two-dimensional coordinates) and the matrix of the current camera
      raycaster.setFromCamera(mouse, this.camera);

      // Gets the array of objects intersecting with the ray, in which the elements are sorted according to the distance, and the closer they are, the higher they are
      let moudle = this.scene.children.filter((item) => {
        if (item.type === "Group") {
          return item;
        }
      });
      let intersects = raycaster.intersectObjects(moudle, true);
      if (intersects.length > 0) {
        let object = intersects[0].object;
        this.getGroup(object);
        return this.selectGroup;
      }
      return {};
    },
    // Get group
    getGroup(intersects) {
      let selectGroup = intersects.parent;
      if (selectGroup.type === "Group") {
        return (this.selectGroup = selectGroup);
      } else {
        selectGroup = selectGroup.parent;
        this.getGroup(selectGroup);
      }
    },

First, register the Raycaster object, obtain the coordinates of the mouse, and calculate the position of the ray according to the coordinates and the current camera (because the angle of view of the camera is also a very important factor) The intersectobjects () method obtains the 3D object intersecting with the ray, and then obtains its 3D group according to the 3D object. Because the externally loaded 3D model usually appears in the form of group, the ray may only touch an object of the group.

// Event triggered by mouse click
    mouseClick(event) {
      let intersects = this.getIntersects(event);
      if (intersects !== {} && intersects.type === "Group") {
        this.selectObject = intersects;
        if (this.selectObject.name === "Rotatable") {
          this.rotateBtnShow = true;
          this.addRotateClick(this.selectObject);
        } else {
          this.editWellObject = true;
        }
      } else {
        this.rotateBtnShow = false;
        this.selectObject = null;
        this.editWellObject = false;
      }
    },

Obviously, we need to register the mouse click event to obtain the group obtained after the trigger ray intersects with the 3D object. The mouse registration event is placed in mounted. I won't mention it specifically here. Here, we should also pay attention to one point. Because the mouse is registered globally, when clicking blank, it may cause an error because it can't obtain the relevant object, Moreover, not all 3D models need rotation effects. For example, walls and floors are not supported, so we need to add a filter condition, that is, intersects Type = = = "group" the rotation effect is triggered only when the obtained result is a group; (unregistered methods in the code will be found later in the code)

Add rotation button and rotation method

After obtaining the selected object, we will give it a rotatable method. In order to distinguish whether the current object is rotatable and highlight the currently selected object, first create a rotation button.

createRotateBtn() {
     let halWidth = window.innerWidth / 2;
     let halHeight = window.innerHeight / 2;
     let vector = this.selectObject.position.clone().project(this.camera);
     this.$refs.rotateBtn.style.left = `${
       halWidth + vector.x * halWidth - 40
     }px`;
     this.$refs.rotateBtn.style.top = `${
       -vector.y * halHeight + halHeight - this.selectObject.position.y - 230
     }px`;
   },

In fact, the position of the rotation button is mainly determined by the positioning of Css, so we need to calculate the left and top of the rotation button through the coordinates of the selected 3D model

   addRotateClick() {
     if (this.addRotate) {
       this.addRotate = false;
       let that = this;
       let newX = Number(this.$refs.rotateBtn.style.left.replace("px", "")); // Data format conversion
       document.addEventListener("dragenter ", function (e) {
         e.preventDefault();
       });
       document.addEventListener("dragover", function (e) {
         e.preventDefault();
       });
       // Rotation event
       this.$refs.rotateBtn.addEventListener(
         "drag",
         function (event) {
           if (event.x < newX) {
             that.selectObject.rotateY(Math.PI / 120);
           } else if (event.x > newX) {
             that.selectObject.rotateY(Math.PI / -120);
           }
           newX = event.x;
         },
         true
       );
     }

If the rotation event is registered all the time, it will not only consume memory, but also make the rotation speed faster and faster. In order to avoid the rotation event being registered all the time, set addRotate as a switch. For the rotation event, I mainly assign it to the rotation button, and affect the rotation angle of the 3D model by dragging the rotation button.
Based on the coordinates of the current button, if you drag the button to the left, the corresponding 3D model will rotate counterclockwise. The rotation amplitude can be set by yourself. Use SelectObject The rotation (math. Pi / 120) method achieves the rotation of the selected object. Similarly, drag the button to the right and the corresponding 3D model rotates clockwise.
Upper effect:

Tips

When dragging, it may stop dragging because the mouse moves to the rotation button. Therefore, in the previous dragging code, the rotation button is hidden in the callback function that starts dragging.
Because three JS will count all objects that the ray emitted by the mouse passes through, so when dragging the object, the camera angle may also be dragged, so the track controller should also be disabled in the callback function to start dragging to prevent the camera from being rotated.

Topics: Javascript Three.js Vue Vue.js 3d