wsgg
wsgg

Reputation: 984

Fit to scale mapped texture on plane in THREE.js

I'm trying 'fit-to-scale' a mapped image texture on a single plane. kinda simulating the behaviour of object-fit:cover.

So the image map has to scale up or down propotionally to cover the whole plane completely.

I tried playing around with the repeat and offset of the texture but was unsuccessful. (commented out in my code)

As you can see what i have so far in the snippet below, still has the image stretch to fit. Any help is appreciated!

var renderer = new THREE.WebGLRenderer({ canvas : document.getElementById('canvas'), antialias:true});
    renderer.setClearColor(0x7b7b7b);
    //  use device aspect ratio //
    renderer.setPixelRatio(window.devicePixelRatio);
    // set size of canvas within window 
    renderer.setSize(window.innerWidth, window.innerHeight);

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

    // CAMERA
    var camera = new THREE.PerspectiveCamera( 45, window.innerWidth/window.innerHeight, 0.1, 1000 );
    camera.position.z = 5;




    // MESH 0

    // texture
  var texture_0 = new THREE.  TextureLoader().load("https://i.imgur.com/YO0ygMx.jpg");
  texture_0.wrapS = THREE.ClampToEdgeWrapping;
  texture_0.wrapT = THREE.RepeatWrapping;
    // var tileWidth = 2;
    // var tileHeight = 1;
    // repeatX = tileWidth * 1024 / (tileHeight * 2048);
    // repeatY = 1;
    // texture_0.repeat.set(repeatX, repeatY);

    var geometry_0 = new THREE.PlaneGeometry(1.3,1,32);
    var material_0 = new THREE.MeshBasicMaterial({
      color: 0xd8d0d1,
      side: THREE.DoubleSide,
      map: texture_0
    });

    var mesh_0 = new THREE.Mesh(geometry_0, material_0);
    scene.add(mesh_0);

    mesh_0.position.x = -0.7



    // MESH 1

  var texture_1 = new THREE.TextureLoader().load("https://i.imgur.com/YO0ygMx.jpg");
  texture_1.wrapS = THREE.ClampToEdgeWrapping;
  texture_1.wrapT = THREE.RepeatWrapping;


    var geometry_1 = new THREE.PlaneGeometry(1,3,32);
    var material_1 = new THREE.MeshBasicMaterial({
      color: 0xd8d0d1,
      side: THREE.DoubleSide,
      map: texture_1
    });

    var mesh_1 = new THREE.Mesh(geometry_1, material_1);
    scene.add(mesh_1);

    mesh_1.position.x = 0.7


    // RENDER + ANIMATE
    function animate() {
        /* render scene and camera */
        renderer.render(scene,camera);
        requestAnimationFrame(animate);



    }

    requestAnimationFrame(animate);

    // RESIZE EVENTS
    window.addEventListener('resize', onResize);

    function onResize() {
        width = window.innerWidth;
        height = window.innerHeight;
        camera.aspect = width / height;
        camera.updateProjectionMatrix();
        renderer.setSize(width, height);
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r120/three.min.js"></script>
<canvas id="canvas"></canvas>

Upvotes: 1

Views: 2466

Answers (1)

user128511
user128511

Reputation:

There are a lot of possible aspects. There's the aspect of the texture itself, the size of the plane, the size the plane is displayed (like if you scaled it), etc..

Anyway, at a minimum you need to know the aspect of the image and the aspect of the plane. Unless you hard code the image size into your app you won't know the image aspect until after it's loaded. At that point you can compute the correct offset and repeat settings as detailed in this article

  // Set the repeat and offset properties of the background texture
  // to keep the image's aspect correct.
  const planeAspect = planeWidth / planeHeight;
  const imageAspect = texture.image.width / texture.image.height;
  const aspect = imageAspect / planeAspect;
 
  texture.offset.x = aspect > 1 ? (1 - 1 / aspect) / 2 : 0;
  texture.repeat.x = aspect > 1 ? 1 / aspect : 1;
 
  texture.offset.y = aspect > 1 ? 0 : (1 - aspect) / 2;
  texture.repeat.y = aspect > 1 ? 1 : aspect;

var renderer = new THREE.WebGLRenderer({ canvas : document.getElementById('canvas'), antialias:true});
    renderer.setClearColor(0x7b7b7b);
    //  use device aspect ratio //
    renderer.setPixelRatio(window.devicePixelRatio);
    // set size of canvas within window 
    renderer.setSize(window.innerWidth, window.innerHeight);

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

    // CAMERA
    var camera = new THREE.PerspectiveCamera( 45, window.innerWidth/window.innerHeight, 0.1, 1000 );
    camera.position.z = 5;




  // Set the repeat and offset properties of the background texture
  // to keep the image's aspect correct.
  function fixTexture(planeWidth, planeHeight) {
    return function(texture) {
      const planeAspect = planeWidth / planeHeight;
      const imageAspect = texture.image.width / texture.image.height;
      const aspect = imageAspect / planeAspect;

      texture.offset.x = aspect > 1 ? (1 - 1 / aspect) / 2 : 0;
      texture.repeat.x = aspect > 1 ? 1 / aspect : 1;

      texture.offset.y = aspect > 1 ? 0 : (1 - aspect) / 2;
      texture.repeat.y = aspect > 1 ? 1 : aspect;
    }
  }
    // MESH 0

    // texture
  var texture_0 = new THREE.  TextureLoader().load("https://i.imgur.com/YO0ygMx.jpg", fixTexture(1.3, 1));
  texture_0.wrapS = THREE.ClampToEdgeWrapping;
  texture_0.wrapT = THREE.RepeatWrapping;
    // var tileWidth = 2;
    // var tileHeight = 1;
    // repeatX = tileWidth * 1024 / (tileHeight * 2048);
    // repeatY = 1;
    // texture_0.repeat.set(repeatX, repeatY);

    var geometry_0 = new THREE.PlaneGeometry(1.3,1,32);
    var material_0 = new THREE.MeshBasicMaterial({
      color: 0xd8d0d1,
      side: THREE.DoubleSide,
      map: texture_0
    });

    var mesh_0 = new THREE.Mesh(geometry_0, material_0);
    scene.add(mesh_0);

    mesh_0.position.x = -0.7



    // MESH 1

  var texture_1 = new THREE.TextureLoader().load("https://i.imgur.com/YO0ygMx.jpg", fixTexture(1,3));
  texture_1.wrapS = THREE.ClampToEdgeWrapping;
  texture_1.wrapT = THREE.RepeatWrapping;


    var geometry_1 = new THREE.PlaneGeometry(1,3,32);
    var material_1 = new THREE.MeshBasicMaterial({
      color: 0xd8d0d1,
      side: THREE.DoubleSide,
      map: texture_1
    });

    var mesh_1 = new THREE.Mesh(geometry_1, material_1);
    scene.add(mesh_1);

    mesh_1.position.x = 0.7


    // RENDER + ANIMATE
    function animate() {
        /* render scene and camera */
        renderer.render(scene,camera);
        requestAnimationFrame(animate);



    }

    requestAnimationFrame(animate);

    // RESIZE EVENTS
    window.addEventListener('resize', onResize);

    function onResize() {
        width = window.innerWidth;
        height = window.innerHeight;
        camera.aspect = width / height;
        camera.updateProjectionMatrix();
        renderer.setSize(width, height);
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r120/three.min.js"></script>
<canvas id="canvas"></canvas>

Upvotes: 5

Related Questions