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

I am trying to repeat the tile image texture on the already create walls & floor mesh material.

what I am trying to do is apply all the available materials randomly on the floor or walls, but in current code i am able to choose one of the texture. also I want to keep ` texture.repeat.set(10, 10);` as ` texture.repeat.set(1, 1);` but if we keep (1,1) only one single texture covers whole area.

also how can we rotate whole texture in specific angle, like when we try 45,135 image were rotated but how to rotate those drawn lines ?

with 45 angle

with 90 angle

Queries :

1. how to repeat all the generated image textures with their original ratio on the mesh material based on the textureâ€™s original size.
2. how to rotate whole texture repetition.

I am new to ThreeJS, maybe this questions are very basic, any help or suggestions is much appreciated.Thanks in advance

Bumpâ€¦ Still stucked in this issue, help & suggestions required.

Thanks.

I donâ€™t think you want to do this:

``````        const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
// Rotate the image if needed
let rotatedImage = rotateImage(image, rotateAngle);
``````

Instead set the texture.rotation = Math.PI * .5 // Rotate the texture 90 degrees

https://threejs.org/docs/#api/en/textures/Texture.rotation

and texture.repeat.set( 10, 10) //Make the texture repeat 10 timesâ€¦

https://threejs.org/docs/#api/en/textures/Texture.repeat

preserving the aspect ratio is a bit trickier mathâ€¦

youâ€™ll have to get it as aspect = image.width/image.height

then scale one axis of the repeat like texture.repeat.set(10, 10 / aspect )

Or smth, hth!

1 Like

okay will give a try for rotation. and can you please guide how can we combine & create a new texture which is combination of some different textures.

something like this. I found one reference for this but not sure how to implement follow code file.
engine.tile.js (30.1 KB)

in this reference code they are rotating the texture and repeating them as well based on the angle.

Thanks in advance for the guidance

it worked. thanks
I kept value as 0.25 for 45 angle. is it correct ? any guide book to apply the number as per the angle value ?

``````        texture.rotation = Math.PI * 0.25; // Rotate the texture 45 degrees

``````

now wondering how can I repeat the different texture on mesh.

blurry lines in those area where multiple obstacles are present. there are 3 skybox layers for matt & glossy effect and below that tile texture layer exists. I assume blurriness occuring due to those multiple layers. as I want to apply glossy and matt effects on the tiles so those layers are above the tile texture mesh layer. Is it possible to fix the issue without changing renderOrder of mesh and skyboxes ?

@manthrax I have reduced code of engine.tile.js and kept only default code as of now for grid layout.
engine.tile.js (16.3 KB)

now can you please guide how they are repeating it ?

overview of debug till now

randsim function is used to avoid repeating same tile texture next to each other.

they are creating texture material with following for loops, but not able to understand how they are positioning the textures.

``````for (var qx = 0; qx * gtw <= plane_width + 1; qx++) {
for (var qy = 0; qy * gth <= plane_height + 1; qy++) {
var rgb = [temp_01 % 256, Math.floor(temp_01 / 256), index_m];
var met_dat = {
transparent: true,
depthTest: false
};
if (for_indexize) {
met_dat.color = "rgb(" + rgb + ")";
} else {
var isI = isIndexed(rgb + "");
var isD = free_tid == 0 || isI;
met_dat.map = randsim(imgs[(qx) % imgs.length], qx, qy, 'random', data[0]).texture;
if (is_free && !isD) {
}
}
var floorMaterial = new THREE.MeshBasicMaterial(met_dat);
if (rotateAngle === 45 && typeof met_dat.map == "undefined") {
rotateTexture(floorGeometry, floorMaterial, rotateAngle, iw, ih);
}
floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.position.x = (qx + 0.5) * gtw + skew_width * qy;
floor.position.y = (qy + 0.5) * gth + skew_height * qx;
floor.position.z = -index_m * 0.0001;
}
temp_01++;
}
}
``````

data object values :

``````{0: 1, 1: -149.5, 2: -123, 3: -168, 4: 0, 5: 71.5, 6: 0, 7: 500, 8: 500, 9: 22, 10: 22, 11: Array(8), 12: 0, 13: 0, 14: 0, 15: 0, 16: 0, 17: 0, 176: 'Wall', 177: 0}
``````

output with data values

debug values :

even with the walls & floor layer only, texture lines looks blurry as distance increases from the camera point

I found that anisotropy may help to fix this.

but how to calculate the required number to fix the issue. like this ?

``````   texture.anisotropy = 100;
``````

Anisotropy range is 1-16
`texture.anisotropy = renderer.capabilities.getMaxAnisotropy();`

2 Likes

I guess it worked. Thanks

could you guide further by reviewing above questions once, I want to repeat the texture as per the layerâ€™s size.

For not angled objects like box you need for â€śTriplanar shaderâ€ť.
But maybe you need manualy change tile repeating by changing uv coordinates, not texture.repeat
uv was 0,0,1,1 and now is like 0,0,10,10 then texture will repeats 10 times

okay understood. I am implementing on flat surface, all objects are images only, 3D isnâ€™t use for this.

I have shared a reference file here in which they have done mapping, I am trying to replicate that.

Thanks.

this is latest changes which I have managed with debugging and help of AI chatbot. still facing scaling issue a bit and texture became blurry.

``````function loadTextureAndSetRepeat({
element,
rotateAngle = 0, // Default to 0 if not provided
}) {
let surfaceData = element.userData;
let currentTexture = tilesData[5];
const imageUrls = currentTexture.imgUrl;
const gth = (16 * currentTexture.height) / 500;
const gtw = (16 * currentTexture.width) / 500;
const plane_height = surfaceData.height;
const plane_width = surfaceData.width;
const textures = [];

.then((images) => {
images.forEach((image) => {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");

canvas.width = image.width;
canvas.height = image.height;
context.drawImage(image, 0, 0, canvas.width, canvas.height);

if (grout_v_width > 0 || grout_h_width > 0) {
context.strokeStyle = grout_color;
context.globalAlpha = grout_alpha;
if (grout_v_width > 0) {
context.lineWidth = grout_v_width;
context.beginPath();
context.moveTo(0, 0);
context.lineTo(canvas.width, 0);
context.closePath();
context.stroke();
context.beginPath();
context.moveTo(0, canvas.height);
context.lineTo(canvas.width, canvas.height);
context.closePath();
context.stroke();
}
if (grout_h_width > 0) {
context.lineWidth = grout_h_width;
context.beginPath();
context.moveTo(0, 0);
context.lineTo(0, canvas.height);
context.closePath();
context.stroke();
context.beginPath();
context.moveTo(canvas.width, 0);
context.lineTo(canvas.width, canvas.height);
context.closePath();
context.stroke();
}
context.globalAlpha = 1;
}

const texture = new THREE.Texture(canvas);
texture.premultiplyAlpha = false;
texture.needsUpdate = true;
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
texture.repeat.set(1, 1);

textures.push(texture);
});

// Create a combined canvas
const combinedCanvas = document.createElement("canvas");
combinedCanvas.width = nextPowerOf2(plane_width);
combinedCanvas.height = nextPowerOf2(plane_height);
const combinedContext = combinedCanvas.getContext("2d");

for (let indexQx = 0; indexQx * gtw <= combinedCanvas.width; indexQx++) {
for (let indexQy = 0; indexQy * gth <= combinedCanvas.height; indexQy++) {
const randomTexture = textures[Math.floor(Math.random() * textures.length)].image;
combinedContext.drawImage(
randomTexture,
indexQx * gtw,
indexQy * gth,
gtw,
gth
);

// Draw grout lines
if (grout_v_width > 0) {
combinedContext.strokeStyle = grout_color;
combinedContext.globalAlpha = grout_alpha;
combinedContext.lineWidth = grout_v_width;
combinedContext.beginPath();
combinedContext.moveTo(indexQx * gtw, indexQy * gth);
combinedContext.lineTo((indexQx + 1) * gtw, indexQy * gth);
combinedContext.lineTo((indexQx + 1) * gtw, (indexQy + 1) * gth);
combinedContext.lineTo(indexQx * gtw, (indexQy + 1) * gth);
combinedContext.closePath();
combinedContext.stroke();
}

if (grout_h_width > 0) {
combinedContext.strokeStyle = grout_color;
combinedContext.globalAlpha = grout_alpha;
combinedContext.lineWidth = grout_h_width;
combinedContext.beginPath();
combinedContext.moveTo(indexQx * gtw, indexQy * gth);
combinedContext.lineTo(indexQx * gtw, (indexQy + 1) * gth);
combinedContext.lineTo((indexQx + 1) * gtw, (indexQy + 1) * gth);
combinedContext.lineTo((indexQx + 1) * gtw, indexQy * gth);
combinedContext.closePath();
combinedContext.stroke();
}
}
}

const combinedTexture = new THREE.Texture(combinedCanvas);
combinedTexture.needsUpdate = true;
combinedTexture.wrapS = THREE.RepeatWrapping;
combinedTexture.wrapT = THREE.RepeatWrapping;
combinedTexture.anisotropy = renderer.capabilities.getMaxAnisotropy();
combinedTexture.repeat.set(plane_width / combinedCanvas.width, plane_height / combinedCanvas.height);

// Map the combined texture to the element
element.material.map = combinedTexture;
element.material.needsUpdate = true;

renderer.render(scene, camera);
})
.catch((error) => {
});

// Function to load an image
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = "anonymous";
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)));
}
}

``````

output :

expected similar output:

Bumpâ€¦ Still stucked in calculating number of repetitions & on blurred combined material.

Any suggestion or guidance is much appreciated. Thanks

yeah .25*PI is 45 degrees.
.5 is 90
1 == 180
2 == 360

1 Like

If you have a world space size of an objectâ€¦

For instance:

``````let bounds = new THREE.Box3().setFromObject( yourObject );
``````

and a texture (loaded from an image)
and you want to tile the texture while preserving aspect ratioâ€¦
Your calculation might look something like this:

``````let {width,height} = texture.source.data; //Get texture dimensions
let textureAspect = width/height;  //Get texture aspect ratio

let size = bounds.getSize(new THREE.Vector3()) //Get Object dimensions in world space

let modelAspect = size.x / size.y; //Get the aspect ratio of the model in worldspace
//This assumes you want x/y not z/y or something

texture.repeat.set(1,aspect/modelAspect); //Set the texture repeat as some function of both of those aspect ratios.. it might be * it might be /. But something like this...
``````
1 Like

Thanks @manthrax I will try this

1 Like

I understood one thing that the tile images which I was using were not properly scaled. I have updated them.

also figured out to calculate the number of repetition.

now the issue is currently creating the new combinedCanvas and appling on it. instead of that I want to update the intersected objectâ€™s material and also want to do repetition on it.

Edit : canvas is being generated as per the intersected wall / floor. just looping of repetition not working correctly

can someone guide ? thanks.

``````
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;
const firstImageUrl = imageUrls[0];

.then((firstImage) => {
const tiWidth = firstImage.width;
const tiHeight = firstImage.height;
var gtw = (informazione.wall_tile_w * tiWidth) / 500;
var gth = (informazione.wall_tile_h * tiHeight) / 500;

const plane_height = surfaceData.height;
const plane_width = surfaceData.width;
const textures = [];

.then((images) => {
images.forEach((image) => {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
canvas.width = nextPowerOf2(image.width);
canvas.height = nextPowerOf2(image.height);
context.drawImage(image, 0, 0, canvas.width, canvas.height);
const texture = new THREE.Texture(canvas);
texture.premultiplyAlpha = false;
texture.needsUpdate = true;
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
texture.repeat.set(5, 5);
textures.push(texture);
});

// Create a combined canvas
const combinedCanvas = document.createElement("canvas");
combinedCanvas.width = plane_width;
combinedCanvas.height = plane_height;
const combinedContext = combinedCanvas.getContext("2d");
for (let indexQx = 0; indexQx * gtw <= combinedCanvas.width; indexQx++) {
for (let indexQy = 0; indexQy * gth <= combinedCanvas.height; indexQy++) {
const randomTexture = textures[Math.floor(Math.random() * textures.length)].image;
combinedContext.drawImage(randomTexture, indexQx * gtw, indexQy * gth, gtw, gth);

// Draw grout lines
if (grout_v_width > 0) {
combinedContext.strokeStyle = grout_color;
combinedContext.globalAlpha = grout_alpha;
combinedContext.lineWidth = grout_v_width;
combinedContext.beginPath();
combinedContext.moveTo(indexQx * gtw, indexQy * gth);
combinedContext.lineTo((indexQx + 1) * gtw, indexQy * gth);
combinedContext.lineTo((indexQx + 1) * gtw, (indexQy + 1) * gth);
combinedContext.lineTo(indexQx * gtw, (indexQy + 1) * gth);
combinedContext.closePath();
combinedContext.stroke();
}

if (grout_h_width > 0) {
combinedContext.strokeStyle = grout_color;
combinedContext.globalAlpha = grout_alpha;
combinedContext.lineWidth = grout_h_width;
combinedContext.beginPath();
combinedContext.moveTo(indexQx * gtw, indexQy * gth);
combinedContext.lineTo(indexQx * gtw, (indexQy + 1) * gth);
combinedContext.lineTo((indexQx + 1) * gtw, (indexQy + 1) * gth);
combinedContext.lineTo((indexQx + 1) * gtw, indexQy * gth);
combinedContext.closePath();
combinedContext.stroke();
}
}
}

const combinedTexture = new THREE.Texture(combinedCanvas);
combinedTexture.needsUpdate = true;
combinedTexture.wrapS = THREE.RepeatWrapping;
combinedTexture.wrapT = THREE.RepeatWrapping;
combinedTexture.anisotropy = renderer.capabilities.getMaxAnisotropy();
combinedTexture.repeat.set(1, 1);

// Map the combined texture to the element
element.material.map = textures[0];
element.material.needsUpdate = true;

renderer.render(scene, camera);
})
.catch((error) => {
});
})
.catch((error) => {
});
}
``````

tilesData

``````var tilesData = [
{
index: 1,
imgUrl: [
"images/1/1.jpg",
"images/1/2.jpg",
"images/1/3.jpg",
"images/1/4.jpg",
"images/1/5.jpg",
"images/1/6.jpg",
"images/1/7.jpg",
"images/1/8.jpg",
],
width: 600,
height: 600,
},
];

var informazione = {
floor_tile_w: 6,
floor_tile_h: 12,
wall_tile_w: 12,
wall_tile_h: 6,
camera_fov: 75,
camera_position_x: 0,
camera_position_y: 0,
camera_position_z: 3,
intensity: 0.98,
R_intensity: 0.01,
wall_tile_width: 2400,
wall_tile_height: 1200,
floor_tile_width: 2400,
floor_tile_height: 1200,
};
``````

output with combinedTexture

``````          element.material.map = combinedTexture;
``````

output when we just map image texture

``````          element.material.map = textures[0];
``````

as image scale is small and repeated in 1,1 set so it became blurry. trying to manage it with repetition loop

I have applied changes as per the reference, I am not able to set the obj on the same position as the actual element.

``````
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;
const firstImageUrl = imageUrls[0];

.then((firstImage) => {
const tiWidth = firstImage.width;
const tiHeight = firstImage.height;
const tile_rate_w = informazione.wall_tile_w;
const tile_rate_h = informazione.wall_tile_h;
var gtw = (tile_rate_w * tiWidth) / 500;
var gth = (tile_rate_h * tiHeight) / 500;

// Adjust geometry for skew using BufferGeometry
const floorGeometry = new THREE.PlaneGeometry(gtw, gth);
const skew_vertical = 0.1; // example value, set accordingly
const skew_horizontal = 0.1; // example value, set accordingly
const skew_width = gth / Math.tan((1 - skew_vertical) * Math.PI / 2);
const skew_height = gtw / Math.tan((1 - skew_horizontal) * Math.PI / 2);

const position = floorGeometry.attributes.position;
position.setX(0, position.getX(0) + skew_width);
position.setX(1, position.getX(1) + skew_width);
position.setY(1, position.getY(1) + skew_height);
position.setY(3, position.getY(3) + skew_height);
position.needsUpdate = true;

const plane_height = surfaceData.height;
const plane_width = surfaceData.width;
const textures = [];

.then((images) => {
images.forEach((image) => {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
canvas.width = nextPowerOf2(image.width);
canvas.height = nextPowerOf2(image.height);
context.drawImage(image, 0, 0, canvas.width, canvas.height);
const texture = new THREE.Texture(canvas);
texture.premultiplyAlpha = false;
texture.needsUpdate = true;
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
texture.repeat.set(1, 1);
textures.push(texture);
});

const obj = new THREE.Object3D();
// const imgs = textures.map(texture => texture.image);

for (let qx = 0; qx * gtw <= plane_width + 1; qx++) {
for (let qy = 0; qy * gth <= plane_height + 1; qy++) {
const met_dat = {
transparent: true,
depthTest: false,
map: textures[Math.floor(Math.random() * textures.length)],
};
const floorMaterial = new THREE.MeshBasicMaterial(met_dat);
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.position.x = (qx + 0.5) * gtw + skew_width * qy;
floor.position.y = (qy + 0.5) * gth + skew_height * qx;
floor.position.z = -Math.random() * 0.0001; // Random small offset for layering
}
}
console.log("surfaceData", surfaceData)
obj.position.set(
surfaceData["position-x"],
surfaceData["position-y"],
surfaceData["position-z"]
);
// obj.position.set(
//   1,
//  0,
//   0
// );
// obj.rotation.x = surfaceData.rotateX;

renderer.render(scene, camera);
})
.catch((error) => {
});
})
.catch((error) => {
});
}

``````

surface Data object

``````{
"name": "Wall 3",
"face": "wall",
"width": 285,
"height": 285,
"position-x": -79,
"position-y": 102,
"position-z": -101,
"rotateX": 3.141592653589793,
"color": 0xfff000, //default color without any texture
"rotation-y": 0
}
``````

please suggest the approach to apply the object on the element OR is it any better way to do the same thing ?

Bumpâ€¦

here is my code to set object in the same place of wall

``````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;
const firstImageUrl = imageUrls[0];

.then((firstImage) => {
const tiWidth = firstImage.width;
const tiHeight = firstImage.height;
const tile_rate_w = 22;
const tile_rate_h = 22;
var gtw = (tile_rate_w * tiWidth) / 500;
var gth = (tile_rate_h * tiHeight) / 500;

// Adjust geometry for skew using BufferGeometry
const floorGeometry = new THREE.PlaneGeometry(gtw, gth);
const skew_vertical = 0; // example value, set accordingly
const skew_horizontal = 0; // example value, set accordingly
const skew_width = gth / Math.tan(((1 - skew_vertical) * Math.PI) / 2);
const skew_height = gtw / Math.tan(((1 - skew_horizontal) * Math.PI) / 2);
const plane_height = surfaceData.height;
const plane_width = surfaceData.width;
const position = floorGeometry.attributes.position;
position.setX(0, position.getX(0) + skew_width);
position.setX(1, position.getX(1) + skew_width);
position.setY(1, position.getY(1) + skew_height);
position.setY(3, position.getY(3) + skew_height);
position.needsUpdate = true;

const textures = [];

.then((images) => {
images.forEach((image) => {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
canvas.width = nextPowerOf2(image.width);
canvas.height = nextPowerOf2(image.height);
context.drawImage(image, 0, 0, canvas.width, canvas.height);
const texture = new THREE.Texture(canvas);
texture.premultiplyAlpha = false;
texture.needsUpdate = true;
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
texture.repeat.set(1, 1);
textures.push(texture);
});

const obj = new THREE.Object3D();
for (let qx = 0; qx * gtw <= plane_width + 1; qx++) {
for (let qy = 0; qy * gth <= plane_height + 1; qy++) {
const met_dat = {
transparent: true,
depthTest: false,
map: textures[Math.floor(Math.random() * textures.length)],
};
const floorMaterial = new THREE.MeshBasicMaterial(met_dat);
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.position.x = (qx + 0.5) * gtw + skew_width * qy;
floor.position.y = (qy + 0.5) * gth + skew_height * qx;
floor.position.z = -Math.random() * 0.0001; // Random small offset for layering
}
}

obj.position.x = surfaceData["position-x"] || 0;
obj.position.y = surfaceData["position-y"] || 0;
obj.position.z = surfaceData["position-z"] || 0;

renderer.render(scene, camera);
})
.catch((error) => {
});
})
.catch((error) => {
});
}

``````

wall creation code :

``````  for (const element of data) {
if (element.face.toLowerCase() === "wall") {
const wallGeometry = new THREE.PlaneGeometry(
element.width,
element.height
);
const wallMaterial = new THREE.MeshBasicMaterial({
map: defaultWallTexture, // Use default texture
side: THREE.DoubleSide,
depthTest: true,
});
const wallMesh = new THREE.Mesh(wallGeometry, wallMaterial);
wallMesh.userData = element;
wallMesh.position.set(
element["position-x"],
element["position-y"],
element["position-z"]
);
wallMesh.rotation.y = element["rotation-y"];
wallMesh.rotation.x = element.rotateX;
}
if (element.face.toLowerCase() === "floor") {
const floorGeometry = new THREE.PlaneGeometry(
element.width,
element.height
);
const floorMaterial = new THREE.MeshBasicMaterial({
map: defaultFloorTexture, // Use default texture
side: THREE.DoubleSide,
depthTest: true,
});
const floorMesh = new THREE.Mesh(floorGeometry, floorMaterial);
let dataObj = element;
floorMesh.userData = dataObj;
floorMesh.position.set(
element["position-x"],
element["position-y"],
element["position-z"]
);
floorMesh.rotation.x = Math.PI / 2;