Search code examples
three.jsscaletweentweenjs

ThreeJS - How do I scale down intersected object on "mouseout"


I have n+1 hexshapes in a honeycomb grid. The objects are stacked close together. With this code:

// Get intersected objects, a.k.a objects "hit" by mouse, a.k.a objects that are mouse-overed
const intersects = raycaster.intersectObjects(hexObjects);

// If there is one (or more) intersections
let scaleTween = null;
if (intersects.length > 0) {
  // If mouse is not currently over an object

  // Set cursor to pointer so that the user can see that the object is clickable
  document.body.style.cursor = 'pointer';

  // Get the last intersected object, it's most likely that object we are currently hovering
  const is = intersects.length > 0 ? intersects.length - 1 : 0;

  // Is the object hovered over for the first time?
  if (INTERSECTED === null) {
    // Save current hovered object
    INTERSECTED = intersects[is].object;
    // HIGHLIGHT
    // Save current color
    INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
    // Set highlight color
    INTERSECTED.material.color.setHex(COLOR_HIGHLIGHT);

    // SCALE UP
    // Try to stop the current tween, if any, in progress, so we can proceed with the next, if any, tween
    try {
      scaleTween.stop();
    } catch (e) {}

    // Create tween, save it so we can try to stop it, if needed
    scaleTween = scale_tween(
      INTERSECTED,
      INTERSECTED.scale.clone(),
      {
        x: 1.5,
        y: 1.5
      },
      100
    );
    scaleTween.start();

    // SET Z-INDEX
    INTERSECTED.position.z = 10;
  } else {
    // If the mouse is over an object

    // Do we have a previous hovered item?
    if (INTERSECTED !== null) {
      // Revert color
      INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
      // SCALE DOWN
      // Try to stop the current tween, if any, in progress, so we can proceed with the next, if any, tween
      try {
        scaleTween.stop();
      } catch (e) {}

      // Create tween, save it so we can try to stop it, if needed
      scaleTween = scale_tween(
        INTERSECTED,
        INTERSECTED.scale.clone(),
        {
          x: 1,
          y: 1
        },
        100
      );
      scaleTween.start();

      // REVERT Z-INDEX
      INTERSECTED.position.z = 1;
    }

    // Save current intersected object
    INTERSECTED = intersects[is].object;

    // HIGHLIGHT
    // Save current color
    INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
    // Set highlight color
    INTERSECTED.material.color.setHex(COLOR_HIGHLIGHT);

    // SCALE UP
    // Try to stop the current tween, if any, in progress, so we can proceed with the next, if any, tween
    try {
      scaleTween.stop();
    } catch (e) {}

    // Create tween, save it so we can try to stop it, if needed
    scaleTween = scale_tween(
      INTERSECTED,
      INTERSECTED.scale.clone(),
      {
        x: 1.5,
        y: 1.5
      },
      100
    );
    scaleTween.start();

    // SET Z-INDEX
    INTERSECTED.position.z = 10;
  }
} else {
  // If there are no intersections

  // Reset cursor
  document.body.style.cursor = 'default';

  // Restore previous intersection object (if it exists) to its original color
  if (INTERSECTED !== null) {
    // REVERT COLOR
    INTERSECTED.material.color.setHex(INTERSECTED.currentHex);

    // SCALE DOWN
    // Try to stop the current tween, if any, in progress, so we can proceed with the next, if any, tween
    try {
      scaleTween.stop();
    } catch (e) {}

    // Create tween, save it so we can try to stop it, if needed
    scaleTween = scale_tween(
      INTERSECTED,
      INTERSECTED.scale.clone(),
      {
        x: 1,
        y: 1
      },
      100
    );
    scaleTween.start();

    // REVERT "Z-INDEX"
    INTERSECTED.position.z = 1;
  }

  // Remove previous intersection object reference  by setting current intersection object to "nothing"
  INTERSECTED = null;
}

I've managed to highlight the object and scale it up with a tween quite nicely, but when I move the mouse out of the object onto the next object (the scaled object is scaled over the next object a bit), the highlight is gone, but the scale persists. How do I manage to scale the object down? And preferably with a tween?

A pen for this code can be found here: https://codepen.io/phun-ky/pen/erBZZy, the relevant part is at about line 1284 or search for INTERSECTED.


Solution

  • I wrote my own one. It's hell imperfect, but, at least, it scales up and down the hexagons:

    var scene = new THREE.Scene();
    var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
    camera.position.set(0, 0, 10);
    var renderer = new THREE.WebGLRenderer({
      antialias: true
    });
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setClearColor(0x101010);
    document.body.appendChild(renderer.domElement);
    
    var hexes = [];
    
    var colCount = 5;
    var rowCount = 4;
    var hexDiameter = 3;
    
    var xStart = -(colCount) * hexDiameter * 0.5;
    var rowSpace = Math.sqrt(3) * hexDiameter * 0.5;
    var yStart = (rowCount - 1) * rowSpace * 0.5;
    var hexGeom = new THREE.CylinderGeometry(hexDiameter * 0.5, hexDiameter * 0.5, 0.0625, 6, 1);
    hexGeom.rotateX(Math.PI * 0.5);
    for (let j = 0; j < rowCount; j++) {
      for (let i = 0; i < colCount + (j % 2 === 0 ? 0 : 1); i++) {
        let hex = new THREE.Mesh(hexGeom, new THREE.MeshBasicMaterial({
          color: Math.random() * 0x7e7e7e + 0x7e7e7e,
          wireframe: false
        }));
        hex.position.set(xStart + i * hexDiameter + (j % 2 === 0 ? 0.5 * hexDiameter : 0), yStart - j * rowSpace, 0);
        hex.userData.scaleUp = function(h) {
          if (h.userData.scaleDownTween) h.userData.scaleDownTween.stop();
          let initScale = h.scale.clone();
          let finalScale = new THREE.Vector3().setScalar(2);
          h.userData.scaleUpTween = new TWEEN.Tween(initScale).to(finalScale, 500).onUpdate(function(obj) {
            h.scale.copy(obj)
          }).start();
        }
        hex.userData.scaleDown = function(h) {
          if (h.userData.scaleUpTween) h.userData.scaleUpTween.stop();
          let initScale = h.scale.clone();
          let finalScale = new THREE.Vector3().setScalar(1);
          h.userData.scaleUpTween = new TWEEN.Tween(initScale).to(finalScale, 500).onUpdate(function(obj) {
            h.scale.copy(obj)
          }).start();
        }
        scene.add(hex);
        hexes.push(hex);
      }
    }
    
    window.addEventListener("mousemove", onMouseMove, false);
    
    var raycaster = new THREE.Raycaster();
    var mouse = new THREE.Vector2();
    var intersects = [];
    var intersected;
    
    function onMouseMove(event) {
      mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
      raycaster.setFromCamera(mouse, camera);
      intersects = raycaster.intersectObjects(hexes);
      if (intersects.length > 0) {
        if (intersected != intersects[0].object) {
          if (intersected) intersected.userData.scaleDown(intersected);
          intersected = intersects[0].object;
          intersected.userData.scaleUp(intersected);
        }
      } else {
        if (intersected) intersected.userData.scaleDown(intersected);
        intersected = null;
      }
    }
    
    render();
    
    function render() {
      requestAnimationFrame(render);
      TWEEN.update();
      renderer.render(scene, camera);
    }
    body {
      overflow: hidden;
      margin: 0;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/17.2.0/Tween.min.js"></script>