0

I've looked at a lot of examples -- and borrowed from some -- and can't seem to get this to work right. What I want is for the raycaster in onDocumentMouseDown to pick up sprites when the user clicks anywhere on the visible surface of a sprite. What I'm getting is a misaligned result, in that a sprite may be picked up if the user clicks somewhat to the right, above, or below the sprite, and will not pick it up at all if the user clicks on the left edge of the sprite. So basically something is misaligned, and I am at a loss for figuring out what I am doing wrong. Any guidance would be appreciated.

<script src="/common/three.js"></script>
<script src="/common/Detector.js"></script>
<script src="/common/CanvasRenderer.js"></script>


<script src="/common/GeometryUtils.js"></script>
<script src="/common/OrbitControls.js"></script>

<div id="WebGLCanvas"></div>



<script>
    var container, scene, camera, renderer, controls;
    var keyboard;

</script>

<script>




    // custom global variables
    var mouse = { x: 0, y: 0 };
    var raycaster;
    var sprites = new Array();
    init();
    try {
        for (i = 0; i < 10; i++) {
            var text = "Text " + i;
            var x = Math.random() * 100;
            var y = Math.random() * 100;
            var z = Math.random() * 100;
            var spritey = addOrUPdateSprite(text, i, x, y, z);
        }

    }
    catch (ex) {
        alert("error when creating sprite: " + ex.message);
    }


    animate();



    function init() {
        try {
            scene = new THREE.Scene();
            // CAMERA
            var cont = document.getElementById("WebGLCanvas");
            var SCREEN_WIDTH = window.innerWidth;
            OFFSET_TOP = document.getElementById("WebGLCanvas").getBoundingClientRect().top;
            var SCREEN_HEIGHT = window.innerHeight - OFFSET_TOP;    //;   //-document.getElementById("upper").clientHeight;
            var VIEW_ANGLE = 60;
            var ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT;
            var NEAR = 0.1;
            var FAR = 1000;
            camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
            scene.add(camera);
            camera.position.set(0, 100, 200);
            camera.lookAt(new THREE.Vector3());
            renderer = new THREE.WebGLRenderer({ antialias: true });
            container = document.getElementById('WebGLCanvas');
            container.appendChild(renderer.domElement);
            renderer.setSize(window.innerWidth, SCREEN_HEIGHT);

            controls = new THREE.OrbitControls(camera, renderer.domElement);
            //                spritey.position.normalize();

            raycaster = new THREE.Raycaster();
            document.addEventListener('mousedown', onDocumentMouseDown, false);
            document.addEventListener('touchstart', onDocumentTouchStart, false);
        }
        catch (ex) {
            alert("error " + ex.message);
        }


    }

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

    function update() {
        controls.update();
    }

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

    function addOrUPdateSprite(text, name, x, y, z) {
        var sprite = scene.getObjectByName(name);
        if (sprite == null) {
            sprite = makeTextSprite(text, { fontsize: 36, borderColor: { r: 255, g: 0, b: 0, a: 1.0 }, backgroundColor: { r: 255, g: 100, b: 100, a: 0.8 } });
            sprite.name = name;
            sprites.push(sprite);
            scene.add(sprite);
        }

        sprite.position.set(x, y, z);
    }

    function makeTextSprite(message, parameters) {
        if (parameters === undefined) parameters = {};
        var fontface = parameters.hasOwnProperty("fontface") ? parameters["fontface"] : "sans-serif";
        var fontsize = parameters.hasOwnProperty("fontsize") ? parameters["fontsize"] : 36;
        var borderThickness = parameters.hasOwnProperty("borderThickness") ? parameters["borderThickness"] : 1;
        var borderColor = parameters.hasOwnProperty("borderColor") ? parameters["borderColor"] : { r: 0, g: 0, b: 0, a: 1.0 };
        var backgroundColor = parameters.hasOwnProperty("backgroundColor") ? parameters["backgroundColor"] : { r: 255, g: 255, b: 255, a: 1.0 };
        var textColor = parameters.hasOwnProperty("textColor") ? parameters["textColor"] : { r: 0, g: 0, b: 0, a: 1.0 };

        var canvas = document.createElement('canvas');
        var context = canvas.getContext('2d');
        context.font = fontsize + "px " + fontface;
        var metrics = context.measureText(message);
        var textWidth = metrics.width;

        context.fillStyle = "rgba(" + backgroundColor.r + "," + backgroundColor.g + "," + backgroundColor.b + "," + backgroundColor.a + ")";
        context.strokeStyle = "rgba(" + borderColor.r + "," + borderColor.g + "," + borderColor.b + "," + borderColor.a + ")";

        context.lineWidth = borderThickness;
        roundRect(context, borderThickness / 2, borderThickness / 2, (textWidth + borderThickness) * 1.1, fontsize * 1.4 + borderThickness, 8);

        context.fillStyle = "rgba(" + textColor.r + ", " + textColor.g + ", " + textColor.b + ", 1.0)";
        context.fillText(message, borderThickness, fontsize + borderThickness);

        var texture = new THREE.Texture(canvas)
        texture.needsUpdate = true;

        var spriteMaterial = new THREE.SpriteMaterial({ map: texture, useScreenCoordinates: false });
        var sprite = new THREE.Sprite(spriteMaterial);
        sprite.scale.set(1.0 * fontsize, 0.5 * fontsize, 1.5 * fontsize);
        return sprite;


    }

    // function for drawing rounded rectangles
    function roundRect(ctx, x, y, w, h, r) {
        ctx.beginPath();
        ctx.moveTo(x + r, y);
        ctx.lineTo(x + w - r, y);
        ctx.quadraticCurveTo(x + w, y, x + w, y + r);
        ctx.lineTo(x + w, y + h - r);
        ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
        ctx.lineTo(x + r, y + h);
        ctx.quadraticCurveTo(x, y + h, x, y + h - r);
        ctx.lineTo(x, y + r);
        ctx.quadraticCurveTo(x, y, x + r, y);
        ctx.closePath();
        ctx.fill();
        ctx.stroke();
    }



    function onDocumentTouchStart(event) {
        event.preventDefault();

        event.clientX = event.touches[0].clientX;
        event.clientY = event.touches[0].clientY;
        onDocumentMouseDown(event);



    }

    function onDocumentMouseDown(event) {
        mouse.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1;
        mouse.y = -((event.clientY) / renderer.domElement.clientHeight) * 2 + 1;
        raycaster.setFromCamera(mouse, camera);

        var intersects = raycaster.intersectObjects(sprites, true);

        if (intersects.length > 0) {
            var obj = intersects[0].object;

            alert(obj.name);

            event.preventDefault();
        }


    }




</script>
Len White
  • 892
  • 13
  • 25

2 Answers2

1

In your makeTextSprite() function, after

var textWidth = metrics.width;

add this

context.strokeStyle = "white";
context.lineWidth = 5;
context.strokeRect(0,0,canvas.width, canvas.height);

and you will see, that your sprites have not the size you think of.

UPD. You can set the size of a canvas like this

var ctxFont = "bold " + fontsize + "px " + fontface;
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
context.font = ctxFont;
var metrics = context.measureText(message);
var textWidth = metrics.width;

canvas.width = textWidth + borderThickness * 2;
canvas.height = fontsize * 1.2 + (borderThickness * 2);
context = canvas.getContext('2d');
context.font = ctxFont;

and then set the scale of a sprite

sprite.scale.set(canvas.width / 10, canvas.height /  10, 1);

jsfiddle example

prisoner849
  • 16,894
  • 4
  • 34
  • 68
  • Interesting, thanks! I think this explains why wide ones are truncated. Do you know whether there is any way of having the actual size be only what is needed? – Len White Nov 11 '16 at 09:17
  • Thanks prisoner849, this is very helpful in that it has fixed the canvas width which I did not know was an issue, and now the text is no longer truncated and clicking outside the visible sprite doesn't get picked up. It remains an issue however that clicking in the left side of the sprite still does not get picked up, nor does clicking in the far right side of the sprite. It's as if the raycaster now believes the sprite to be smaller than it is. Your jsfiddle seems to bear this out. This is a dramatic improvement so I am grateful! Any more tricks up your sleave? – Len White Nov 11 '16 at 18:46
  • I've found this [SO question](http://stackoverflow.com/questions/36123458/three-raycaster-not-working-properly-with-scaled-three-sprite). In simple words: the closer your sprite to a regular tetragon (square) the better. Thus, it's better to click to the center of a sprite as closer as possible. – prisoner849 Nov 11 '16 at 20:06
  • I see, thanks. So basically this is a bug. I'm not sure whether the answer in the question you referenced means to imply that it's been fixed, or that an approximation has been provided. It definitely doesn't work for me, and my scenario requires some precision. I guess I'll have to cobble together an alternative to raycaster. Thanks for your help! – Len White Nov 11 '16 at 21:47
0

Your Three.js canvas probably isn't at the top left of the screen, and you're not taking into account the offset from 0,0 on the page. To fix it, adjust the mouse position to subtract the offset.

var rect = container.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / renderer.domElement.clientWidth) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / renderer.domElement.clientHeight) * 2 + 1;
Andy Ray
  • 30,372
  • 14
  • 101
  • 138
  • No, that's not it. And not only is it at 0,0 but if you map out the area in which the raycaster picks up the sprite, it is larger than the sprite. It is as if the sprite is bigger and shifted to the right of where it shows on the screen. I feel as though I have something wrong with the camera, but I don't know what it could be. – Len White Nov 11 '16 at 06:08