skip to Main Content

I have the following code which works great to “hover” objects, however it is possible for multiple objects to be hovered at once if their meshes overlap at the point of the mouse. I understand that the intersections array is sorted such that the first element is the closest, but I can’t figure out how to use this to my advantage given the following code. Any help is appreciated.

Live demo with code: https://playcode.io/1436752

import * as THREE from 'three';
import { OrbitControls } from 'three-controls';

// ----------------------- SCENE
const scene = new THREE.Scene();

// ----------------------- CAMERA
const camera = new THREE.PerspectiveCamera(
  60,
  window.innerWidth / window.innerHeight,
  0.1,
  10000
);
camera.position.set(0, 0, 5);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xfff000);
document.body.appendChild(renderer.domElement);
renderer.setPixelRatio(window.devicePixelRatio);

// ----------------------- CONTROLS
const controls = new OrbitControls(camera, renderer.domElement);
controls.addEventListener('change', render);

// ----------------------- RAYCASTING
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();

var raycastLayer = [];
let hovered = {};
let intersects = [];

window.addEventListener('pointermove', e => {
  pointer.set(
    (e.clientX / window.innerWidth) * 2 - 1,
    -(e.clientY / window.innerHeight) * 2 + 1
  );
  raycaster.setFromCamera(pointer, camera);
  intersects = raycaster.intersectObjects(raycastLayer, true);


  // if a previously hovered item is not among the hits we must call onPointerOut
  Object.keys(hovered).forEach(key => {
    const hit = intersects.find(hit => hit.object.uuid === key);

    if (hit === undefined) {
      const hoveredItem = hovered[key];
      if (hoveredItem.object.onPointerOut)
        hoveredItem.object.onPointerOut(hoveredItem);
      delete hovered[key];
    }
  });

  if (intersects.length > 0) {
    [intersects[0]].forEach(hit => {
      // if a hit has not been flagged as hovered we must call onPointerOver
      if (!hovered[hit.object.uuid]) {
        hovered[hit.object.uuid] = hit;
        if (hit.object.onPointerOver) hit.object.onPointerOver(hit);
      }

      // call onPointerMove
      if (hit.object.onPointerMove) hit.object.onPointerMove(hit);
    });
  }

  render();
});

window.addEventListener('click', e => {
  if (e.target == renderer.domElement) {
    // update the picking ray with the camera and pointer position
    raycaster.setFromCamera(pointer, camera);
    const intersects = raycaster.intersectObjects(raycastLayer, true);

    intersects.forEach(hit => {
      if (hit.object.onClick) {
        hit.object.onClick(hit);
      }
    });
  }
});

// ----------------------- LIGHTING
const ambLight = new THREE.AmbientLight(0xffffff, 0.7); // soft white light
scene.add(ambLight);
scene.background = new THREE.Color(0xffff00);

// ----------------------- GLYPHS
const activeGlyphScale = 1.25;

class Glyph extends THREE.Mesh {
  constructor() {
    super();

    this.position.set(
      Math.random() * 5 - 2.5,
      Math.random() * 5 - 2.5,
      Math.random() * 5 - 2.5
    );

    this.isActive = false;

    let randCol = new THREE.Color(0xffffff);
    randCol.setHex(Math.random() * 0xffffff);

    this.geometry = new THREE.PlaneGeometry(1, 1);

    this.material = new THREE.MeshStandardMaterial({
      side: 2,
      color: randCol,
      emissiveIntensity: 1,
    });

    this.scale.setScalar(1);
  }

  onPointerOver() {
    this.scale.setScalar(1.5);
  }

  onPointerOut() {
    this.scale.setScalar(1);
  }

  onClick(e) {}
}
let glyphs = [];
for (let i = 0; i < 15; i++) {
  glyphs[i] = new Glyph();
  scene.add(glyphs[i]);
  raycastLayer.push(glyphs[i]);
}

// ----------------------- RENDER
render();
function render() {
  renderer.render(scene, camera);
}

animate();
function animate() {
  requestAnimationFrame(animate);
}

2

Answers


  1. As you said it yourself, the objects are sorted based on the intersection point distance from ray origin. Just pick the one that is at the index zero.

    Login or Signup to reply.
  2. I using somethink like this to pick first object. If you want intersect only some group of objects you can use layers for this.

    const raycaster = new THREE.Raycaster()
    raycaster.layers.set(layer)
    raycaster.setFromCamera(screenPosition, this.camera);
    const intersectedObjects = raycaster.intersectObjects([mainScene], true);
    if (intersectedObjects.length) {
      /* return object or apply hover effect */
      return intersectedObjects[0]
    } else {
      /* nothing change or clear hover effect on all objects */
      return null
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search