Texture repetition and grout lines generation issue

I am stuck in a issue with generating textures.

query : grouts and layouts are being generated as per the selection, but have white dashes in between the grout lines being drawn. also layout type is not getting applied on the floor. I have tested that layout type 2 was passed for wall and floor but generated different outputs as following.


fshader

export default `
uniform sampler2D tileTexture;
uniform float tileWidth;
uniform float tileHeight;
uniform float planeWidth;
uniform float planeHeight;
uniform float scaleFactor;
uniform int layoutType; // 0 for grid, 1 for brick, 2 for brick2
varying vec2 vUv;

void main() {
  float repeatX = (planeWidth / tileWidth) * scaleFactor;
  float repeatY = (planeHeight / tileHeight) * scaleFactor;

  vec2 uv = vUv;

  if (layoutType == 1) {
    // Brick layout
    uv.x += mod(floor(vUv.y * repeatY), 2.0) * 0.5;
  } else if (layoutType == 2) {
    // Brick2 layout
    uv.y += mod(floor(vUv.x * repeatX), 2.0) * 0.5;
  }

  uv = mod(uv * vec2(repeatX, repeatY), 1.0);
  gl_FragColor = texture2D(tileTexture, uv);
}

`;

vshader

export default `
varying vec2 vUv;

void main() {
  vUv = uv;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;

script.js

// Function to load an image
function loadImage(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.crossOrigin = "anonymous";
    img.onload = () => resolve(img);
    img.onerror = reject;
    img.src = url;
  });
}

// Function to find the next power of 2
function nextPowerOf2(x) {
  return Math.pow(2, Math.ceil(Math.log2(x)));
}

// Function to draw grout lines on a tile image
function drawGroutLines(image, groutColor = "red", groutWidth = 10, groutAlpha = 1) {
  const canvas = document.createElement("canvas");
  const context = canvas.getContext("2d");
  canvas.width = nextPowerOf2(image.width);
  canvas.height = nextPowerOf2(image.height);

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

  // Draw grout lines
  context.strokeStyle = groutColor;
  context.globalAlpha = groutAlpha;
  context.lineWidth = groutWidth;
  context.strokeRect(0, 0, canvas.width, canvas.height);
  context.globalAlpha = 1;

  return canvas;
}

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

  const imageUrls = currentTexture.imgUrl;

  loadImage(imageUrls[0])
    .then((firstImage) => {
      const tiWidthMM = firstImage.width; // Tile width in mm
      const tiHeightMM = firstImage.height; // Tile height in mm
      const tiWidthM = tiWidthMM / 1000; // Convert to meters
      const tiHeightM = tiHeightMM / 1000; // Convert to meters

      const planeHeightM = surfaceData.height; // Plane height in meters
      const planeWidthM = surfaceData.width; // Plane width in meters

      // Determine scale factor based on surface type
      const scaleFactor = surfaceData.type === 'wall' ? 0.025 : 0.012;

      Promise.all(imageUrls.map(loadImage))
        .then((images) => {
          const processedImages = images.map(img => drawGroutLines(img));
          const texture = new THREE.Texture(processedImages[0]);
          texture.needsUpdate = true;
          // Determine layout type
          let layoutType = 0; // Default to grid
          if (layout === 'brick') {
            layoutType = 1;
          } else if (layout === 'brick2') {
            layoutType = 2;
          }
          // Create a shader material to handle repetition
          const shaderMaterial = new THREE.ShaderMaterial({
            uniforms: {
              tileTexture: { value: texture },
              tileWidth: { value: tiWidthM },
              tileHeight: { value: tiHeightM },
              planeWidth: { value: planeWidthM },
              planeHeight: { value: planeHeightM },
              scaleFactor: { value: scaleFactor },
              layoutType: { value: layoutType }
            },
            vertexShader,
            fragmentShader,
            side: THREE.DoubleSide // Make the shader material double-sided
          });

          // Ensure the element has the necessary material properties for the shader
          element.material = shaderMaterial;
          element.material.needsUpdate = true;

          // Render the scene to reflect the texture change
          renderer.render(scene, camera);
        })
        .catch((error) => {
          console.error("Error loading images:", error);
        });
    })
    .catch((error) => {
      console.error("Error loading first image:", error);
    });
}

issue is little bit fixed. now getting spreaded solid lines.

fshaders

export default `
uniform sampler2D tileTexture;
uniform float tileWidth;
uniform float tileHeight;
uniform float planeWidth;
uniform float planeHeight;
uniform float scaleFactor;
uniform int layoutType; // 0 for grid, 1 for brick, 2 for brick2
uniform float rotationAngle; // Rotation angle in radians
uniform float groutWidth; // Width of the grout lines
uniform vec3 groutColor; // Color of the grout lines
varying vec2 vUv;

void main() {
  float repeatX = (planeWidth / tileWidth) * scaleFactor;
  float repeatY = (planeHeight / tileHeight) * scaleFactor;

  vec2 uv = vUv;

  if (layoutType == 1) {
    // Brick layout
    uv.x += mod(floor(vUv.y * repeatY), 2.0) * 0.5 / repeatX;
  } else if (layoutType == 2) {
    // Brick2 layout
    uv.y += mod(floor(vUv.x * repeatX), 2.0) * 0.5 / repeatY;
  }

  // Calculate the center of the texture coordinates
  vec2 center = vec2(0.5, 0.5);

  // Translate texture coordinates to origin
  uv = uv * vec2(repeatX, repeatY) - center;

  // Apply rotation
  float cosAngle = cos(rotationAngle);
  float sinAngle = sin(rotationAngle);
  vec2 rotatedUv = vec2(
    uv.x * cosAngle - uv.y * sinAngle,
    uv.x * sinAngle + uv.y * cosAngle
  );

  // Translate texture coordinates back to center
  rotatedUv += center;

  // Check if the current fragment is within the grout lines
  vec2 groutCheck = fract(rotatedUv);
  if (groutCheck.x < groutWidth || groutCheck.y < groutWidth) {
    gl_FragColor = vec4(groutColor, 1.0); // Draw grout line
  } else {
    gl_FragColor = texture2D(tileTexture, rotatedUv); // Draw tile texture
  }
}
`;

JS code

function drawGroutLines(
  image,
  groutColor = "red",
  groutWidth = 10,
  groutAlpha = 1
) {
  const canvas = document.createElement("canvas");
  const context = canvas.getContext("2d");
  canvas.width = nextPowerOf2(image.width);
  canvas.height = nextPowerOf2(image.height);

  // Draw the tile image on the canvas
  context.drawImage(image, 0, 0, canvas.width, canvas.height);
  // Draw grout lines
  if (groutWidth > 0) {
    context.strokeStyle = groutColor;
    context.globalAlpha = groutAlpha;
    context.lineWidth = groutWidth;
    context.lineHeight = groutWidth;
    context.strokeRect(0, 0, canvas.width, canvas.height);
  }

  return canvas;
}

async function loadTextureAndSetRepeat({
  element,
  rotateAngle = 0,
  layout = "grid",
  groutWidth = 0.01, // Width of the grout lines in texture coordinates
  groutColor = [1.0, 0.0, 0.0], // Color of the grout lines (RGB values, example: red)
}) {
  let surfaceData = element.userData;
  let randomIndex = Math.floor(Math.random() * tilesData.length);
  let currentTexture = tilesData[randomIndex];

  const imageUrls = currentTexture.imgUrl;

  loadImage(imageUrls[0])
    .then((firstImage) => {
      const tiWidthMM = firstImage.width; // Tile width in mm
      const tiHeightMM = firstImage.height; // Tile height in mm
      const tiWidthM = tiWidthMM / 1000; // Convert to meters
      const tiHeightM = tiHeightMM / 1000; // Convert to meters

      const planeHeightM = surfaceData.height; // Plane height in meters
      const planeWidthM = surfaceData.width; // Plane width in meters

      // Determine scale factor based on surface type
      const scaleFactor = surfaceData.face === "wall" ? 0.025 : 0.012;

      // Convert rotation angle from degrees to radians
      const rotationAngleInRadians = THREE.MathUtils.degToRad(rotateAngle);

      Promise.all(imageUrls.map(loadImage))
        .then((images) => {
          const processedImages = images.map((img) => drawGroutLines(img));
          const texture = new THREE.Texture(processedImages[0]);
          texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
          texture.minFilter = THREE.LinearMipmapLinearFilter;
          texture.magFilter = THREE.LinearFilter;
          texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
          texture.needsUpdate = true;

          // Determine layout type
          let layoutType = 0; // Default to grid
          if (layout === "brick") {
            layoutType = 1;
          } else if (layout === "brick2") {
            layoutType = 2;
          }

          // Create a shader material to handle repetition
          const shaderMaterial = new THREE.ShaderMaterial({
            uniforms: {
              tileTexture: { value: texture },
              tileWidth: { value: tiWidthM },
              tileHeight: { value: tiHeightM },
              planeWidth: { value: planeWidthM },
              planeHeight: { value: planeHeightM },
              scaleFactor: { value: scaleFactor },
              layoutType: { value: layoutType },
              rotationAngle: { value: rotationAngleInRadians },
              groutWidth: { value: groutWidth },
              groutColor: { value: new THREE.Vector3(...groutColor) },
            },
            vertexShader,
            fragmentShader,
            side: THREE.DoubleSide, // Make the shader material double-sided
          });

          // Ensure the element has the necessary material properties for the shader
          element.material = shaderMaterial;
          element.material.needsUpdate = true;

          // Render the scene to reflect the texture change
          renderer.render(scene, camera);
        })
        .catch((error) => {
          console.error("Error loading images:", error);
        });
    })
    .catch((error) => {
      console.error("Error loading first image:", error);
    });
}

lines were being drawn twice, one from script.js (dashed)

in shaders overlapped those lines to make them solid and perfect.

still not facing issues. any suggestion or guidance.