Search code examples
three.jsraycasting

THREE.js Identify click on one geometry (merged geometry)


I want to change color only for one geometry on click. I'm creating geometries by cloning and merging them to one buffer geometry. So there is one big mesh with a lot of buffergeometry attributes. Now by clicking on the mesh, I can change color for all mesh, but is it possible to change color only for one element (box)?

Here's my code:

// set up threejs
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 50, window.innerWidth/window.innerHeight, 0.01, 1000 );

var renderer = new THREE.WebGLRenderer(); 
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
camera.position.z = 5;


// add merged geometries
var allGeometries = new THREE.Geometry();
var geometry = new THREE.BoxGeometry( 0.5, 0.5, 0.5 );
var geoVerticeAmm = geometry.vertices.length;
var amm = 50;

for(var i=0; i<amm; i++){
  var x = (0.5-Math.random())*3;
  var y = (0.5-Math.random())*3;
  var z = (0.5-Math.random())*3;
  
  var newGeo = geometry.clone();
  newGeo.translate(x, y, z);
  allGeometries.merge( newGeo );
}

var material = new THREE.MeshBasicMaterial( { 
  color: 0x005E99,
  vertexColors: THREE.VertexColors,
  transparent: true,
} );
var buffGeometry = new THREE.BufferGeometry().fromGeometry(allGeometries);
var mesh = new THREE.Mesh( buffGeometry, material );

var allGeoVerticeAmm = mesh.geometry.attributes.position.count;
var colorsArray = new Float32Array(allGeoVerticeAmm * 3);
mesh.geometry.addAttribute('color', new THREE.BufferAttribute(colorsArray, 3));

for(var i=0; i<allGeoVerticeAmm; i++){
  var col = i/allGeoVerticeAmm;
  mesh.geometry.attributes.color.array[ i * 3 ] = col;
  mesh.geometry.attributes.color.array[ i * 3 + 1 ] = col;
  mesh.geometry.attributes.color.array[ i * 3 + 2 ] = col;
} 
mesh.geometry.colorsNeedUpdate = true;

mesh.callback = function() { 
  // change color only for one box
  var r = Math.round( Math.random()*255 );
  var g = Math.round( Math.random()*255 );
  var b = Math.round( Math.random()*255 );
  
  this.material.color = new THREE.Color("rgb(" + r + ", " + g + ", " + b + ")");
}

scene.add(mesh);


// raycaster
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();


function onDocumentMouseDown( event ) {
  event.preventDefault();
  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( scene.children ); 

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

document.addEventListener('mousedown', onDocumentMouseDown, false);

// render
var render = function () {
  requestAnimationFrame( render );
  renderer.render(scene, camera);
};
render();
Click on box
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r73/three.min.js"></script>


Solution

  • You have 2 options:

    1. When you perform the raycasting, the returned object has a face and faceIndex property. You could use that information to loop through the mesh's color BufferAttribute that you created, and do a comparison: if the vertices are part of the geometry connected to that face, change their color values.

    2. You could keep the geometries independent as separate meshes. This is much simpler, because after performing the raycasting, you could simply set:
      intersects[0].object.material.color = '0xff9900'; without having to loop through individual vertices.