Reputation: 126
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
Reputation: 28502
You're grouping your LineSegments
together with your Mesh
es. 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:
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);
raycaster.params.Line.threshold = 0.1
Upvotes: 2