Having trouble getting point index clicked on in Points geometry

I have a scene which takes on the form of a sphere, representing the Earth, with a collection of points in orbit. I now want to be able to click on a given point and get its index, but I am having trouble getting it working.

I am looking at other questions, but I don’t seem to be working out the missing piece of the puzzle.

The camera and scene are setup as follows:

    this.scene = new SatelliteOrbitScene();
    this.camera = new PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 );

    this.renderer = new WebGLRenderer({ antialias: false });
    this.renderer.setPixelRatio(window.devicePixelRatio);
    this.renderer.setSize(window.innerWidth, window.innerHeight);
    document
      .querySelector(this.config.canvasSelector)
      ?.appendChild(this.renderer.domElement);

    this.controls = new OrbitControls( this.camera, this.renderer.domElement );
    this.camera.position.set( 15, 0, 10000 );
    this.controls.update();

    this.raycaster = new Raycaster();

The points are set up as follows:

    const geometry = new BufferGeometry();
    const vertices: Float32Array = new Float32Array();
    const sizes: Float32Array = new Float32Array();
    const colors: number[] = [];

    vertices.fill(0, 0, this.satelliteStore.satData.length * 3);
    colors.fill(0, 0, this.satelliteStore.satData.length * 3);
    sizes.fill(10, 0, this.satelliteStore.satData.length);

    geometry.setAttribute('position', new Float32BufferAttribute( vertices, 3 ) );
    geometry.setAttribute('color', new Float32BufferAttribute( colors, 4 ) );
    geometry.setAttribute('size', new Float32BufferAttribute( sizes, 1 ) );

    const texture = new TextureLoader().load(`${this.baseUrl}/images/circle.png`);
    const shader = this.shaderStore.getShader('dot2');

    const material = new ShaderMaterial({
      uniforms: {
        color: { value: new Color( 0xffffff ) },
        pointTexture: { value: texture }
      },
      clipping: true,
      vertexShader: shader.vertex,
      fragmentShader: shader.fragment,
      blending: AdditiveBlending,
      depthTest: true,
      transparent: true
    });

Then trying to handle the click:

    const canvasElement = this.renderer.domElement;
    canvasElement.addEventListener('click', (event:MouseEvent) => {
      if (!this.raycaster || !this.scene) {
        return;
      }

      const bounds = canvasElement.getBoundingClientRect();
      const mouse: Vector2 = new Vector2();
      mouse.x = ( (event.clientX - bounds.left) / canvasElement.clientWidth ) * 2 - 1;
      mouse.y = - ( (event.clientY - bounds.top) / canvasElement.clientHeight ) * 2 + 1;
      this.raycaster.setFromCamera( mouse, this.camera as PerspectiveCamera);
      const intersects = this.raycaster.intersectObjects(this.scene.children, true);
      if (intersects.length > 0) {
        console.log('>>>', intersects);
        // Do stuff
      }
    });

When I look at the value of intersects I am not seeing anything that would let me know the point that was clicked.

Can anyone suggest where I’m going wrong?

BTW the project code: GitHub - ajmas/StuffInSpace at issue-1-threejs

What DO you see when you get an intersect? Or are you not getting an intersect?

(you can adjust the tolerance of point clicking by adjusting the raycaster.params.Point.threshold)

https://threejs.org/docs/#api/en/core/Raycaster.params

If I adjust the points threshold (this.raycaster.params.Points.threshold):

  • 1: Unless I click on the Earth nothing. It just gives me 2 intersects when clicking on the earth
  • 5: Unless I click on the Earth nothing. It just gives me 2 intersects when clicking on the earth
  • 5.1+: Clicking on a point gives me 11000 intersects

I also tried 0.1 per another question, but no help.

Looks this is not quite the solution by itself.

I’m all out of ideas. :slight_smile: can you make a minimal repro that shows the issue, in something like glitch?

Hi!

What does happen in vertex shader?
If you move vertices with shader, then raycaster knows nothing about it, as it works with data of a geometry on JS side.

Based on @manthrax’s suggestion I have created a cut down sandbox project and I do see that it is working in the scenario. I’ll need to explore why this isn’t working properly in my main project.

And on GitHub: GitHub - ajmas/threejs-points-raycast: Sandbox project to demonstrate raycasting for click in a point cloud

At the very least this serves as a working example for anyone else dealing with a point cloud.

BTW to answer @prisoner849: replacing the ShaderMaterial with PointsMaterial in the main project didn’t resolve the issue. The vertex shader code was:

attribute float size;
attribute vec4 color;

varying vec4 vColor;

void main() {
  vec4 position = projectionMatrix * modelViewMatrix * vec4(position,1.);
  gl_PointSize = min(max(320000.0 / position.w, 7.5), 20.0) * 1.0;
  gl_Position = position;
  vColor = color;
}

Opening the example with chrome, it seems to work, the points I click turn red.
Maybe I don’t quite understand the problem as the raycaster seems to work.

The example works, so I now have a reference of part of the code which works. I shared it so others could make use of it - there are a lot questions on raycaster and points cloud, but not any that really came with a suitable working example.

It just isn’t working as part of my integrated code (see project linked at start), so I’ll need to explore what could be throwing off the raycaster and share my findings.

I did some more digging and the issue is happening if the point coordinates are updated after the initial render.

So in the code I initially provided above:

const vertices: Float32Array = new Float32Array();

and then:

setTimeout(() => {
  geometry.setAttribute('position', new Float32BufferAttribute(allPointCoords, 3));
}, 200);

This reproduces the issue: Raycasting Troubleshooting

I suspect there is a call I am missing to let the raycaster to use the new values?

So this missing part to all this was calling some functions following the position attribute changes:

geometry.computeBoundingBox();
geometry.computeBoundingSphere();

Documentation on this: three.js docs

Updated example, showing the working solution: Raycasting and updating points

1 Like