0
votes

On the minimal example below (don't forget to adapt the URL of three.min.js) then open the html file in a window. You should see a (non-regular) tetrahedron. When moving the mouse over the canvas you should see the number of intersection of the ray from the camera to the mouse with all the objects of the scene object, tested with this line in the code:

raycaster.intersectObjects(scene.children,false);

Since apart from the ligths, there is only the tetrahedron, it says mostly 0 or 2 because it counts the number of faces that have been intersected by the infinite ray and because I have chosen a double sided material:

var material = new THREE.MeshLambertMaterial( { color: 0xd8f8b0, side: THREE.DoubleSide } );

Now click the checkbox. Another tetrahedron is created on the fly, its Geometry being a clone of the Geometry of the first Mesh.

geom2 = geom.clone();

I offset the new geom by adding 1 to all the coordinates of its vertices. However, the raycaster answers 0 for most rays intersecting the new object. Is there a bug or did I forget or misunderstand something?

If the geometry is not a clone (change clone=true; to clone=false; on the top of min.js) then it works.

Three.js version : r86

Minimal example

the html file:

<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<script src="three.min.js"></script>
</head>

<body>
<div style="text-align: center;">
<canvas id="ze-canvas" width="800" height="600"></canvas>
<p>
<input type="checkbox" id="filBox"> click me
<p>
<span id="info-text"></span>
<p>
<script src="min.js"></script>
<script>visualiseur("ze-canvas","info-text","filBox");</script>
</div>
</body>
</html>

the file min.js

var visualiseur =  function(canvas_name,info_name,box_name) {
  var clone = true;

  var canvas = document.getElementById(canvas_name);
  var cbox = document.getElementById(box_name);
  var textInfo = document.getElementById(info_name);

  cbox.checked = false;

  var camera = new THREE.PerspectiveCamera( 33, canvas.width / canvas.height, 0.1, 1000 );
  camera.position.set(-2,4,8);
  camera.lookAt(new THREE.Vector3(0,0,0));

  var scene = new THREE.Scene();

  scene.add( new THREE.AmbientLight( 0xffffff, .3) );
  var light1 = new THREE.PointLight( 0xCCffff, .7, 0, 2 );
  var light2 = new THREE.PointLight( 0xffffCC, .7, 0, 2 );
  light1.position.set( 50, -50, 20 );
  light2.position.set( -50, 150, 60 );
  scene.add( light1 );
  scene.add( light2 );

  var material = new THREE.MeshLambertMaterial( { color: 0xd8f8b0, side: THREE.DoubleSide } );

  var makeGeom = function(geom) {
    geom.vertices.push(new THREE.Vector3(0,0,0));
    geom.vertices.push(new THREE.Vector3(0,0,1));
    geom.vertices.push(new THREE.Vector3(0,1,0));
    geom.vertices.push(new THREE.Vector3(1,0,0));
    geom.faces.push(new THREE.Face3(0,1,2));
    geom.faces.push(new THREE.Face3(1,3,2));
    geom.faces.push(new THREE.Face3(0,2,3));
    geom.faces.push(new THREE.Face3(0,3,1));
    geom.computeFlatVertexNormals();
  }

  var geom = new THREE.Geometry();
  makeGeom(geom);
  var mesh = new THREE.Mesh(geom,material);
  scene.add(mesh);

  var renderer = new THREE.WebGLRenderer({ canvas : canvas, antialias: true});

  var render = function() {
    renderer.render( scene, camera );
  }

  function getMousePos(evt) {
    var rect = canvas.getBoundingClientRect();
    return {
      x: evt.clientX - rect.left,
      y: evt.clientY - rect.top
    };
  }

  var raycaster = new THREE.Raycaster();

  canvas.onmousemove = function(e) {
    render();
    var p=getMousePos(e);
    p.x = p.x/canvas.width*2 - 1;
    p.y = -p.y/canvas.height*2 + 1;
    raycaster.setFromCamera( new THREE.Vector2(p.x,p.y), camera);
    var intersects = raycaster.intersectObjects(scene.children,false);
    textInfo.innerHTML=intersects.length+" intersections";
  }

  var done=false;

  cbox.onclick = function(e) {
    if(done) return;
    done = true;
    var geom2;
    if(clone) {
      geom2 = geom.clone();
    }
    else {
      geom2 = new THREE.Geometry();
      makeGeom(geom2);
    }
    geom2.vertices.forEach(function(v) {
      v.x += 1;
      v.y += 1;
      v.z += 1;
    });
    geom2.verticesNeedUpdate=true;
    geom2.computeFlatVertexNormals();
    scene.add(new THREE.Mesh(geom2,material));
    render();
  }

  render();
}
1

1 Answers

1
votes

This was a tricky one but I found the solution.

In short: add

geom2.computeBoundingSphere();

just after changing the vertices.

Long version: Here's how I found the solution. I started looking at the source code of Geometry.js and looking at every member function or variable in Geometry that I might have overlooked. I finally noticed this bounding box and bounding sphere things, which I had never heard of in THREE.js before. Looking back at the Geometry section of the documentation of THREE.js, they are mentioned but without any explanation on what they are used for.

One would naturally think then they are used to accelerate the rendering on the graphics card by first computing the intersection of a ray with the box/sphere (this is fast I suppose) and if there is none we can skip the whole object.

This turns out to be a false assumption: on my minimal example, the second tetrahedron does show up even though its bounding sphere is wrong.

Then it got even more strange. I had the script log the bounding boxes and spheres of the geometries when I click the box, once just before the cloning and once just after the next rendering pass. They never get a bounding box. The first geometry has bounding sphere before and after. The second has a bounding sphere before rendering only if clone = true (so no bounding sphere when created). After rendering, both objects have a bounding sphere.

Conclusion : the bounding sphere is used by the Raycaster but not the rendered. (this is a surprise to me)

By inspecting the bounding sphere centers, I realized that the bounding sphere of the second geometry was wrong when it is cloned and the vertices moved, and is not updated by render(). When an object is created and render() called, then its bounding sphere is created and is correct. However, if you change the vertices, and even if you set the verticesNeedUpdate flag to true, the bounding sphere does not get updated, you have to call manually computeBoundingSphere().

It is all the more puzzling that the bounding sphere is secretly created when you call render() but not the bounding box.

Let me sum up what I understood of this all:

  • the bounding sphere is used by the Raycaster but not the renderer

  • I ignore if the bounding box is used by either (I have not spent time testing that)

  • if the bounding sphere does not exist, it will get created when calling render(). If it exists it is not updated by calling render() even when the flag verticesNeedUpdate is set to true.

  • the bounding box does not get created by render()

Is this the designed behaviour or is this a bug?