Three.js: Proper way to add and remove child objects using THREE.SceneUtils.attach/detach functions
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
- identify the pivot cube (the one at the center of the face)
- identify the surrounding cubes
- attach those to the pivot
- rotate the pivot
- 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
The_Obfuscator
Updated on June 07, 2022Comments
-
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 about 10 yearsThat 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 about 10 yearsWhen 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 about 10 yearsI 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 about 10 yearsI 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 about 10 yearsThanks 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 about 10 yearsThe 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 about 10 yearsThe 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 about 10 yearsadded explanation to the answer
-
The_Obfuscator about 10 yearsSo, 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 about 10 yearsFollowing 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.