Tomasz Pupiec
Tomasz Pupiec

Reputation: 1

THREE.js how to make transparent front walls in 3d room model

I want to make a 3d room in three.js. I want the walls, that are in front of the camera view, to become transparent as I rotate the room.

Here is example what i need: http://jsfiddle.net/tp2f2oo4/

It seems that solution is adding THREE.BackSide to the material.

var material2 = new THREE.MeshPhongMaterial( {
    color: 0xffffff, 
    transparent: false,
    side: THREE.BackSide
} );

Indeed this works perfectly when a room acts as a single THREE.BoxGeometry, but in my case, every wall, ceiling, and floor are separate THREE.BoxGeometry objects. Is there any way to hide them or not rendering when are in front of the camera view?

Upvotes: 0

Views: 4388

Answers (2)

WestLangley
WestLangley

Reputation: 104833

You want to hide a mesh based on some condition. This is a perfect use case for using the onBeforeRender() and onAfterRender() methods.

// callbacks
var onBeforeRender = function() {

    var v = new THREE.Vector3();

    return function onBeforeRender( renderer, scene, camera, geometry, material, group ) {

        // this is one way. adapt to your use case.
        if ( v.subVectors( camera.position, this.position ).dot( this.userData.normal ) < 0 ) {

            geometry.setDrawRange( 0, 0 ); // it is too late to set visible = false, so do this, instead

        }

    };

}();

var onAfterRender = function( renderer, scene, camera, geometry, material, group ) {

    geometry.setDrawRange( 0, Infinity );

};

// mesh
mesh = new THREE.Mesh( geometry, material );
mesh.position.set( 0, 0, 10 );
mesh.rotation.set( 0, 0, 0 );
scene.add( mesh );
mesh.userData.normal = new THREE.Vector3( 0, 0, - 1 );
mesh.onBeforeRender = onBeforeRender;
mesh.onAfterRender = onAfterRender;

fiddle: http://jsfiddle.net/3qh815xa/

three.js r.114

Upvotes: 6

TheJim01
TheJim01

Reputation: 8886

You could use ray-casting to determine which walls to turn off. Basically, shoot a ray from your camera, and set the transparency of the wall it intersects.

For a simplified case, see updateWallTransparency_simple in the snippet below. It fires a ray from the center of the camera, and sets the transparency of the intersected wall.

However, you'll probably encounter a literal corner case, where you transition from one wall into the next. You can compensate for this with a second raycast, but you need to offset the two rays from the center, so they won't intersect the same wall in the case of a corner. See updateWallTransparency_corners in the snippet below for an example.

Now this is a highly simplified case. If your wall shapes are more complex, you'll need to perform further checks to see if certain walls should be hidden or not. Also, if you tip the camera too far up, you'll notice it will make the back walls hidden instead. There is a lot more to consider for this problem as a whole, but this should get you started.

var renderer, scene, camera, controls, stats;

var WIDTH = window.innerWidth,
  HEIGHT = window.innerHeight,
  FOV = 35,
  NEAR = 1,
  FAR = 1000,
  degrad = (Math.PI / 180),
  walls = [];

var raycaster = null,
  coords = null;

function updateWallTransparency_simple() {
  // reset all walls. You can optimize this by storing which ones are transparent/not
  walls.forEach(function(wall) {
    wall.material.opacity = 1;
  });

  raycaster.setFromCamera(coords, camera);
  var intersects = raycaster.intersectObjects(walls);
  if (intersects && intersects.length > 0) {
    intersects[0].object.material.opacity = 0.25;
  }
}

function updateWallTransparency_corners() {
  // reset all walls. You can optimize this by storing which ones are transparent/not
  walls.forEach(function(wall) {
    wall.material.opacity = 1;
  });

  coords.set(-0.1, 0);
  raycaster.setFromCamera(coords, camera);
  var intersects = raycaster.intersectObjects(walls);
  if (intersects && intersects.length > 0) {
    intersects[0].object.material.opacity = 0.25;
  }

  coords.set(0.1, 0);
  raycaster.setFromCamera(coords, camera);
  var intersects = raycaster.intersectObjects(walls);
  if (intersects && intersects.length > 0) {
    intersects[0].object.material.opacity = 0.25;
  }
}

function populateScene() {
  var geo = new THREE.BoxBufferGeometry(20, 20, 0.1),
    mat = new THREE.MeshPhongMaterial({
      color: "darkred",
      transparent: true,
      opacity: 1
    });
  var mesh = new THREE.Mesh(geo, mat);
  mesh.position.set(0, 0, -10);
  mesh.name = "backWall";
  scene.add(mesh);
  mesh.updateMatrixWorld(true);
  walls.push(mesh);

  mesh = new THREE.Mesh(geo, mat.clone());
  mesh.name = "frontWall";
  mesh.position.set(0, 0, 10);
  scene.add(mesh);
  mesh.updateMatrixWorld(true);
  walls.push(mesh);

  mesh = new THREE.Mesh(geo, mat.clone());
  mesh.name = "leftWall";
  mesh.position.set(-10, 0, 0);
  mesh.rotation.set(0, (Math.PI / 180) * 90, 0);
  scene.add(mesh);
  mesh.updateMatrixWorld(true);
  walls.push(mesh);

  mesh = new THREE.Mesh(geo, mat.clone());
  mesh.name = "rightWall";
  mesh.position.set(10, 0, 0);
  mesh.rotation.set(0, (Math.PI / 180) * 90, 0);
  scene.add(mesh);
  mesh.updateMatrixWorld(true);
  walls.push(mesh);

  geo = new THREE.BoxBufferGeometry(50, 0.1, 50),
    mat = new THREE.MeshPhongMaterial({
      color: "green"
    });
  mesh = new THREE.Mesh(geo, mat);
  mesh.position.set(0, -10, 0);
  mesh.name = "ground";
  scene.add(mesh);

  geo = new THREE.TorusKnotBufferGeometry(2, 0.5, 100, 16),
    mat = new THREE.MeshPhongMaterial({
      color: "blue"
    });
  mesh = new THREE.Mesh(geo, mat);
  mesh.name = "insideObject";
  scene.add(mesh);
}

function init() {
  document.body.style.backgroundColor = "lightBlue";

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

  document.body.appendChild(renderer.domElement);
  document.body.style.overflow = "hidden";
  document.body.style.margin = "0";
  document.body.style.padding = "0";

  scene = new THREE.Scene();

  camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
  camera.position.z = 50;
  scene.add(camera);

  controls = new THREE.OrbitControls(camera);
  controls.enableZoom = false;
  controls.enablePan = false;
  controls.maxPolarAngle = Math.PI / 2;

  var light = new THREE.PointLight(0xffffff, 1, Infinity);
  camera.add(light);

  light = new THREE.HemisphereLight(0xffffbb, 0x00ff00, 1);
  scene.add(light);

  stats = new Stats();
  stats.domElement.style.position = 'absolute';
  stats.domElement.style.top = '0';
  document.body.appendChild(stats.domElement);

  resize();
  window.onresize = resize;

  populateScene();

  raycaster = new THREE.Raycaster();
  coords = new THREE.Vector3();

  animate();
}

function resize() {
  WIDTH = window.innerWidth;
  HEIGHT = window.innerHeight;
  if (renderer && camera && controls) {
    renderer.setSize(WIDTH, HEIGHT);
    camera.aspect = WIDTH / HEIGHT;
    camera.updateProjectionMatrix();
  }
}

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

function animate() {
  requestAnimationFrame(animate);
  //updateWallTransparency_simple();
  updateWallTransparency_corners();
  render();
  stats.update();
}

function threeReady() {
  init();
}

(function() {
  function addScript(url, callback) {
    callback = callback || function() {};
    var script = document.createElement("script");
    script.addEventListener("load", callback);
    script.setAttribute("src", url);
    document.head.appendChild(script);
  }

  addScript("https://threejs.org/build/three.js", function() {
    addScript("https://threejs.org/examples/js/controls/OrbitControls.js", function() {
      addScript("https://threejs.org/examples/js/libs/stats.min.js", function() {
        threeReady();
      })
    })
  })
})();

Upvotes: 3

Related Questions