10

this could be a duplicate, but I have not found anything as yet. I am trying to create tooltip for on mouse hover. (perspective camera)

Tooltip issue: https://jsfiddle.net/thehui87/d12fLr0b/14/

threejs r76

function onDocumentMouseMove( event ) 
{
    // update sprite position
    // sprite1.position.set( event.clientX, event.clientY, 1 );

    // update the mouse variable
    mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
    mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

    var vector = new THREE.Vector3( mouse.x, mouse.y, 0 );
    vector.unproject(camera);

    var dir = vector.sub( camera.position ).normalize();
    var distance = - camera.position.z / dir.z;
    var pos = camera.position.clone().add( dir.multiplyScalar( distance ) );
    sprite1.position.copy(pos);


}

But once i orbit the camera the tooltip moves away.

References for tooltip.

If anyone could kindly point me to the right answer on stackoverflow or provide an alternate solution.

Thanks

Community
  • 1
  • 1
Deb
  • 157
  • 2
  • 9
  • 1
    I don't have a ready-made answer for you right now, but your problem appears to be that you're assuming the z-axis of the camera is always going to be from viewer to object. OrbitControls rotates the camera around a particular "look at" point; so you need to take the vector from camera to the intersection point and use *all* its axis to compute the distance and sprite position. – Leeft Aug 27 '16 at 08:18
  • yep, that's one and the other i just found out is that the tooltip also should not be zooming. So I need to use a second camera to render the tooltip. Thanks. – Deb Aug 27 '16 at 08:31
  • I might need to do something like this http://www.evermade.fi/pure-three-js-hud/ , let see once it is done will post the answer here. – Deb Aug 27 '16 at 08:37
  • You need the orbit controller to move your object only, not the entire scene. If you can't do that you will need to compensate with the inverse 3D transform matrix. – mika Oct 17 '16 at 21:13

2 Answers2

11

Well working tooltip is not only correctly placed text label. It has some of show and hide intelligence.

It should:

  1. not appear immediately, but once mouse becomes stationary over some object,
  2. not disappear immediately, rather it should fade away after mouse left the object area,
  3. it should neither follow the mouse, nor stay where it was, if user moves the mouse around the object area not leaving it.

All of that is included in my solution: http://jsfiddle.net/mmalex/ycnh0wze/ threejs html css tooltip

<div id="tooltip"></div> must be initially added to HTML. The recommended CSS you find below. Crucial to have it position: fixed;, anything else is a matter of taste.

#tooltip {
  position: fixed;
  left: 0;
  top: 0;
  min-width: 100px;
  text-align: center;
  padding: 5px 12px;
  font-family: monospace;
  background: #a0c020;
  display: none;
  opacity: 0;
  border: 1px solid black;
  box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5);
  transition: opacity 0.25s linear;
  border-radius: 3px;
}
var scene = new THREE.Scene();
var raycaster = new THREE.Raycaster();

//create some camera
camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.x = 5;
camera.position.y = 5;
camera.position.z = 5;
camera.lookAt(0, 0, 0);

var controls = new THREE.OrbitControls(camera);

var renderer = new THREE.WebGLRenderer({
    antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(new THREE.Color(0x595959));
document.body.appendChild(renderer.domElement);

// white spotlight shining from the side, casting a shadow
var spotLight = new THREE.SpotLight(0xffffff, 2.5, 25, Math.PI / 6);
spotLight.position.set(4, 10, 7);
scene.add(spotLight);

// collect objects for raycasting, 
// for better performance don't raytrace all scene
var tooltipEnabledObjects = [];

var colors = new RayysWebColors();
for (let k = 0; k < 12; k++) {
    var size = 0.5;
    var geometry = new THREE.BoxGeometry(size, 0.2, size);
    var randomColor = colors.pickRandom();
    var material = new THREE.MeshPhongMaterial({
        color: randomColor.hex,
        transparent: true,
        opacity: 0.75
    });
    var cube = new THREE.Mesh(geometry, material);
    cube.userData.tooltipText = randomColor.name;
    cube.applyMatrix(new THREE.Matrix4().makeTranslation(-3 + 6 * Math.random(), 0, -3 + 0.5 * k));
    scene.add(cube);
    tooltipEnabledObjects.push(cube);
}

function animate() {
    requestAnimationFrame(animate);
    controls.update();
    renderer.render(scene, camera);
};

// this will be 2D coordinates of the current mouse position, [0,0] is middle of the screen.
var mouse = new THREE.Vector2();

var latestMouseProjection; // this is the latest projection of the mouse on object (i.e. intersection with ray)
var hoveredObj; // this objects is hovered at the moment

// tooltip will not appear immediately. If object was hovered shortly,
// - the timer will be canceled and tooltip will not appear at all.
var tooltipDisplayTimeout;

// This will move tooltip to the current mouse position and show it by timer.
function showTooltip() {
    var divElement = $("#tooltip");

    if (divElement && latestMouseProjection) {
        divElement.css({
            display: "block",
            opacity: 0.0
        });

        var canvasHalfWidth = renderer.domElement.offsetWidth / 2;
        var canvasHalfHeight = renderer.domElement.offsetHeight / 2;

        var tooltipPosition = latestMouseProjection.clone().project(camera);
        tooltipPosition.x = (tooltipPosition.x * canvasHalfWidth) + canvasHalfWidth + renderer.domElement.offsetLeft;
        tooltipPosition.y = -(tooltipPosition.y * canvasHalfHeight) + canvasHalfHeight + renderer.domElement.offsetTop;

        var tootipWidth = divElement[0].offsetWidth;
        var tootipHeight = divElement[0].offsetHeight;

        divElement.css({
            left: `${tooltipPosition.x - tootipWidth/2}px`,
            top: `${tooltipPosition.y - tootipHeight - 5}px`
        });

        // var position = new THREE.Vector3();
        // var quaternion = new THREE.Quaternion();
        // var scale = new THREE.Vector3();
        // hoveredObj.matrix.decompose(position, quaternion, scale);
        divElement.text(hoveredObj.userData.tooltipText);

        setTimeout(function() {
            divElement.css({
                opacity: 1.0
            });
        }, 25);
    }
}

// This will immediately hide tooltip.
function hideTooltip() {
    var divElement = $("#tooltip");
    if (divElement) {
        divElement.css({
            display: "none"
        });
    }
}

// Following two functions will convert mouse coordinates
// from screen to three.js system (where [0,0] is in the middle of the screen)
function updateMouseCoords(event, coordsObj) {
    coordsObj.x = ((event.clientX - renderer.domElement.offsetLeft + 0.5) / window.innerWidth) * 2 - 1;
    coordsObj.y = -((event.clientY - renderer.domElement.offsetTop + 0.5) / window.innerHeight) * 2 + 1;
}

function handleManipulationUpdate() {
    raycaster.setFromCamera(mouse, camera); {
        var intersects = raycaster.intersectObjects(tooltipEnabledObjects);
        if (intersects.length > 0) {
            latestMouseProjection = intersects[0].point;
            hoveredObj = intersects[0].object;
        }
    }

    if (tooltipDisplayTimeout || !latestMouseProjection) {
        clearTimeout(tooltipDisplayTimeout);
        tooltipDisplayTimeout = undefined;
        hideTooltip();
    }

    if (!tooltipDisplayTimeout && latestMouseProjection) {
        tooltipDisplayTimeout = setTimeout(function() {
            tooltipDisplayTimeout = undefined;
            showTooltip();
        }, 330);
    }
}

function onMouseMove(event) {
    updateMouseCoords(event, mouse);

    latestMouseProjection = undefined;
    hoveredObj = undefined;
    handleManipulationUpdate();
}

window.addEventListener('mousemove', onMouseMove, false);

animate();
Alex Khoroshylov
  • 2,234
  • 1
  • 17
  • 28
  • How would you know if the object is behind the camera ? in this case the tooltip would show up in the wrong place? – Sebastian Mansfield May 23 '19 at 20:53
  • @Sebastian Mansfield, feel free to fork my jsfiddle to demonstrate the problem you found. – Alex Khoroshylov May 24 '19 at 11:09
  • Great stuff - thanks Alex. I've made a version broken down in to two Typescript classes `MouseInput` and `Tooltip`, which use userData for tooltip contents, and doesn't require jQuery. In case useful to others: https://pastebin.com/bkCQeD02 – Peter Ehrlich Sep 09 '22 at 15:06
1

Replace:

var vector = new THREE.Vector3( mouse.x, mouse.y, 0 );

by:

var vector = new THREE.Vector3( mouse.x, mouse.y, 1 );
Sport
  • 8,570
  • 6
  • 46
  • 65
Sab
  • 11
  • 1