Search code examples
javascript3dthree.jstextures

In THREE.js, how to map one texture to a 3D rectangle


I'm trying to make a box in THREE that represents a box of 2x4 Legos, 24 pieces wide by 48 pieces long and and arbitrary number of pieces tall. I've generated a texture that shows this pattern using random colors:

enter image description here

I need to show two sides this cube, but the textures have to align so that the pieces on the edges are the same colors, like so (generated in Blender):

enter image description here

I'd really prefer not to make six images for a CubeTexture, particularly since four are not visible. Is it possible to flip the texture on one side so that they appear to align? (We're just going for visual effect here.)

Further, not all 3D rectangles will be cubes, but I can't quite figure out how to set the texture.repeat.x and texture.repeat.y so that the x is scaled correctly and the y is at the same scale, but just cuts off when the height of the object ends, like so:

enter image description here

Thanks!


Solution

  • You can flip an image by flipping the UVs.

    You'll need to figure out which UVs correspond to the face you're trying to mirror, and which direction to flip them (not sure how your geometry is created).

    Here's an example using a basic BoxBufferGeometry and modifying its uv attribute. (The face on the right is the mirrored-by-UV-flipping face.)

    var textureURL = "https://upload.wikimedia.org/wikipedia/commons/0/02/Triangular_hebesphenorotunda.png";
    // attribution and license here: https://commons.wikimedia.org/wiki/File:Triangular_hebesphenorotunda.png
    
    var renderer = new THREE.WebGLRenderer({antialias:true});
    document.body.appendChild(renderer.domElement);
    renderer.setSize(500, 500);
    
    var textureLoader = new THREE.TextureLoader();
    
    var scene = new THREE.Scene();
    var camera = new THREE.PerspectiveCamera(28, 1, 1, 1000);
    camera.position.set(50, 25, 50);
    camera.lookAt(scene.position);
    scene.add(camera);
    
    camera.add(new THREE.PointLight(0xffffff, 1, Infinity));
    
    var cubeGeo = new THREE.BoxBufferGeometry(20, 20, 20);
    var uvs = cubeGeo.attributes.uv;
    // originally:
    // [0] = 0,1
    // [1] = 1,1
    // [2] = 0,0
    // [3] = 1,0
    // convert to:
    // [0] = 1,1
    // [1] = 0,1
    // [2] = 1,0
    // [3] = 0.0
    uvs.setX(0, 1);
    uvs.setY(0, 1);
    uvs.setX(1, 0);
    uvs.setY(1, 1);
    uvs.setX(2, 1);
    uvs.setY(2, 0);
    uvs.setX(3, 0);
    uvs.setY(3, 0);
    uvs.needsUpdate = true;
    
    var mat = new THREE.MeshLambertMaterial({
      color: "white",
      map: textureLoader.load(textureURL, function(){
        animate();
      })
    });
    var mesh = new THREE.Mesh(cubeGeo, mat);
    scene.add(mesh);
    
    function render() {
      renderer.render(scene, camera);
    }
    
    function animate() {
      requestAnimationFrame(animate);
      render();
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.min.js"></script>