javascriptthree.jsclone

Changing attributes on a cloned object changes all objects


I have loaded an OBJ file and from that original I .clone() it into an array. Later I wish to change the vertexColor of some of them to a different color, but changing one changes them all.

var oload = new OBJLoader();
oload.load("objects/tunnel1.obj", function(data)
{
    tunnels[0] = data;
    tunnels[0].traverse(function(titem)
    {
        if(titem.isMesh)
        {
            if(titem.name.toUpperCase().indexOf("INNER")>-1)
            {
                titem.material = new THREE.MeshBasicMaterial(map: wnewtex, vertexColors: true);
            }
        }
    });
});

for(var x = 0; x < width; x++)
{
    for(var y = 0; y < height; y++)
    {
        if(leveldata[x][y] == 15)
        {
            tunnels.push(tunnels[0].clone());
            tunnels[tunnels.length-1].position.set(-x*2,newh,(height-y-1)*2);
            scene.add(tunnels[tunnels.length-1]);
        }
        else if(leveldata[x][y] == 16)
        {
            tunnels.push(tunnels[0].clone());
            tunnels[tunnels.length-1].position.set(-x*2,newh,(height-y-1)*2);
            tunnels[tunnels.length-1].rotation.set(0,Math.PI/2,0);
            scene.add(tunnels[tunnels.length-1]);
        }
        else if(leveldata[x][y] == 23)
        {
            tunnels.push(tunnels[0].clone());
            tunnels[tunnels.length-1].position.set(-x*2,newh,(height-y-1)*2);
            tunnels[tunnels.length-1].rotation.set(0,Math.PI/2,0);
            tunnels[tunnels.length-1].traverse(function(titem)
            {
                if(titem.isMesh)
                {
                    if(titem.name.toUpperCase().indexOf("INNER")>-1)
                    {
                        titem.geometry.setAttribute("color",new THREE.BufferAttribute(new Float32Array(item.geometry.attributes.position.count*3), 3 ));
                        var colort = titem.geometry.attributes.color.array;
                        for(var v2 = 0; v2 < titem.geometry.attributes.position.count*3; v2+=3)
                        {   // Make Red
                            colort[v2+0] = 1;
                            colort[v2+1] = 0;
                            colort[v2+2] = 0;
                        }
                        titem.geometry.attributes.color.needsUpdate = true;
                    }
                }
            });
            scene.add(tunnels[tunnels.length-1]);
        }
        else if(leveldata[x][y] == 26)
        {
            tunnels.push(tunnels[0].clone());
            tunnels[tunnels.length-1].position.set(-x*2,newh,(height-y-1)*2);
            tunnels[tunnels.length-1].rotation.set(0,Math.PI/2,0);
            tunnels[tunnels.length-1].traverse(function(titem)
            {
                if(titem.isMesh)
                {
                    if(titem.name.toUpperCase().indexOf("INNER")>-1)
                    {
                        titem.geometry.setAttribute("color",new THREE.BufferAttribute(new Float32Array(item.geometry.attributes.position.count*3), 3 ));
                        var colort = titem.geometry.attributes.color.array;
                        for(var v2 = 0; v2 < titem.geometry.attributes.position.count*3; v2+=3)
                        {   // Make Green
                            colort[v2+0] = 0;
                            colort[v2+1] = 1;
                            colort[v2+2] = 0;
                        }
                        titem.geometry.attributes.color.needsUpdate = true;
                    }   // If 26 was the last tunnel adjusted
                }       // then even 23 turns green
            });
            scene.add(tunnels[tunnels.length-1]);
        }
    }
}

I take it .clone() does not make a new copy of the attributes for the new object. What is another method to make a clone where the attributes are not linked?


Solution

  • You are correct that clone maintains references, rather than making copies. And clone calls the object's copy function, so even Mesh.copy keeps references to the original geometry and material, rather than making copies. This is done for memory efficiency.

    Reference: https://github.com/mrdoob/three.js/blob/a2e9ee8204b67f9dca79f48cf620a34a05aa8126/src/objects/Mesh.js#L68 (three.js r164)

    But it's not the end. You can always get around it by dereferencing the geometry and/or material after performing a clone. And clone actually does what you want if you take that single step lower.

    const original = new Mesh( geo, mat )
    const theClone = original.clone()
    
    console.log( original.geometry === theClone.geometry ) // true
    console.log( original.material === theClone.material ) // true
    
    theClone.geometry = theClone.geometry.clone()
    theClone.material = theClone.material.clone()
    
    console.log( original.geometry === theClone.geometry ) // false
    console.log( original.material === theClone.material ) // false