Scaling texture to fit on a plane in THREE.js

I am attempting to 'fit-to-scale' a mapped image texture on a single plane, aiming to simulate the behavior of object-fit:cover.

The image map needs to scale proportionally in order to cover the entire plane completely.

I have experimented with adjusting the repeat and offset of the texture but have been unsuccessful thus far (the code is commented out).

As you can see from the snippet below, the current result still stretches the image to fit. Any assistance would be greatly 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>

Answer №1

There are numerous aspects to consider when dealing with textures, such as the texture itself, the size of the plane, and how the plane is displayed (e.g., scaling).

At a minimum, understanding the aspect ratios of both the image and the plane is essential. Without hard coding the image size in your application, you can only determine the image aspect ratio after it's been loaded. Once you have this information, you can calculate the correct offset and repeat settings following the instructions outlined in this article

// Set the repeat and offset properties of the background texture
// to maintain the image's correct aspect ratio.
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 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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The Fixed Navbar is causing sections to be slightly off from their intended positions

Utilizing a bootstrap navigation menu on my website with a fixed position. When clicking a menu item, it takes me to the designated section but slightly above the desired position. How can I ensure that it goes to the exact position upon clicking the men ...

Incorporate npm libraries into vanilla JavaScript for HTML pages

Attempting to incorporate an npm package into plain JS/HTML served using Python and Flask. <script type="module"> import { configureChains, createClient } from "./node_modules/@wagmi/core"; import { bsc } from "./nod ...

Lodash Debounce failing to properly debounce

Currently, I am attempting to use Lodash to debounce a function but it doesn't seem to be working properly. My issue appears to be different from what others have experienced on both Stack Overflow and Google when using the _.debounce method. The imp ...

Obtaining a reference to a filtered result in ngRepeat with AngularJS

Using an ng-repeat directive with a filter, the code looks like this: ng-repeat="item in items | orderBy:'order_prop' | filter:query | limitTo:4" The rendered results are visible; now the goal is to perform some logic on that result in the cont ...

Issue with Jquery plugin malfunctioning on dynamically loaded elements via Ajax requests

Description: I'm currently working on a project where I need to load elements with expiration dates pulled from my database. To achieve this, I am using a Jquery plugin that relies on the HTML5 Data Type Attribute for setting the "end date". Everythin ...

calling the setState function multiple times

I keep calling the setState function multiple times in a row by pressing the down button, but I noticed that React JS stops updating the state after around 40-50 updates. Each click increases the sold value by one. Is this normal behavior for React JS, or ...

Existence resembling parchment in the realm of three.js

I am currently working on a game that involves a scene where the character is engaged in reading a book. I have managed to create pages of this book that should realistically flip and move around using Three.js. Creating a cube geometry with specific speci ...

Activate SVG graphics upon entering the window (scroll)

Can anyone assist me with a challenging issue? I have numerous SVG graphics on certain pages of my website that start playing when the page loads. However, since many of them are located below the fold, I would like them to only begin playing (and play onc ...

Encountering Problem with Loading Blender Model in Three.js

I am struggling to successfully import a blender model into three.js. Here is what I have attempted so far: <script src="GLTFLoader.js"></script> // CHILDREN children = new THREE.Object3D(); // Blender Model ...

Extracting user input from an iframe and transferring it to another frame in HTML format

Can someone advise me on how to extract values from iframe1 and transmit them to iframe2 as HTML? I am open to either JavaScript or jQuery - no preference. As a beginner in javascript, I stumbled upon some code that seems relevant. <!DOCTYPE html> ...

How can I make Backbone.js execute a function following the creation of a Collection?

It seems like I may not be grasping the full picture here, but let me lay out my current understanding: I have a Model that holds 'all' the data (JSON retrieved from a single URL). This model contains one or more Collections which are populated ...

Is there a javascript function that performs the reverse action of indexof()?

Is there a JavaScript function that is the opposite of indexOf()? In my code, when you press the button it will display 3 [from indexOf(".")]. However, I am looking for a function that is the opposite of indexOf(), which would show me 2 [decimal]. http: ...

What is the best way to refrain from utilizing the && logical operator within the mapStateToProps function in Redux?

When I'm trying to access a nested state in a selector, I often find myself having to use the && logical operators. const mapStateToProps = store => ({ image: store.auth.user && store.auth.user.photoURL; }); If I don't use ...

Having difficulty requesting an API in Next.js that relies on a backend cookie

1: When a user logs in, I generate a refresh token and create a cookie using 'cookie-parser'. This cookie is then sent to the path '/user/refresh-token' res.cookie('refreshtoken', refreshtoken, { httpOnly: true, ...

What exactly does a 'hoisted manifest' mean when it comes to using Yarn?

Encountering an issue while attempting to install a package using yarn, I am receiving the error message: expected hoisted manifest for \"myPackage#@material-ui/core#react-dom\" However, the concept of a 'hoisted manifest' is not entir ...

Accessing objects within an array in JSON using React Native

I have been struggling with this issue for a while and need some help. I am attempting to access an object within an array, specifically the URL. Here are the methods I have tried: post.yoast_head_json.og_image.url post.yoast_head_json.og_image[0].url pos ...

How to Showcase a Circular Shape within an SVG Using D3.js

I've been working on my Angular app and have made some progress. However, I'm having trouble getting a circle to show up on the SVG. Can anyone help me figure out what I'm doing wrong? svg: any; margin = 50; width = 350 - (this.margin ...

What is the reason for using a string as the index of an array in the following code?

var arrayOfNumbers = [1, 2, 3, 4, 5, 6, 78]; for(var index in arrayOfNumbers){ console.log(index+1); } The result produced by the given code snippet is as follows: 01 11 21 31 41 51 61 What is the reason behind JavaScript treating these ...

Is there an issue with using $_POST in an ajax form submission?

Whenever I attempt to verify if a user-inputted name already exists through an ajax form submission, all I receive is an error stating "Undefined index: username" in the sessions.php file. What could be causing this issue? <form action="" method="POST" ...

Can PushState be used to Ajaxify CSS files?

Currently, I am in the process of developing a basic website and have decided to incorporate the Ajaxify library for seamless page transitions. One challenge I have encountered involves the combination of global CSS files (applied throughout the entire sit ...