5

I am trying out the following using three.js, where I have 2 shapes: “squares” and a “sphere”.

Each object has a button associated with it. If the user clicks a button, the corresponding shape will be added to the scene however, there can only be 1 square and 1 sphere in a scene at any one time.

If the user adds a new square, it should replace the old one. I seem to be having trouble achieving this though with my code below. Currently, I can successfully add the square and sphere objects however, when I click to add another square, the current square doesn’t get replaced.

Please advise, thank you!

<html>
  <head>
    <title>My second three.js app</title>
    <style>
      body { margin: 0; }
      canvas { width: 100%; height: 100% }
    </style>
  </head>
  <body>
    <button id="button1">Square1</button>
    <button id="button2">Square2</button>
    <button id="button3">Sphere</button>
    <script src="js/three.js"></script>
    <script>
  var scene = new THREE.Scene();
  var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );

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

  loader.load( ‘square1.glb’, function ( geometry ) {

    geometry.scene.traverse( function ( node ) {

        if ( node.isMesh ){
            square = node;
            square.material.map.anisotropy = maxAnisotropy;
        }

    document.getElementById("button1").addEventListener("click", function(){
      scene.remove(square);
      scene.add(square);

    });

  });

  loader.load( ‘square2.glb’, function ( geometry ) {

    geometry.scene.traverse( function ( node ) {

      if ( node.isMesh ){
          square = node;
          square.material.map.anisotropy = maxAnisotropy;
      }

    document.getElementById("button2").addEventListener("click", function(){
      scene.remove(square);
      scene.add(square);

    });

  });

  loader.load( ‘sphere.glb’, function ( geometry ) {

    geometry.scene.traverse( function ( node ) {

      if ( node.isMesh ){
          sphere = node;
          sphere.material.map.anisotropy = maxAnisotropy;
      }

    document.getElementById("button3").addEventListener("click", function(){

    scene.add(sphere);

    });

  });

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

  animate();
</script>
  </body>
</html>

1 Answer 1

5

Consider tracking the "current square" shape outside the scope of your <button> click handlers, to ensure that the logic for adding and removing these shape from the scene functions as expected.

Specifically, you'll want to call scene.remove() with the current square shape (if present), rather than to call it with the shape object that you subsequently call scene.add() with:

/* Current code */
document.getElementById("button1").addEventListener("click", function(){

  scene.remove(square); /* You're removing the square object here, */

  scene.add(square); /* and then adding the same square object to the scene */
});

You might find it useful to adapt your script as follows:

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );

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

/*
Not sure where you get your loader object from
*/
var loader = ''; // ??

/* 
Add this to track current shapes
*/
var currentSquare = '';
var currentSphere = '';

/*
Define a general, reusable method for loading geometry
*/
function loadShape(filename, onLoaded) {

  loader.load( filename, function ( geometry ) {

        var foundMesh = '';

        geometry.scene.traverse( function ( node ) {
            if ( node.isMesh ){
                foundMesh = node;
                foundMesh.material.map.anisotropy = maxAnisotropy;
            }
        });

        if(foundMesh) {
            onLoaded(foundMesh)
        }
    }
}

/*
Use the general loader method defined above
*/
loadShape('square1.glb', function(nextSquare) {

    document.getElementById("button1").addEventListener("click", function(){

        /*
        If current square exists, remove it
        */
        if(currentSquare) {
            scene.remove(currentSquare);
        }

        /*
        Add the square for the button just clicked 
        and update currentSquare
        */
        scene.add(nextSquare);  
        currentSquare = nextSquare;
    });
})

loadShape('square2.glb', function(nextSquare) {

    document.getElementById("button2").addEventListener("click", function(){

        if(currentSquare) {
            scene.remove(currentSquare);
        }

        scene.add(nextSquare);  
        currentSquare = nextSquare;
    });
})

loadShape('sphere.glb', function(nextSphere) {

    document.getElementById("button3").addEventListener("click", function(){

        if(currentSphere) {
            scene.remove(currentSphere);
        }

        scene.add(nextSphere);  
        currentSphere = nextSphere;
    });
})

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

animate();

Hope this helps!

Sign up to request clarification or add additional context in comments.

4 Comments

Amazing! I really like the general reusable function, makes debugging a lot easier. Just another question while we're at it: wouldn't there be performance issues if I keep adding the same object to the scene? For example, if I was trigger happy, I would click button1 and add 100 squares...
You're welcome :-) . Just to clarify - you won't be able to add the same instance of an object to the scene, however if you load multiple copies of the square, then you could add these to your scene. Eventually your app will slow down if too many squares are present, however 100 squares should be quite manageable. Hope that makes sense :-)
Ahh i think I understand. Since the loaders are only "called" once, there will only ever be one instance of each object reagardless of how many times I click. In this case, if I did click 100 times, there would still only be 1 square1.glb in the scene.
Yup, you got it :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.