1
votes

I'm new to three.js and I'd like to learn how to cycle through materials onclick. This example shows what I'd like to achieve except it's cycling through textures rather than materials.

I'd like to have the materials in an array, then be able to click the canvas to cycle and loop through the materials.

Here's the code for the above example:

var scene = new THREE.Scene();
scene.background = new THREE.Color( 0xcccccc );

var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
camera.position.y = 5;
camera.position.x = 5;
camera.lookAt(new THREE.Vector3(0,0,0)); // Make the camera look at the point of origin

var renderer = new THREE.WebGLRenderer({antialias:true});
var devicePixelRatio = window.devicePixelRatio || 1; // To handle high pixel density displays

renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(devicePixelRatio);

document.body.appendChild( renderer.domElement );

var render = function () {

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

// instantiate a texture loader
var loader = new THREE.TextureLoader();
//allow cross origin loading
loader.crossOrigin = '';

// The textures to use
var arr = [
    'https://s3-us-west-2.amazonaws.com/s.cdpn.io/259155/THREE_gates.jpg',
    'https://s3-us-west-2.amazonaws.com/s.cdpn.io/259155/THREE_crate1.jpg',
    'https://s3-us-west-2.amazonaws.com/s.cdpn.io/259155/THREE_crate2.jpg'
];
var textureToShow = 0;

// Load the first texture
// var texture = loadTexture('https://s3-us-west-2.amazonaws.com/s.cdpn.io/259155/MarbleSurface.jpg');

// Instantiate the material we will be using
var material = new THREE.MeshBasicMaterial();
// Instantiate a geometry to use
var geometry = new THREE.BoxGeometry( 1, 1, 1 );
// Instatiate the mesh with the geometry and material
var cube = new THREE.Mesh( geometry, material );
cube.position.y = 0.5;

// Then load the texture
loader.load(arr[textureToShow], function(tex) {
    // Once the texture has loaded
    // Asign it to the material
    material.map = tex;
    // Update the next texture to show
    textureToShow++;
    // Add the mesh into the scene
    scene.add( cube );
});

// Click interaction
var canvas = document.getElementsByTagName("canvas")[0];

canvas.addEventListener("click", function() {

    loader.load(arr[textureToShow], function(tex) {
        // Once the texture has loaded
        // Asign it to the material
        material.map = tex;
        // Update the next texture to show
        textureToShow++;
        // Have we got to the end of the textures array
        if(textureToShow > arr.length-1) {
            textureToShow = 0;
        }
    }); 
});

// Start rendering the scene
render();

Here's the code I can't get working:

        var container, stats;
    var camera, scene, renderer;
    var mouseX = 0,
        mouseY = 0;
    var windowHalfX = window.innerWidth / 2;
    var windowHalfY = window.innerHeight / 2;
    var globalObject;

    init();
    animate();

    function init() {
        container = document.createElement('div');
        document.body.appendChild(container);

        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
        camera.position.z = 500;

        //scene
        scene = new THREE.Scene();

        ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
        scene.add(ambientLight);

        pointLight = new THREE.PointLight(0xff0000, 0.5);
        pointLight.position.z = 2500;
        scene.add(pointLight);

        var pointLight2 = new THREE.PointLight(0xff6666, 1);
        camera.add(pointLight2);

        var pointLight3 = new THREE.PointLight(0x0000ff, 0.5);
        pointLight3.position.x = -1000;
        pointLight3.position.z = 1000;
        scene.add(pointLight3);

        //manager
        var manager = new THREE.LoadingManager();
        manager.onProgress = function(item, loaded, total) {
            console.log(item, loaded, total);
        };

        //materials
        var path = "textures/cube/skybox/";
        var format = '.jpg';
        var urls = [
            path + 'px' + format, path + 'nx' + format,
            path + 'py' + format, path + 'ny' + format,
            path + 'pz' + format, path + 'nz' + format
        ];

        var reflectionCube = new THREE.CubeTextureLoader().load(urls);

        var material = new THREE.MeshStandardMaterial({
            envMap: reflectionCube,
            roughness: 0.1,
            metalness: 1,
            color: 0xfee6af,
        });

        var materialTwo = new THREE.MeshStandardMaterial({
            envMap: reflectionCube,
            roughness: 0.1,
            metalness: 0,
            color: 0xffff00,
        });




        //model
        var loader = new THREE.OBJLoader(manager);
        loader.load('logo97.obj', function(object) {
            //store global reference to .obj
            globalObject = object;

            object.traverse(function(child) {
                if (child instanceof THREE.Mesh) {
                    child.material = material;
                    child.material.needsUpdate = true;
                }
            });

            object.position.y = 0;
            scene.add(object);
        });

        //render
        renderer = new THREE.WebGLRenderer({
            antialias: true,
            alpha: true
        });
        renderer.setSize(window.innerWidth, window.innerHeight);
        container.appendChild(renderer.domElement);

        document.addEventListener('mousemove', onDocumentMouseMove, false);
        window.addEventListener('resize', onWindowResize, false);
    }

    function onWindowResize() {
        windowHalfX = window.innerWidth / 2;
        windowHalfY = window.innerHeight / 2;
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
    }

    function onDocumentMouseMove(event) {
        mouseX = (event.clientX - windowHalfX) / 2;
        mouseY = (event.clientY - windowHalfY) / 2;
    }

    // Click interaction
    var canvas = document.getElementsByTagName("canvas")[0];

    canvas.addEventListener("click", function() {

         var materials = [
            new THREE.MeshBasicMaterial( {color:'#b02030'} ),
            new THREE.MeshLambertMaterial( {color:'#b02030'} ), 
            new THREE.MeshPhongMaterial( {color:'#b02030', shininess: 100 } ),
            new THREE.MeshNormalMaterial(),
            // ...
        ];
        var materialToShow = 0;

        materialToShow ++;
        if ( materialToShow >= materials.length ) materialToShow = 0;
        child.material = materials[materialToShow]; 


    });

    //animate
    function animate() {
        requestAnimationFrame(animate);
        render();
    }

    function render() {
        camera.position.x += (mouseX - camera.position.x) * .05;
        camera.position.y += (-mouseY - camera.position.y) * .05;
        camera.lookAt(scene.position);
        renderer.render(scene, camera);
    }
1

1 Answers

1
votes

You have to create an array of THREE.Materials:

e.g.

var materials = [
    new THREE.MeshBasicMaterial( {color:'#b02030'} ),
    new THREE.MeshLambertMaterial( {color:'#b02030'} ), 
    new THREE.MeshPhongMaterial( {color:'#b02030', shininess: 100 } ),
    new THREE.MeshNormalMaterial(),
    // ...
];
var materialToShow = 0;

In the "click" can change the current material of the THREE.Mesh by changing the property .material.

canvas.addEventListener("click", function() {

    materialToShow ++;
    if ( materialToShow >= materials.length ) materialToShow = 0;
    mesh.material = materials[materialToShow];   
});

Of course you have to add some light (e.g. THREE.AmbientLight, THREE.DirectionalLight) to the scne, to make the behaviour of the different materials "visible".

See the example, where I applied the suggested changes to your original code.
I used a sphere, to represent the specular highlight of the THREE.MeshPhongMaterial.
And I removed the texture, but of course you can apply the texture, too.

var scene = new THREE.Scene();
scene.background = new THREE.Color( 0xcccccc );

var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.y = 4;
camera.lookAt(new THREE.Vector3(0,0,0)); // Make the camera look at the point of origin

var renderer = new THREE.WebGLRenderer({antialias:true});
var devicePixelRatio = window.devicePixelRatio || 1; // To handle high pixel density displays

renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(devicePixelRatio);

document.body.appendChild( renderer.domElement );
window.onresize = resize;

var ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);

var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
directionalLight.position.set(1,2,-1.0);
scene.add( directionalLight );

var render = function () {

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

var materials = [
    new THREE.MeshBasicMaterial( {color:'#b02030'} ),
    new THREE.MeshLambertMaterial( {color:'#b02030'} ), 
    new THREE.MeshPhongMaterial( {color:'#b02030', shininess: 100 } ),
    new THREE.MeshNormalMaterial(),
    // ...
];
var materialToShow = 0;

var geometry = new THREE.SphereGeometry( 1, 32, 16 );
var mesh = new THREE.Mesh( geometry, materials[materialToShow] );
scene.add( mesh );

// Click interaction
var canvas = document.getElementsByTagName("canvas")[0];

canvas.addEventListener("click", function() {

    materialToShow ++;
    if ( materialToShow >= materials.length ) materialToShow = 0;
    mesh.material = materials[materialToShow];   
});

function resize() {
    
    var aspect = window.innerWidth / window.innerHeight;
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = aspect;
    camera.updateProjectionMatrix();
}

// Start rendering the scene
render();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/99/three.min.js"></script>

Since you have used the THREE.OBJLoader, you have to change of the .material of the .children of the root object globalObject (Object3D). Of course materialToShow and materials have to be initialized only once:

var materials = [
    new THREE.MeshBasicMaterial( {color:'#b02030'} ),
    new THREE.MeshLambertMaterial( {color:'#b02030'} ), 
    new THREE.MeshPhongMaterial( {color:'#b02030', shininess: 100 } ),
    new THREE.MeshNormalMaterial(),
    // ...
];
var materialToShow = 0;
canvas.addEventListener("click", function() {

    materialToShow ++;
    if ( materialToShow >= materials.length ) materialToShow = 0;

    globalObject.traverse(function(child) {
        if (child instanceof THREE.Mesh) {
            child.material = materials[materialToShow];
            child.material.needsUpdate = true;
        }
    });
});

See the example, where I applied the changes to your original code, with some changes to make it work in here:

var container, stats;
var camera, scene, renderer;
var mouseX = 0,
    mouseY = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
var globalObject;

init();
animate();

function init() {
    container = document.createElement('div');
    document.body.appendChild(container);

    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
    camera.position.y = 3;
    camera.position.z = 3;

    //scene
    scene = new THREE.Scene();

    ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
    scene.add(ambientLight);

    var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
    directionalLight.position.set(0.5,2,-1.0);
    scene.add( directionalLight );

    pointLight = new THREE.PointLight(0xff0000, 0.5);
    pointLight.position.z = 10;
    scene.add(pointLight);

    var pointLight2 = new THREE.PointLight(0xff6666, 1);
    camera.add(pointLight2);

    var pointLight3 = new THREE.PointLight(0x0000ff, 0.5);
    pointLight3.position.x = -10;
    pointLight3.position.z = 10;
    scene.add(pointLight3);

    //manager
    var manager = new THREE.LoadingManager();
    manager.onProgress = function(item, loaded, total) {
        console.log(item, loaded, total);
    };

    //materials
    /*
    var path = "textures/cube/skybox/";
    var format = '.jpg';
    var urls = [
        path + 'px' + format, path + 'nx' + format,
        path + 'py' + format, path + 'ny' + format,
        path + 'pz' + format, path + 'nz' + format
    ];
    */

    //var reflectionCube = new THREE.CubeTextureLoader().load(urls);

    var material = new THREE.MeshStandardMaterial({
        //envMap: reflectionCube,
        roughness: 0.1,
        metalness: 1,
        color: 0xfee6af,
    });

    var materialTwo = new THREE.MeshStandardMaterial({
        //envMap: reflectionCube,
        roughness: 0.1,
        metalness: 0,
        color: 0xffff00,
    });


    makeTextFile = function (text) {
      var data = new Blob([text], {type: 'text/plain'});
      var textFile = window.URL.createObjectURL(data);
      return textFile;   
    }
    var textbox_obj = document.getElementById('plane_obj');
    var obj_url = makeTextFile(textbox_obj.value);


    //model
    var loader = new THREE.OBJLoader(manager);
    loader.load(obj_url, function(object) {
        //store global reference to .obj
        globalObject = object;

        object.traverse(function(child) {
            if (child instanceof THREE.Mesh) {
                child.material = material;
                child.material.needsUpdate = true;
            }
        });

        object.position.y = 0;
        scene.add(object);
    });

    //render
    renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true
    });
    renderer.setSize(window.innerWidth, window.innerHeight);
    container.appendChild(renderer.domElement);

    document.addEventListener('mousemove', onDocumentMouseMove, false);
    window.addEventListener('resize', onWindowResize, false);
}

function onWindowResize() {
    windowHalfX = window.innerWidth / 2;
    windowHalfY = window.innerHeight / 2;
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
}

function onDocumentMouseMove(event) {
    mouseX = (event.clientX - windowHalfX) / 2;
    mouseY = (event.clientY - windowHalfY) / 2;
}

// Click interaction
var canvas = document.getElementsByTagName("canvas")[0];

var materials = [
    new THREE.MeshBasicMaterial( {color:'#b02030'} ),
    new THREE.MeshLambertMaterial( {color:'#b02030'} ), 
    new THREE.MeshPhongMaterial( {color:'#b02030', shininess: 100 } ),
    new THREE.MeshNormalMaterial(),
    // ...
];
var materialToShow = 0;
canvas.addEventListener("click", function() {

     materialToShow ++;
    if ( materialToShow >= materials.length ) materialToShow = 0;
        
    globalObject.traverse(function(child) {
        if (child instanceof THREE.Mesh) {
            child.material = materials[materialToShow];
            child.material.needsUpdate = true;
        }
    });
});

//animate
function animate() {
    requestAnimationFrame(animate);
    render();
}

function render() {
    //camera.position.x += (mouseX - camera.position.x) * .05;
    //camera.position.y += (-mouseY - camera.position.y) * .05;
    camera.lookAt(scene.position);
    renderer.render(scene, camera);
}
<textarea id="plane_obj" style="display:none;">
# Blender v2.77 (sub 0) OBJ File: 'Plane.blend'
# www.blender.org
mtllib Plane.mtl
o Plane

v -1.000000 0.000000 1.000000
v 1.000000 0.000000 1.000000
v -1.000000 0.000000 -1.000000
v 1.000000 0.000000 -1.000000

vt 0.000000 0.000000
vt 1.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 1.000000

vn 0.0000 1.0000 0.0000

usemtl Material
s off
f 1/1/1 2/2/1 4/4/1 3/3/1
</textarea>

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/99/three.min.js"></script>
<script src="https://rawgit.com/mrdoob/three.js/dev/examples/js/loaders/OBJLoader.js"></script>