Borislav Kiselkov
Borislav Kiselkov

Reputation: 126

Raycaster cannot get the correct mesh . Three.js

I know this question has been asked I lot of times but I cannot understand how does raycasting work. I have a function creating country borders from a geo json file as well as the fill of the country which I want to be clicked. When clicked some information should be displayed about the clicked country.

var borders = new THREE.Group();
function createBorders(dataJSON) {

var SIZE_AMPLIFIER = 20;
var WIDTH = 2500 * SIZE_AMPLIFIER;


var projection = d3.geoTransverseMercator().rotate([-10, 0, 0]) //center meridian
  .center([10, 52])                                             //longitude, latitude
  .scale(WIDTH * 1.5 - 505);                                    //scale

var path = d3.geoPath().projection(projection);
var svg = d3.select("#Map").append("svg");

svg.selectAll(".country")
  .data(dataJSON)
  .enter()
  .append("path")
  .attr("class", ".country")
  .attr("d", path);

var svgMarkup = svg.node().outerHTML;
var loader = new SVGLoader();
var svgData = loader.parse(svgMarkup);

svgData.paths.forEach((path, i) => {
  var shapes = path.toShapes(true);

  shapes.forEach((shape, j) => {

    var geomSVG = new THREE.ExtrudeBufferGeometry(shape, {
      depth: 50,
      bevelEnabled: false
    })

    //needed for click event!
    var materialSVG = new THREE.MeshLambertMaterial({
      color: 0xFFFFFF,
      transparent: true,
      opacity: 0.8,
    });

    var meshSVG = new THREE.Mesh(geomSVG, materialSVG);
    this.borders.add(meshSVG);

    //create borders
    var borderMaterial = new THREE.LineBasicMaterial({ color: 0x000000, linewidth: 3 })
    var borderGeometry = new THREE.EdgesGeometry(geomSVG, 15);
    var bordermesh = new THREE.LineSegments(borderGeometry, borderMaterial);

    this.borders.add(bordermesh);

  })

})

this.borders.rotateX(Math.PI / 2)
this.borders.position.z = -300;
this.borders.position.x = 16300;

this.borders.position.y = 0;

//get only meshes, no linesegments

var countryMeshes = [];
for(let m in this.borders.children){
  if(this.borders.children[m] instanceof THREE.Mesh){
    countryMeshes.push(this.borders.children[m])
  }
}

//add callback for each mesh

for(let c in countryMeshes){
  countryMeshes[c].callback = function(){console.log(dataJSON[c].properties.name_long)}

}

this.scene.add(this.state.borders);
svg.remove();

}

Then I make the raycasting function:

 var raycaster = new THREE.Raycaster();

   function onMeshClick(event) {

    event.preventDefault();

    var vector = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5);


    raycaster.setFromCamera(vector, camera);
    var intersects = raycaster.intersectObjects(this.borders.children, true); 

    if (intersects.length > 0) {
      intersects[0].object.callback();
    }

  }

and at the end I add an event listener:

window.addEventListener('click', onMeshClick, false);

The callback works but not in the correct order. I have to click on another country or even outside the map to be able to call some of the countries.

Upvotes: 1

Views: 784

Answers (1)

M -
M -

Reputation: 28502

You're grouping your LineSegments together with your Meshes. Maybe when clicking, it's capturing a line instead of the mesh, since the Raycaster has a precision threshold of 1 world unit when measuring lines, which might be too large. Think of it as ray-casting with a wide brush instead of a single pixel.

You have 2 options:

  1. Group your country areas separately from its borders.
var areas = new THREE.Group();
var borders = new THREE.Group();
scene.add(areas);
scene.add(borders);

svgData.paths.forEach((path, i) => {
    // ...
    areas.add(meshSVG);
    // ...
    borders.add(bordermesh);
})

// When raycasting, only test country areas, leaving borders out
var intersects = raycaster.intersectObjects(areas, true); 
  1. Lower the threshold of your raycaster so borders don't take up as much space in the raycaster's eyes: raycaster.params.Line.threshold = 0.1

Upvotes: 2

Related Questions