GLTF модель и взаимодействие в три. js - PullRequest
2 голосов
/ 07 января 2020

Мои js навыки могут быть улучшены, если не сказать больше! Но, борясь с этим

, я могу заставить свою модель нормально загружаться в сцену, но не могу заставить работать взаимодействие.

Это как будто мне нужно t ie в файле 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 << FULL Code </p>

Извините: проблема перекрестного происхождения с файлом gltf на CodePen, хотя! Он не будет загружаться, но, надеюсь, вы поняли идею.

Супер признателен за любую помощь, спасибо!

Ответы [ 2 ]

2 голосов
/ 07 января 2020

Вы должны выполнить тест пересечения следующим образом:

const intersectedObjects = this.raycaster.intersectObjects(scene.children, true);

Обратите внимание на второй аргумент intersectObjects(). Это указывает на то, что raycaster должен обработать всю иерархию объектов, которая необходима в контексте загруженного glTF ресурса.

three.js R112

1 голос
/ 07 января 2020

Непонятно, что вы пытаетесь сделать. GLTF-файлы - это набор материалов, анимаций, геометрий, сеток и т. Д. c .. поэтому вы не можете «выбрать» GLTF-файл. Вы можете «выбрать» отдельные элементы внутри. Вы могли бы написать некоторый код, который, если что-то будет выбрано, проверяет, что выбранное является одной из сеток, загруженных в сцену GLTF, а затем выбирает любую другую вещь, которая была загружена в сцене GLTF.

В любом case,

Вам необходимо предоставить RayCaster список объектов для выбора. В исходном примере это был scene.children, который является просто списком ящиков, добавленных к root сцены. Но при загрузке GLTF, если вы уже не знаете структуру GLTF, потому что вы сами создали сцену, вам нужно go найти вещи, которые вы хотите выбрать, и добавить их в некоторый список, который вы можете передать 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.intersectObjects как в 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, который вы использовали, менял цвет материала на каждой коробке, но это работало только потому, что каждая коробка имеет свой материал. Если ящики разделяют материалы, то изменение цвета материала изменит все ящики.

При загрузке другого 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>
...