Three.js: Proper way to add and remove child objects using THREE.SceneUtils.attach/detach functions

12,343

Solution 1

your line of code

   THREE.SceneUtils.detach(cubeMesh[i], scene, parentCube);

should be

   THREE.SceneUtils.detach(cubeMesh[i], parentCube, scene);

I have done a demo with your example, and what I believe should be te correct approach.

HTML

<body>
    <button onclick="attachChild();">attach</button>
    <button onclick="detachChild();">dettach</button>
</body>

JavaScript

var camera, scene, renderer;
var geometry, material1, material2;
var parentCube;
var cubeMesh = [];
var cameraControls;
var attached = true;

window.onload = function() {
    init();
    animate();
}

function init() {

    camera = new THREE.PerspectiveCamera(75, 2, 1, 10000);
    camera.position.z = 400;
    camera.position.y = 100;

    scene = new THREE.Scene();

    geometry = new THREE.BoxGeometry(200, 200, 200);
    material1 = new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true  });

    //Create parentCube mesh
    parentCube = new THREE.Mesh(new THREE.CubeGeometry(100, 100, 100, 1, 1, 1), material1);
    scene.add(parentCube);

    //...create materials for the child cubes....
    material2 = new THREE.MeshBasicMaterial({        color: 0x00ff00   });
    //create child cube mesh
    cubeMesh[0] = new THREE.Mesh(new THREE.CubeGeometry(100, 100, 100, 1, 1, 1), material2);
    cubeMesh[1] = new THREE.Mesh(new THREE.CubeGeometry(100, 100, 100, 1, 1, 1), material2);
    cubeMesh[2] = new THREE.Mesh(new THREE.CubeGeometry(100, 100, 100, 1, 1, 1), material2);
    cubeMesh[3] = new THREE.Mesh(new THREE.CubeGeometry(100, 100, 100, 1, 1, 1), material2);

        //--> Set child cube world positions before the attachment to parentCube mesh
    cubeMesh[0].position.set(100,100,0);
    cubeMesh[1].position.set(-100,100,0);
    cubeMesh[2].position.set(-100,-100,0);
    cubeMesh[3].position.set(100,-100,0);

        //Add child cubes to the scene
    for (var i = 0; i < 4; i++)
        parentCube.add(cubeMesh[i]);


   //translate parentCube
   parentCube.position.set(0,50,0);


  renderer = new THREE.CanvasRenderer();
    renderer.setSize(600, 300);

    document.body.appendChild(renderer.domElement);

        // CONTROLS
    cameraControls = new THREE.OrbitControls(camera);
    cameraControls.addEventListener( 'change', render );


}

function animate() {
    cameraControls.update();
    requestAnimationFrame(animate);
    parentCube.rotation.z += 0.01;
    render ();
}

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

}

function attachChild () {
    if (attached) {
        alert ("already attached");
    } else {
        for (var i = 0; i < 4; i++)
            THREE.SceneUtils.attach(cubeMesh[i], scene, parentCube);
        attached = true;
    }
}

function detachChild () {
    if ( ! attached) {
        alert ("not attached");
    } else {
        for (var i = 0; i < 4; i++)
            THREE.SceneUtils.detach(cubeMesh[i], parentCube, scene);
        attached = false;
    }
}

Notice specially that I add the children directly to the parent (not the scene), and that I consider them attached from the beginning. After that, detaching and reattaching them works as expected

How could this be used for a Rubik's cube simulation ?

I would just create all the cubes added to the scene.

Then, to perform a move, you have to

  1. identify the pivot cube (the one at the center of the face)
  2. identify the surrounding cubes
  3. attach those to the pivot
  4. rotate the pivot
  5. detach the cubes

Solution 2

This is an old post but the sake of search engines here are my thoughts about this

Looking at the code for THREE.SceneUtils.attach() it appears to me that the code assumes the object that you want to attach is parented to the scene object. This makes it problematic to work with when your objects are actually nested further down in the scene graph. To address this problem I wrote this function

function reparentObject3D(subject, newParent)
{
    subject.matrix.copy(subject.matrixWorld);
    subject.applyMatrix(new THREE.Matrix4().getInverse(newParent.matrixWorld));
    newParent.add(subject);
}

This allows you to reparent an object from any level of the scene graph to any other level in the scene graph. This dispenses with the need to "Detach" objects. You just reparent them to the scene if thats what you need

Share:
12,343
The_Obfuscator
Author by

The_Obfuscator

Updated on June 07, 2022

Comments

  • The_Obfuscator
    The_Obfuscator almost 2 years

    Using three.js, and adapting instructions from West Langley's post provided here: Three.js: Adding and Removing Children of Rotated Objects, I set up a WebGL scene to which five cube meshes are added. Initially, all objects are children of the scene, then, I attach them to the fifth "parentCube" cube and translate it 100 units along the Y-Axis thereby translating the other four cubes and subsequently detach them.

    After that, I want to independently translate the "parentCube" cube (previously the parent of the four cubes) back to the origin, however, when I perform that translation, the other four cube meshes also translate with the former parent cube mesh, even when I detached them.

    This may be a very basic question, but how can I independently translate "parentCube" without affecting the position of the other cubes considering all of the above details? Where am I going wrong with the detachment? Any help would be appreciated. Thank you :)

    Here's the code sample which I use to perform all of the above:

            //Create parentCube mesh
            var parentCube = new THREE.Mesh(new THREE.CubeGeometry(100, 100, 100, 10, 10, 10), new THREE.MeshBasicMaterial({ color: 0xa1ff11, wireframe: true }));
            scene.add(parentCube);
    
            //...create materials for the child cubes....
    
            //create child cube mesh
            for(var i = 0; i < 4; i++)
                cubeMesh[i] = new THREE.Mesh(new THREE.CubeGeometry(100, 100, 100, 30, 30, 30), materials[i]);
    
            //--> Set child cube world positions before the attachment to parentCube mesh
            cubeMesh[0].position.set((100 / 2),(100 / 2),(100 / 2));
            cubeMesh[1].position.set(-(100 / 2),(100 / 2),(100 / 2));
            cubeMesh[2].position.set(-(100 / 2),-(100 / 2),(100 / 2));
            cubeMesh[3].position.set((100 / 2),-(100 / 2),(100 / 2));
    
            //Add child cubes to the scene
            for(var i = 0; i < cubeMesh.length; i++)
                scene.add(cubeMesh[i]);
    
            //attach child cubeMesh[i] to parentCube mesh
            for(var i = 0; i < 4; i++)
                THREE.SceneUtils.attach(cubeMesh[i], scene, parentCube);
    
            //--> Set positions of child elements after attachment to parentCube
            cubeMesh[0].position.set((100 / 2),(100 / 2),(100 / 2));
            cubeMesh[1].position.set(-(100 / 2),(100 / 2),(100 / 2));
            cubeMesh[2].position.set(-(100 / 2),(100 / 2),-(100 / 2));
            cubeMesh[3].position.set((100 / 2),(100 / 2),-(100 / 2));
    
            //translate parentCube
            parentCube.position.set(0,150,0);
            parentCube.updateMatrixWorld();
    
            //Attempt to detach child objects from parentCube
            //And make them children of the scene
            for(var i = 0; i < 4; i++)
            {
                cubeMesh[i].updateMatrixWorld();
                THREE.SceneUtils.detach(cubeMesh[i], parentCube, scene);
            }
    
            //Attempt to translate parentCube back to origin
            parentCube.position.set(0,0,0);
        }
    
  • The_Obfuscator
    The_Obfuscator about 10 years
    That worked well. Additionally, as I'm quite new to WebGL development, I wanted to know: Is it always necessary to reposition child elements in world space after parenting them to an object (cubeMesh[i] attached to parentCube, in this case), as I've done above? Or is there some easier way of working around that? Whenever I parent objects, the children occupy the position of the parent object even though I assign world space positions to the children prior to the attachment. Thanks in advance.
  • vals
    vals about 10 years
    When you do the attach, the mesh shouldn't change the world coordinates. So, if it has been positioned before, it should keep the global position, and you shouldn't need to do anything else. If this is not the case, please post more of your code, and I'll take a look
  • The_Obfuscator
    The_Obfuscator about 10 years
    I updated the main questions' code to reflect my queries posed in the comments. After creating the cubeMesh[i] child cubes (prior to the attachment to the parentCube), I set their global positions. Post the attachment of cubeMesh[i] to parentCube, I have to again reset their positions in world space (Kindly refer to the arrow-headed commented portions of the code). If I don't do that after the attachment, all the cubeMesh[i] meshes get repositioned at parentCubes' position, thereby losing their original world space positions. Thanks.
  • vals
    vals about 10 years
    I believe that the problem is that you haven't gone thru any render between setting the objects position and attaching them to the parent. Is this a real approach (are you really in need of attach them to the parent just after creation?) if so, why don't add them to the parent instead of to the scene ?
  • The_Obfuscator
    The_Obfuscator about 10 years
    Thanks for the example. Understood a lot from it. I'm actually working on modelling and animating a Rubik's Cube and as such cubeMesh[i] (the child cubes) will be attached and detached on the fly to the parentCube cube/object3D() (Whichever is used).
  • The_Obfuscator
    The_Obfuscator about 10 years
    The entire series of operations of creation, attachment and detachment that I encapsulated in the lines of my code was done in the fillScene() (Or init() method, either way) method. Thus, the respective function was called only once with the scene rendered as usual. Did the error stem from that, somehow?
  • vals
    vals about 10 years
    The main issue is that calling attach on a newly created mesh is not the purpose of the function, for this simply call add(). I believe that the problem with your code is that setting the position doesn't calculate the matrices, but sets a needUpdate flag. If you then call a function that uses the matrices, it will fail. And the matrices are usually automatically updated in render
  • vals
    vals about 10 years
    added explanation to the answer
  • The_Obfuscator
    The_Obfuscator about 10 years
    So, I guess I finally understand why. Thanks for the detailed explanation. In that case, if I understood your explanation correctly, it's always advisable to use attach()/detach() with methods that are invoked in the render loop since the matrices are always updated that way?
  • The_Obfuscator
    The_Obfuscator about 10 years
    Following the steps you outlined in your answer, I designed a working sample of a 2x2x2 Rubiks Cube. The face rotations occur properly when executed independently, however, they get messed up when applied one after the other (The attachment and detachment of child objects to the parent was done properly, and I confirmed that). I started a new thread regarding that and would highly appreciate if you could take a look at it and let me know where I'm going wrong: stackoverflow.com/questions/23485212/…. Many thanks.