首页 > 解决方案 > Three.js 中的 GLTF 模型和交互

问题描述

至少可以说我的js技能可以提高!但为此苦苦挣扎

我可以让我的模型正常加载到场景中,但似乎无法让交互正常工作。

就像我需要将 GLTF 文件绑定到 raycaster 中,下面的代码是其中的一部分。完整的 Codepen 链接在此代码下方。

class PickHelper {
constructor() {
  this.raycaster = new THREE.Raycaster();
  this.pickedObject = null;
  this.pickedObjectSavedColor = 0;
}
pick(normalizedPosition, scene, camera, time) {

  if (this.pickedObject) {
    this.pickedObject.material.emissive.setHex(this.pickedObjectSavedColor);
    this.pickedObject = undefined;
  }

  this.raycaster.setFromCamera(normalizedPosition, camera);

  const intersectedObjects = this.raycaster.intersectObjects(scene.children);
  if (intersectedObjects.length) {
    this.pickedObject = intersectedObjects[0].object;
    this.pickedObjectSavedColor = this.pickedObject.material.emissive.getHex();
    this.pickedObject.material.emissive.setHex((time * 8) % 2 > 1 ? 0xFFFF00 : 0xFF0000);
    this.pickedObject.rotation.y += 0.1 ;

  }
}

https://codepen.io/johneemac/pen/abzqdye << 完整代码

抱歉:尽管 CodePen 上的 gltf 文件存在跨源问题!它不会加载,但希望你能明白。

非常感谢任何帮助,谢谢!

标签: three.jsgltf

解决方案


目前尚不清楚您要做什么。GLTF 文件是材质、动画、几何图形、网格等的集合。因此您无法“挑选”GLTF 文件。您可以在里面“挑选”单个元素。您可以编写一些代码,如果选择了某些东西,则检查所选择的东西是 GLTF 场景中加载的网格之一,然后选择所有其他加载到 GLTF 场景中的东西。

任何状况之下,

您需要提供RayCaster可供选择的对象列表。在原始示例中,scene.children这只是添加到场景根目录的 Boxes 列表。但是在加载 GLTF 时,除非您已经知道 GLTF 的结构,因为您自己创建了场景,否则您需要找到您希望能够选择的内容并将它们添加到您可以传递给的某个列表中RayCaster.intersectObjects

此代码Mesh从加载的 GLTF 文件中获取所有对象

      let pickableMeshes = [];


      // this is run after loading the gLTT

          // get a list of all the meshes in the scene
          root.traverse((node) => {
            if (node instanceof THREE.Mesh) {
              pickableMeshes.push(node);
            }
          });

请注意,您也可以将true其作为第二个参数传递给RayCaster.intersectObjectsas in rayCaster.intersectObjects(scene.children, true)。这可能很少是您想要的,尽管您可能在场景中有不希望用户能够选择的东西。例如,如果您只希望用户能够选择汽车,那么类似

         // get a list of all the meshes in the scene who's names start with "car"
          root.traverse((node) => {
            if (node instanceof THREE.Mesh && (/^car/i).test(node.name)) {
              pickableMeshes.push(node);
            }
          });

然后,您使用的 PickHelper 类正在更改每个 Box 上的材质颜色,但这仅适用于每个 Box 都有自己的材质。如果 Boxes 共享材质,则更改材质颜色将更改所有框。

加载不同的 GLTF 大多数对象共享相同的材质,以便能够突出显示一个需要更改与该对象一起使用的材质或选择其他方法来突出显示选定的事物。

function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas});

  const fov = 60;
  const aspect = 2;  // the canvas default
  const near = 0.1;
  const far = 200;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.z = 30;

  const scene = new THREE.Scene();
  scene.background = new THREE.Color('white');

  // put the camera on a pole (parent it to an object)
  // so we can spin the pole to move the camera around the scene
  const cameraPole = new THREE.Object3D();
  scene.add(cameraPole);
  cameraPole.add(camera);

  {
    const color = 0xFFFFFF;
    const intensity = 1;
    const light = new THREE.DirectionalLight(color, intensity);
    light.position.set(-1, 2, 4);
    camera.add(light);
  }

  function frameArea(sizeToFitOnScreen, boxSize, boxCenter, camera) {
    const halfSizeToFitOnScreen = sizeToFitOnScreen * 0.5;
    const halfFovY = THREE.Math.degToRad(camera.fov * .5);
    const distance = halfSizeToFitOnScreen / Math.tan(halfFovY);
    // compute a unit vector that points in the direction the camera is now
    // in the xz plane from the center of the box
    const direction = (new THREE.Vector3())
        .subVectors(camera.position, boxCenter)
        .multiply(new THREE.Vector3(1, 0, 1))
        .normalize();

    // move the camera to a position distance units way from the center
    // in whatever direction the camera was from the center already
    camera.position.copy(direction.multiplyScalar(distance).add(boxCenter));

    // pick some near and far values for the frustum that
    // will contain the box.
    camera.near = boxSize / 100;
    camera.far = boxSize * 100;

    camera.updateProjectionMatrix();

    // point the camera to look at the center of the box
    camera.lookAt(boxCenter.x, boxCenter.y, boxCenter.z);
  }

  let pickableMeshes = [];

  {
    const gltfLoader = new THREE.GLTFLoader();
    gltfLoader.load('https://threejsfundamentals.org/threejs/resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf', (gltf) => {
      const root = gltf.scene;
      scene.add(root);

      // compute the box that contains all the stuff
      // from root and below
      const box = new THREE.Box3().setFromObject(root);

      const boxSize = box.getSize(new THREE.Vector3()).length();
      const boxCenter = box.getCenter(new THREE.Vector3());

      // set the camera to frame the box
      frameArea(boxSize * 0.7, boxSize, boxCenter, camera);
      
      // get a list of all the meshes in the scen
      root.traverse((node) => {
        if (node instanceof THREE.Mesh) {
          pickableMeshes.push(node);
        }
      });
    });
  }

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  class PickHelper {
    constructor() {
      this.raycaster = new THREE.Raycaster();
      this.pickedObject = null;
      this.pickedObjectSavedMaterial = null;
      this.selectMaterial = new THREE.MeshBasicMaterial();
      this.infoElem = document.querySelector('#info');
    }
    pick(normalizedPosition, scene, camera, time) {
      // restore the color if there is a picked object
      if (this.pickedObject) {
        this.pickedObject.material = this.pickedObjectSavedMaterial;
        this.pickedObject = undefined;
        this.infoElem.textContent = '';
      }

      // cast a ray through the frustum
      this.raycaster.setFromCamera(normalizedPosition, camera);
      // get the list of objects the ray intersected
      const intersectedObjects = this.raycaster.intersectObjects(pickableMeshes);
      if (intersectedObjects.length) {
        // pick the first object. It's the closest one
        this.pickedObject = intersectedObjects[0].object;
        // save its color
        this.pickedObjectSavedMaterial = this.pickedObject.material;
        this.pickedObject.material = this.selectMaterial;
        // flash select material color to flashing red/yellow
        this.selectMaterial.color.setHex((time * 8) % 2 > 1 ? 0xFFFF00 : 0xFF0000);
        this.infoElem.textContent = this.pickedObject.name;
      }
    }
  }

  const pickPosition = {x: 0, y: 0};
  const pickHelper = new PickHelper();
  clearPickPosition();

  function render(time) {
    time *= 0.001;  // convert to seconds;

    if (resizeRendererToDisplaySize(renderer)) {
      const canvas = renderer.domElement;
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();
    }

    cameraPole.rotation.y = time * .1;

    pickHelper.pick(pickPosition, scene, camera, time);

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);

  function getCanvasRelativePosition(event) {
    const rect = canvas.getBoundingClientRect();
    return {
      x: event.clientX - rect.left,
      y: event.clientY - rect.top,
    };
  }

  function setPickPosition(event) {
    const pos = getCanvasRelativePosition(event);
    pickPosition.x = (pos.x / canvas.clientWidth ) *  2 - 1;
    pickPosition.y = (pos.y / canvas.clientHeight) * -2 + 1;  // note we flip Y
  }

  function clearPickPosition() {
    // unlike the mouse which always has a position
    // if the user stops touching the screen we want
    // to stop picking. For now we just pick a value
    // unlikely to pick something
    pickPosition.x = -100000;
    pickPosition.y = -100000;
  }
  window.addEventListener('mousemove', setPickPosition);
  window.addEventListener('mouseout', clearPickPosition);
  window.addEventListener('mouseleave', clearPickPosition);

  window.addEventListener('touchstart', (event) => {
    // prevent the window from scrolling
    event.preventDefault();
    setPickPosition(event.touches[0]);
  }, {passive: false});

  window.addEventListener('touchmove', (event) => {
    setPickPosition(event.touches[0]);
  });

  window.addEventListener('touchend', clearPickPosition);
}

main();
body { margin: 0; }
#c { width: 100vw; height: 100vh; display: block; }
#info { position: absolute; left: 0; top: 0; background: black; color: white; padding: 0.5em; font-family: monospace; }
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r112/build/three.min.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r112/examples/js/loaders/GLTFLoader.js"></script>

<canvas id="c"></canvas>
<div id="info"></div>


推荐阅读