美文网首页
three.js - Raycaster and Mouse E

three.js - Raycaster and Mouse E

作者: 闪电西兰花 | 来源:发表于2024-05-05 18:21 被阅读0次
    • A raycaster can cast a ray in a specific direction and test what objects intersect with it
    • Set up
    <script setup>
      import * as THREE from 'three'
      import {OrbitControls} from 'three/addons/controls/OrbitControls.js'
    
      /**
       * scene
      */
      const scene = new THREE.Scene()
    
      /**
       * object
      */
      const object1 = new THREE.Mesh(
        new THREE.SphereGeometry(0.5, 16, 16),
        new THREE.MeshBasicMaterial({ color: '#ff0000' })
      )
      object1.position.x = - 2
    
      const object2 = new THREE.Mesh(
        new THREE.SphereGeometry(0.5, 16, 16),
        new THREE.MeshBasicMaterial({ color: '#ff0000' })
      )
    
      const object3 = new THREE.Mesh(
        new THREE.SphereGeometry(0.5, 16, 16),
        new THREE.MeshBasicMaterial({ color: '#ff0000' })
      )
      object3.position.x = 2
    
      scene.add(object1, object2, object3)
    
      /**
       * camera
      */
      const camera = new THREE.PerspectiveCamera(
        75,
        window.innerWidth / window.innerHeight,
        0.1,
        100
      )
      camera.position.z = 3
    
      /**
       * renderer
      */
      const renderer = new THREE.WebGLRenderer()
      renderer.setSize(window.innerWidth, window.innerHeight)
      document.body.appendChild(renderer.domElement)
    
      window.addEventListener('resize', () => {
        camera.aspect = window.innerWidth / window.innerHeight
        camera.updateProjectionMatrix
    
        renderer.setSize(window.innerWidth, window.innerHeight)
        renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
      })
    
      /**
       * axesHelper
      */
      const axesHelper = new THREE.AxesHelper(5)
      scene.add(axesHelper)
    
      /**
       * control
      */
      const controls = new OrbitControls(camera, renderer.domElement)
      controls.enableDamping = true
    
      /**
       * render
      */
      const clock = new THREE.Clock()
      const tick = () => {
        const elapsedTime = clock.getElapsedTime()
    
        // update
        controls.update()
        requestAnimationFrame(tick)
        renderer.render(scene, camera)
      }
      tick()
    </script>
    
    set up.png
    • Create the Raycaster
      • if we're shooting a ray in a direction, we need to tell the origin of the ray and the direction of the ray, we can use set() to set them
      • the direction has to be normalized
      /**
       * raycaster
      */
      const raycaster = new THREE.Raycaster()
    
      const rayOrigin = new THREE.Vector3(-3, 0, 0)
      const rayDirection = new THREE.Vector3(10, 0, 0)
      rayDirection.normalize()  // 单位向量,保持方向
    
      raycaster.set(rayOrigin, rayDirection)
    
    • Cast a Ray
      • intersectObject() to test one object, its value also be an array, because a ray can go through the same object multiple times
      • intersectObjects() to test an array of objects
      • each object value of the array has some useful information:
        1. distance - distance between the origin of the ray and the collision point
        2. face - the face of the geometry that was hit by the ray
        3. faceIndex - the index of the face
        4. object - what object is concerned by the collision
        5. point - a Vector3 of the exact position of the collision
        6. uv - the uv coordinates in that geometry
      const intersect = raycaster.intersectObject(object3)
      console.log(intersect);
    
      const intersects = raycaster.intersectObjects([object1, object2, object3])
      console.log(intersects);
    
    cast a ray.png
    • Test on each frame
      • we're going to animate the spheres and turn them blue when the ray intersects with them
      • remove the code we did previously and only keep the raycaster instantiation
      /**
       * raycaster
      */
      const raycaster = new THREE.Raycaster()
      
      • animate the spheres by using the elapsed time and Math.sin() in the tick()
      /**
       * render
      */
      const clock = new THREE.Clock()
      const tick = () => {
        const elapsedTime = clock.getElapsedTime()
      
        // animate objects
        object1.position.y = Math.sin(elapsedTime * 0.3) * 1.5
        object2.position.y = Math.sin(elapsedTime * 0.8) * 1.5
        object3.position.y = Math.sin(elapsedTime * 1.4) * 1.5
      
        // update
        ...
      }
      
      • update the caster in the tick()
      /**
       * render
      */
      const clock = new THREE.Clock()
      const tick = () => {
        const elapsedTime = clock.getElapsedTime()
      
        // animate objects
        ...
      
        // cast a ray
        const rayOrigin = new THREE.Vector3(-3, 0, 0)
        const rayDirection = new THREE.Vector3(1, 0, 0)
        rayDirection.normalize()
        raycaster.set(rayOrigin, rayDirection)
      
        // update
        ...
      }
      
      • cast a ray and color the objects
      /**
       * render
      */
      const clock = new THREE.Clock()
      const tick = () => {
        const elapsedTime = clock.getElapsedTime()
      
        // animate objects
        ...
      
        // cast a ray
        ...
      
        const objectsToTest = [object1, object2, object3]
        const intersects = raycaster.intersectObjects(objectsToTest)
        
        for(const object of objectsToTest) {
          object.material.color.set(0xff0000)
        }
        
        for(const intersect of intersects) {
          intersect.object.material.color.set('#0000ff')
        }
      
        // update
        ...
      }
      
    test on each frame.png
    • Use the raycaster with the mouse
      • hovering, we need the coordinates of the mouse, but not in pixel, we need a value goes from -1 to 1 in horizon and vertical axes
      /**
       * mouse
      */
      const mouse = new THREE.Vector2()
      
      window.addEventListener('mousemove', (event) => {
        mouse.x = (event.clientX / window.innerWidth) * 2 - 1
        mouse.y = - (event.clientY / window.innerHeight) * 2 + 1
      })
      
      • use the setFromCamera() method to orient the ray in the right direction
      /**
       * render
      */
      const clock = new THREE.Clock()
      const tick = () => {
        const elapsedTime = clock.getElapsedTime()
      
        // animate objects
        object1.position.y = Math.sin(elapsedTime * 0.3) * 1.5
        object2.position.y = Math.sin(elapsedTime * 0.8) * 1.5
        object3.position.y = Math.sin(elapsedTime * 1.4) * 1.5
      
        // cast a ray from the mouse
        raycaster.setFromCamera(mouse, camera)  // 根据camera的位置设置方向
      
        const objectsToTest = [object1, object2, object3]
        const intersects = raycaster.intersectObjects(objectsToTest)
      
        for(const object of objectsToTest) {
          object.material.color.set(0xff0000)
        }
        
        for(const intersect of intersects) {
          intersect.object.material.color.set('#0000ff')
        }
      
        // update
        controls.update()
        requestAnimationFrame(tick)
        renderer.render(scene, camera)
      }
      
      悬停后的sphere变成蓝色.png
      • mouse enter and mouse leave, we need to create a 'witness' variable containing the currently hovered object
      /**
       * render
      */
      const clock = new THREE.Clock()
      
      let currentIntersect = null
      
      const tick = () => {
        const elapsedTime = clock.getElapsedTime()
      
        // animate objects
        object1.position.y = Math.sin(elapsedTime * 0.3) * 1.5
        object2.position.y = Math.sin(elapsedTime * 0.8) * 1.5
        object3.position.y = Math.sin(elapsedTime * 1.4) * 1.5
      
        // cast a ray from the mouse
        raycaster.setFromCamera(mouse, camera)  // 根据camera的位置设置方向
      
        const objectsToTest = [object1, object2, object3]
        const intersects = raycaster.intersectObjects(objectsToTest)
      
        for(const object of objectsToTest) {
          object.material.color.set(0xff0000)
        }
        
        for(const intersect of intersects) {
          intersect.object.material.color.set('#0000ff')
        }
      
        // cast a ray from the mouse
        raycaster.setFromCamera(mouse, camera)
      
        if(intersects.length) {
          // console.log('enter');
          currentIntersect = intersects[0]
        }else {
          // console.log('leave');
          currentIntersect = null
        }
      
        // update
        controls.update()
        requestAnimationFrame(tick)
        renderer.render(scene, camera)
      }
      
      • mouse click event, we can test which sphere got clicked
      /**
       * mouse
      */
      ...
      ...
      window.addEventListener('click', () => {
        if(currentIntersect) {
          switch(currentIntersect.object) {
            case object1:
              console.log('click obj1');
              break
            case object2:
              console.log('click obj2');
              break
            case object3:
              console.log('click obj3');
              break
          }
        }
      })
      
    • Raycasting with models
      • load the model
      import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
      ...
      ...
      /**
       * gltfLoader
      */
      const gltfLoader = new GLTFLoader()
      let model = null
      gltfLoader.load('../public/models/Duck/glTF-Binary/Duck.glb', (gltf) => {
        model = gltf.scene
        model.position.y = - 1.2
        scene.add(model)
      })
      
      • lights, the model is MeshStandardMaterial, it needs lights to be seen
      /**
       * light
      */
      const ambientLight = new THREE.AmbientLight(0xffffff, 0.3)
      scene.add(ambientLight)
      
      const directionalLight = new THREE.DirectionalLight(0xffffff, 0.7)
      directionalLight.position.set(1, 2, 3)
      scene.add(directionalLight)
      
      model.png
      • intersect the model, we want the model to get larger when we hover it and when we leave it with mouse it will get back to the initial scale
      const tick = () => {
        ...
        ...
        // test intersect with a model
        if(model) {
          const modelIntersects = raycaster.intersectObject(model)
          if(modelIntersects.length) {
            model.scale.set(1.2, 1.2, 1.2)
          }else {
            model.scale.set(1, 1, 1)
          }
        }
        ...
      }
      
    raycasting with models.png

    相关文章

      网友评论

          本文标题:three.js - Raycaster and Mouse E

          本文链接:https://www.haomeiwen.com/subject/bubnndtx.html