Hide
Hide

Reputation: 3317

three.js change object dynamically related to distance

I made a graphical website with three.js.

It's concept is universe that has so many texts.

When distance of between camera and mesh is close, mesh is text.

But if distance is far, it change to square.

I wonder that change mesh related to distance is possible?

I searched in google few hours, there is no information about this.

code here:

// Define Variables
var myElement = document.getElementById("threejs");
let camera, scene, renderer;
const mouse = new THREE.Vector2();
clicked = new THREE.Vector2();
const target = new THREE.Vector2();
const windowHalf = new THREE.Vector2( window.innerWidth / 2, window.innerHeight / 2 );
const moveState = {forward: 0, back: 0};
var isMobile = false;
var hold = -1;

/****** Define Function ******/
/*****************************/

checkMobile = () => {
  var UserAgent = navigator.userAgent;

  if (UserAgent.match(/iPhone|iPod|Android|Windows CE|BlackBerry|Symbian|Windows Phone|webOS|Opera Mini|Opera Mobi|POLARIS|IEMobile|lgtelecom|nokia|SonyEricsson/i) != null || UserAgent.match(/LG|SAMSUNG|Samsung/) != null) {
      isMobile = true;
  } else {
      isMobile = false;
  }
}
checkMobile();

onMouseMove = (event) => {
  mouse.x = ( (event.clientX/2) - (windowHalf.x/2) );
  mouse.y = ( (event.clientY/2) - (windowHalf.y/2) );
  clicked.x = ( event.clientX / window.innerWidth ) * 2 - 1;
  clicked.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}


onResize = (event) => {
  const width = window.innerWidth;
  const height = window.innerHeight;
  
  windowHalf.set( width / 2, height / 2 );
	
  camera.aspect = width / height;
  camera.updateProjectionMatrix();
  renderer.setSize( width, height );
}

onContextMenu = (event) => { 
  event.preventDefault();
}

onMouseDown = (event) => {
  hold = event.which;
}

onMouseUp = () => {
  hold = -1;
};

// TEST

//

// Start Script
init = () => {
  camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 5000 );
  camera.position.x = 0;
  camera.position.y = 0;
  camera.position.z = 1000;

  scene = new THREE.Scene();

  const geometry = new THREE.BoxBufferGeometry();
  const material = new THREE.MeshNormalMaterial({ transparent: true });
  
  if(isMobile) {
    var controls = new THREE.DeviceOrientationControls(camera);
  } else {
    console.log('isMobile false');
  }
  
  group = new THREE.Group();
  
  for ( let i = 0; i < 800; i ++ ) {   
    let sprite = new THREE.TextSprite({
      textSize: 2,
      redrawInterval: 1,
      texture: {
        text: 'TEST',
        fontFamily: 'Arial, Helvetica, sans-serif',
      },
      material: {
        color: 'white',
      },
    });
    sprite.position.x = Math.random() * 180-100;
    sprite.position.y = Math.random() * 180-100;
    sprite.position.z = Math.random() * 1000-40;
    group.add(sprite);

  }
  scene.add(group);

  renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
  renderer.setSize( window.innerWidth, window.innerHeight );
  document.body.appendChild( renderer.domElement );
  
  // Event handler
  document.addEventListener('mousemove', onMouseMove, false);
  document.addEventListener('mousedown', onMouseDown, false);
  document.addEventListener('mouseup', onMouseUp, false);
  document.addEventListener('contextmenu', onContextMenu, false);
  
  window.addEventListener('resize', onResize, false);
  
  // Helper
  var axesHelper = new THREE.AxesHelper( 15 );
  scene.add( axesHelper );

  animate = () => {
    // For camera follow mouse cursor
    target.x = ( 1 - mouse.x ) * 0.002;
    target.y = ( 1 - mouse.y ) * 0.002;
    camera.rotation.x += 0.05 * ( target.y - camera.rotation.x );
    camera.rotation.y += 0.05 * ( target.x - camera.rotation.y );
    
    if(isMobile) {
      controls.update();
    }
    switch(hold) {
      case 1:
        if(camera.position.z > 0) {
          camera.position.z -= 4;
        }
        break;
      case 3:
        camera.position.z += 4;
        break;
    }
    
    // Object opacity related to distance between camera and object
    for (i = 0; i < 800; i++) {
      var distance = camera.position.distanceTo(group.children[i].position);
      var opacity = -1 / 400 * distance + 1;
      if (opacity < 0) {
        opacity = 0;
      }
      group.children[i].material.opacity = opacity;
    }
    
    requestAnimationFrame( animate );
    renderer.render( scene, camera );
  }
  // Run
  animate();
}
// Run
init();
canvas {
  width: 100%;
  height: 100%;
  
/*  background: #11e8bb;  Old browsers */
/*  background: -moz-linear-gradient(top,  #11e8bb 0%, #8200c9 100%);  FF3.6-15 */
/*  background: -webkit-linear-gradient(top,  #11e8bb 0%,#8200c9 100%);  Chrome10-25,Safari5.1-6 */
/*  background: linear-gradient(to bottom,  #11e8bb 0%,#8200c9 100%);  W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
  background: radial-gradient(circle, #ed1654, #f61e6c, #f76098);
  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#11e8bb', endColorstr='#8200c9',GradientType=0 ); /* IE6-9 */
}

body {
  margin: 0;
}
canvas {
  width: 100%;
  height: 100%;
}
#threejs {
  position: absolute;
  overflow: hidden;
  width: 100%;
  height: 100%;
}
header {
  position: fixed;
  z-index: 9999;
  background-color: white;
  width: 100%;
  top: 0;
  display: flex;
  align-items: center;
  height: 50px;
}
/* Header Left */
.header-left {
  display: flex;
  justify-content: center;
  flex: 1;
}
.header-left img {
  width: 80px;
  height: 20px;
}
/* Header Right */
.header-right {
  flex: 1;
  padding-left: 200px;
}
.header-right a {
  text-decoration: none;
  color: black;
  font-weight: 600;
}
.header-right a:nth-child(2) {
  padding-left: 50px;
  padding-right: 50px;
}
/* Main Company */
.down-btn {
  display: flex;
  position: absolute;
  justify-content: center;
  align-items: center;
  bottom: 0;
  color: white;
  left: 50%;
  font-size: 2rem;
  cursor: pointer;
}
.down-btn a {
  text-decoration: none;
  color: white;
  padding-bottom: 20px;
}
/* Section */
section {
  background-color: aliceblue;
  height: 100%;
}
<html>
<head>
  <link rel="stylesheet" type="text/css" href="style.css">
<!--  <script src="three.js"></script>-->
  <script src="https://cdn.rawgit.com/mrdoob/three.js/master/build/three.min.js"></script> 
  <script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>
  <script src="https://unpkg.com/three.texttexture"></script>
  <script src="https://unpkg.com/three.textsprite"></script>
  <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
</head>
<body>
  <header>
    <div class="header-left">
      <a href="#">MAIN</a>
    </div>
    <div class="header-right">
      <a href="#">ABOUT</a>
      <a href="#">PRODUCT</a>
      <a href="#">CONTACT</a>
    </div>
  </header>
  <div id="threejs"></div>
  <script src="src.js"></script>
  <div class="down-btn">
    <a href="#section">&darr;</a>
  </div>
  <section id="section">
    SECTION
  </section>
</body>
</html>

(Mouse left click : move forward / right click : move backward) I implemented almost done except change mesh.

Is it possible or any solution about this issue?

Thanks.

Upvotes: 1

Views: 3095

Answers (1)

Brakebein
Brakebein

Reputation: 2237

You can use the THREE.LOD() (Level of Detail) to replace the a mesh by another mesh at a certain distance.

The example (https://threejs.org/examples/#webgl_lod) uses only same type of geometry for the different distances. But if you look into the code, each distance has its own geometry and mesh instance.

And so can you, to change to 100% different mesh.

// create meshes and LOD objects

for ( let i = 0; i < 800; i ++ ) {

  let lod = new THREE.LOD();

  let sprite = new THREE.TextSprite(...);

  let squareGeo = new THREE.PlaneBufferGeometry(2,2),
      squareMat = new THREE.MeshBasicMaterial(),
      square = new THREE.Mesh(squareGeo, squareMat);

  lod.addLevel(sprite, 1);
  lod.addLevel(square, 100); // will be visible from 100 and beyond

  lod.position.x = Math.random() * 180-100;
  lod.position.y = Math.random() * 180-100;
  lod.position.z = Math.random() * 1000-40;

  group.add(lod);
}

// animation loop
function animate() {

  // ...

  group.children.forEach(function (child) {
    // LOD update
    child.update(camera);

    // opacity
    var distance = camera.position.distanceTo(child.position);
    var opacity = -1 / 400 * distance + 1;
    if (opacity < 0) {
      opacity = 0;
    }
    child.getObjectForDistance(distance).material.opacity = opacity;

  });

  requestAnimationFrame( animate );
  renderer.render( scene, camera );

}

EDIT: LOD class modification for smooth transition

addLevel: function ( object, distance, fadeDistance ) {

    ...

    levels.splice( l, 0, { distance: distance, fadeDistance: fadeDistance || distance, object: object } );

    ...

}

update: function () {

    ...

    levels[ 0 ].object.visible = true;
    levels[ 0 ].object.material.opacity = 1.0;

    for ( var i = 1, l = levels.length; i < l; i ++ ) {

        if ( distance >= levels[ i ].distance && distance < levels[ i ].fadeDistance ) {

            levels[ i ].object.visible = true;

            var t = (distance - levels[i].distance) / (levels[i].fadeDistance - levels[i].distance);
            levels[ i - 1 ].object.material.opacity = 1.0 - t;
            levels[ i ].object.material.opacity = t;

        } else if ( distance >= levels[ i ].fadeDistance ) {

            levels[ i - 1 ].object.visible = false;
            levels[ i ].object.visible = true;
            levels[ i ].object.material.opacity = 1.0;

        } else {

            break;

        }

    }

    ...

}

Of course, material.transparent property of objects should be set true, so opacity will work.

Adding square to LOD object

lod.addLevel(sprite, 1);
lod.addLevel(star, 100, 140); // will fade in at distance of 100 to 140, fully visible beyond 140
lod.addLevel(dummy, 200, 400); // if dummy is an object with empty geometry, star will fade out between 200 and 400

Remove opacity modification in animation loop.

Upvotes: 2

Related Questions