javascripthtmlthree.jsprojector

ThreeJS Highlight/Projector


How would I go about adding a 2d sprite under a 3d object on top of a plane to show that my object(s) are highlighted/selected?

Also, how would I do this on uneven terrain?

I'm attaching a sample image to better explain my question:

enter image description here


Solution

  • I'm not sure that using a sprite is a good idea.

    As you said about uneven terrain, it's better to use something not flat. For example, a sphere or a cylinder with a material with .alphaMap.

    We need something ring-like for our effect of selection.

    enter image description here

    Let's suppose, you chose sphere, then you can set its material's alphaMap from a file or a dynamically created texture from canvas:

    // alpha texture
    var canvas = document.createElement("canvas");
    canvas.width = 128;
    canvas.height = 128;
    var ctx = canvas.getContext("2d");
    var gradient = ctx.createLinearGradient(0, 0, 0, 128);
    gradient.addColorStop(0.35, "black");
    gradient.addColorStop(0.475, "white");
    gradient.addColorStop(0.525, "white");
    gradient.addColorStop(0.65, "black");
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, 128, 128);
    var alphaTexture = new THREE.Texture(canvas);
    alphaTexture.needsUpdate = true;
    

    About .alphaMap:

    The alpha map is a grayscale texture that controls the opacity across the surface (black: fully transparent; white: fully opaque). Default is null. Only the color of the texture is used, ignoring the alpha channel if one exists. For RGB and RGBA textures, the WebGL renderer will use the green channel when sampling this texture due to the extra bit of precision provided for green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and luminance/alpha textures will also still work as expected.

    That's why we have there black and white only.

    Let's create an object of the simpliest NPC with a yellow ring which indicating that our NPC is selected:

    var npc = function() {
      var geom = new THREE.SphereGeometry(4, 4, 2);
      geom.translate(0, 4, 0);
      var mesh = new THREE.Mesh(geom, new THREE.MeshLambertMaterial({
        color: Math.random() * 0xffffff
      }));
    
      // highlighter
      geom.computeBoundingSphere();
      var sphereGeom = new THREE.SphereGeometry(geom.boundingSphere.radius, 32, 24);
      var sphereMat = new THREE.MeshBasicMaterial({
        color: "yellow", // yellow ring
        transparent: true, // to make our alphaMap work, we have to set this parameter to `true`
        alphaMap: alphaTexture 
      });
      var sphere = new THREE.Mesh(sphereGeom, sphereMat);
      sphere.visible = false;
      mesh.add(sphere);
      mesh.userData.direction = new THREE.Vector3(Math.random() - 0.5, 0, Math.random() - 0.5).normalize();
      mesh.userData.speed = Math.random() * 5 + 5;
      mesh.position.set(
        Math.random() * (worldWidth - 10) - (worldWidth - 10) * 0.5,
        10,
        Math.random() * (worldDepth - 10) - (worldDepth - 10) * 0.5
      );
      scene.add(mesh);
      return mesh;
    }
    

    The rest is not so difficult. We need an array of our NPCs which we'll check for intersection

    var npcs = [];
    for (var i = 0; i < 10; i++) {
      npcs.push(npc());
    }
    

    and then on mousedown event we'll select our an NPC (taken from the interactive cubes example and modified for our needs):

    window.addEventListener('mousedown', onMouseDown, false);
    
    function onMouseDown(event) {
      if (event.button != 2) return;
      mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
    
      selector.setFromCamera( mouse, camera );
        var intersects = selector.intersectObjects( npcs );
        if ( intersects.length > 0 ) {
          if ( INTERSECTED != intersects[ 0 ].object ) {
            if ( INTERSECTED ) INTERSECTED.children[0].visible = INTERSECTED.selected;
            INTERSECTED = intersects[ 0 ].object;
            INTERSECTED.selected = INTERSECTED.children[0].visible;
            INTERSECTED.children[0].visible = true;
          }
        } else {
          if ( INTERSECTED ) INTERSECTED.children[0].visible = INTERSECTED.selected;
          INTERSECTED = null;
        }
    }
    

    jsfiddle example. Here you can select objects with the right mouse button.