How to repeat image texture on the mesh material based on the texture's actual size

@manthrax I took reference from this post.

I tried by changing different images, it almost works correctly.
demo : Glitch :・゚✧

EDIT : it works for square tile textures :slight_smile:

can you please guide how can we implement that texture repetition in following function with grouts ?

function loadTextureAndSetRepeat({ element, rotateAngle = 0 }) {
  let surfaceData = element.userData;
  let randomIndex = Math.floor(Math.random() * tilesData.length);
  let currentTexture = tilesData[randomIndex];

  const imageUrls = currentTexture.imgUrl;

  // Convert surfaceData width and height from feet to meters
  const feetToMeters = 0.3048;
  const wallWidthMeters = surfaceData.width * feetToMeters;
  const wallHeightMeters = surfaceData.height * feetToMeters;

  const createTextureWithGrout = (images, widthMeters, heightMeters) => {
    const tileWidthMeters = 6; // Tile width in meters (600mm)
    const tileHeightMeters = 6; // Tile height in meters (600mm)
    const groutThicknessMeters = 0.2; // Grout thickness in meters (5mm)

    const columns = Math.ceil(widthMeters / tileWidthMeters);
    const rows = Math.ceil(heightMeters / tileHeightMeters);

    // Create a canvas to draw the combined texture
    const canvas = document.createElement('canvas');
    canvas.width = Math.ceil((tileWidthMeters * columns) * 100); // Convert to pixels
    canvas.height = Math.ceil((tileHeightMeters * rows) * 100); // Convert to pixels
    const context = canvas.getContext('2d');

    // Check if images are loaded before proceeding
    if (images.some(image => !image.complete || image.naturalWidth === 0)) {
      throw new Error('One or more images failed to load.');
    }

    // Draw each image with grout lines
    for (let col = 0; col < columns; col++) {
      for (let row = 0; row < rows; row++) {
        const x = col * tileWidthMeters * 100;
        const y = row * tileHeightMeters * 100;
        const image = images[(row * columns + col) % images.length];

        // Create a temporary canvas to draw the tile with grout
        const tileCanvas = document.createElement('canvas');
        tileCanvas.width = tileWidthMeters * 100;
        tileCanvas.height = tileHeightMeters * 100;
        const tileContext = tileCanvas.getContext('2d');

        // Draw the tile image on the temporary canvas
        tileContext.drawImage(image, 0, 0, tileCanvas.width, tileCanvas.height);

        // Draw the grout lines on the tile image
        tileContext.fillStyle = 'rgb(255, 0, 0)'; // Red grout color

        // Draw vertical grout lines
        tileContext.fillRect(tileCanvas.width - groutThicknessMeters * 100, 0, groutThicknessMeters * 100, tileCanvas.height);

        // Draw horizontal grout lines
        tileContext.fillRect(0, tileCanvas.height - groutThicknessMeters * 100, tileCanvas.width, groutThicknessMeters * 100);

        // Draw the modified tile image on the main canvas
        context.drawImage(tileCanvas, x, y);
      }
    }

    // Create the Three.js texture from the canvas
    const texture = new THREE.CanvasTexture(canvas);
    texture.wrapS = THREE.RepeatWrapping;
    texture.wrapT = THREE.RepeatWrapping;
    texture.rotation = Math.PI *  tileTextureRotation;
    texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
    return texture;
  };

  const loadImages = (urls) => {
    return Promise.all(urls.map((url) => {
      return new Promise((resolve, reject) => {
        const image = new Image();
        image.crossOrigin = "anonymous"; // To handle CORS issues
        image.onload = () => resolve(image);
        image.onerror = (error) => reject(error);
        image.src = url;
      });
    }));
  };

  loadImages(imageUrls)
    .then((images) => {
      const combinedTexture = createTextureWithGrout(images, wallWidthMeters, wallHeightMeters);

      // Assign texture to element material in Three.js
      element.material.map = combinedTexture;
      element.material.needsUpdate = true;

      // Render scene
      renderer.render(scene, camera);
    })
    .catch((error) => {
      console.error('Error loading images:', error);
    });
}