Clickable area of sprite

I’m trying to make some clickable sprite, but I’m having trouble understanding which is the clickable area. Is it a rectangle? If so, how it is centered?
Here I post an image with the sprites I created

and the functions I used to create the sprites

function createCanvasTexture(color, shape) {
const size = 64; // Texture size
const canvas = document.createElement(‘canvas’);
canvas.width = size;
canvas.height = size;
const context = canvas.getContext(‘2d’);

// Center of the canvas
const centerX = size / 2;
const centerY = size / 2;
const margin = 10; // Margin to ensure shape is smaller than the canvas
const shapeSize = size - margin * 2; // Adjusted size for the shape

context.fillStyle = color;

switch (shape) {
case ‘circle’:
context.beginPath();
context.arc(centerX, centerY, size / 2 - 1, 0, 2 * Math.PI); // Circle within the canvas
context.fill();
break;
case ‘square’:
const squareSize = size / 2;
context.fillRect(centerX - squareSize / 2, centerY - squareSize / 2, squareSize, squareSize);
break;
case ‘triangle’:
const sideLength = size / 2;
context.beginPath();
context.moveTo(centerX, centerY - sideLength / 2);
context.lineTo(centerX + sideLength / 2, centerY + sideLength / 2);
context.lineTo(centerX - sideLength / 2, centerY + sideLength / 2);
context.closePath();
context.fill();
break;
case ‘rhombus’:
const rhombusSize = size / 2;
context.beginPath();
context.moveTo(centerX, centerY - rhombusSize / 2);
context.lineTo(centerX + rhombusSize / 2, centerY);
context.lineTo(centerX, centerY + rhombusSize / 2);
context.lineTo(centerX - rhombusSize / 2, centerY);
context.closePath();
context.fill();
break;

}

return new THREE.CanvasTexture(canvas);
}

export function createSprite(color, shape) {
const texture = createCanvasTexture2(color, shape);
const material = new THREE.SpriteMaterial({ map: texture, transparent: true });
const sprite = new THREE.Sprite(material);
return sprite;
}

Can someone draw the rectangles representing te clickable areas?

If you set transparent to false you should see precisely the clickable area around each sprite.

3 Likes

Now I can see the rectangles, but they do not seem to represent the actual clickable area. If I click slightly to the left or above the rectangle the click event is still detected, while if I click on the lower part on the rectangle or on the right edge my event handler only detects a click on the plane.
How does this work?

In that case there’s a chance your raycasting code does something incorrectly - but hard to say without seeing (1) how you raycast, (2) how you adjust the renderer when resizing of the window.

2 Likes

Here are the functions used to resize and raycast, mostly taken from the documentation

    let camera, controls, scene, renderer;
    const raycaster = new THREE.Raycaster();
    const pointer = new THREE.Vector2();
    const mouse = new THREE.Vector2();
      scene = new THREE.Scene();
      scene.background = new THREE.Color(0x000000);

      renderer = new THREE.WebGLRenderer({ antialias: true });
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(window.innerWidth, window.innerHeight);
      document.body.appendChild(renderer.domElement);
      camera = new THREE.PerspectiveCamera(
        60,
        window.innerWidth / window.innerHeight,
        1e-5,
        10000
      );
      camera.position.set(0, 3, 3);
      window.addEventListener("resize", onWindowResize);
    function onWindowResize() {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();

      renderer.setSize(window.innerWidth, window.innerHeight);
    }
      renderer.domElement.addEventListener('pointerdown', onPointerMove);
    function onPointerMove(event) {
      pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
      pointer.y = - (event.clientY / window.innerHeight) * 2 + 1;

      raycaster.setFromCamera(pointer, camera);

      const intersects = raycaster.intersectObjects(scene.children, true);

      if (intersects.length > 0) {
        const firstIntersectedObject = intersects[0].object;
        console.log("UUID of intersected object: ", firstIntersectedObject.uuid);

        if (firstIntersectedObject instanceof THREE.Sprite) {
          console.log('Sprite clicked:', firstIntersectedObject);
        }
      }
    }

Maybe three.js can return uv coordinates after click and then you can check black area if put textures into canvas2d or put black pixel into data array and check them. Or you can create custom raycasting code which will chececk intersections with rotated triangle, sphere, square.

It looks like there may be a scrollable area and or margins on the three js canvas, if you’re using window.innerwidth and height for raycasting you may be miscalculating the mouse position when setting the raycaster from camera and may need to account for the canvases offsetTop and offsetLeft to get the accurate mouse position…

1 Like

:point_up: Alternatively:

renderer.domElement.style.position = 'absolute';
renderer.domElement.style.top = '0px';
renderer.domElement.style.left = '0px';
renderer.domElement.style.width = '100vw';
renderer.domElement.style.height = '100vh';
2 Likes

Also if three.js can return uv coordinates then u can check is uv point into triangle side, or is distance from uv pont to sphere center lay into sphere radius, or is uv coordinate lay into min max of cuted rectangle without black space. Also u can use renderer readPixel, if pixel is black then not intersected.

The issue disappeared the moment I removed the Grid Helper. I suppose the problem was that the ray intersected the grid before getting to the sprite.
This would imply, also, that the grid helper is actually treated as a plane