mrgloom
mrgloom

Reputation: 21612

threejs: PlaneGeometry load texture to specific faces

Is it possible to load texture to specific faces of PlaneGeometry object? What I want to do is to map separate texture to specific 2 triangles of PlaneGeometry object.

Here is some example that color each 2 trianles of PlaneGeometry object with some color and it works:

<!DOCTYPE html>
<html lang="en">
	<head>
		<title>title</title>
		<meta charset="utf-8">
		<style>
			body {
				font-family: Monospace;
				background-color: #f0f0f0;
				margin: 0px;
				overflow: hidden;
			}
		</style>
	</head>
	
<body>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"></script>
	<script>
		
		var camera, scene, renderer, geometry, material, mesh;

		init();
		animate();
		
		function init() {

			scene = new THREE.Scene();

			camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
			camera.position.z = 500;
			scene.add(camera);

			// geometry
			var thumbnail_width = 32;
			var thumbnail_height = 32;
			var width_segments = 10;
			var height_segments = 10;
			var plane_geometry_width = width_segments * thumbnail_width;
			var plane_geometry_height = height_segments * thumbnail_height;
			var geometry = new THREE.PlaneGeometry(plane_geometry_width, plane_geometry_height, width_segments, height_segments); // faces.length = widthSegments*heightSegments*2
			
			//Draw grid with static colors
			// materials
			var materials = [];
			materials.push(new THREE.MeshBasicMaterial({
				color: 0xff0000, side:THREE.DoubleSide
			}));
			materials.push(new THREE.MeshBasicMaterial({
				color: 0x00ff00, side:THREE.DoubleSide
			}));
			materials.push(new THREE.MeshBasicMaterial({
				color: 0x0000ff, side:THREE.DoubleSide
			}));
			// Add materialIndex to face
			var l = geometry.faces.length / 2;
			for (var i = 0; i < l; i++) {
				var j = 2 * i;
				geometry.faces[j].materialIndex = i % 3;
				geometry.faces[j + 1].materialIndex = i % 3;
			}

			// mesh
			mesh = new THREE.Mesh(geometry, materials);
			scene.add(mesh);
			
			// WebGL renderer
			renderer = new THREE.WebGLRenderer();
			renderer.setSize(window.innerWidth, window.innerHeight);	
			document.body.appendChild(renderer.domElement);
		}

		function animate() {
			requestAnimationFrame(animate);
			render();
		}

		function render() {
			renderer.render(scene, camera);
		}
		
	</script>
</body>

Here is result: enter image description here

However when I try to map texture the same way, it maps to all triangles, which is not what I want:

<!DOCTYPE html>
<html lang="en">
	<head>
		<title>title</title>
		<meta charset="utf-8">
		<style>
			body {
				font-family: Monospace;
				background-color: #f0f0f0;
				margin: 0px;
				overflow: hidden;
			}
		</style>
	</head>
	
<body>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"></script>
	<script>
		
		var camera, scene, renderer, geometry, material, mesh;

		init();
		animate();
		
		function init() {

			scene = new THREE.Scene();

			camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
			camera.position.z = 500;
			scene.add(camera);

			// geometry
			var thumbnail_width = 32;
			var thumbnail_height = 32;
			var width_segments = 10;
			var height_segments = 10;
			var plane_geometry_width = width_segments * thumbnail_width;
			var plane_geometry_height = height_segments * thumbnail_height;
			var geometry = new THREE.PlaneGeometry(plane_geometry_width, plane_geometry_height, width_segments, height_segments); // faces.length = widthSegments*heightSegments*2
			
			var texture = new THREE.TextureLoader().load("image_small/item_1.png")
			
			var materials = [];
			materials.push(new THREE.MeshBasicMaterial({map : texture, side: THREE.DoubleSide}));
			materials.push(new THREE.MeshBasicMaterial({color: 0xff0000, side:THREE.DoubleSide}));
			materials.push(new THREE.MeshBasicMaterial({color: 0x00ff00, side:THREE.DoubleSide}));
			
			// Add materialIndex to face
			var l = geometry.faces.length / 2;
			for (var i = 0; i < l; i++) {
				var j = 2 * i;
				geometry.faces[j].materialIndex = i % 3;
				geometry.faces[j + 1].materialIndex = i % 3;
			}
			
			// mesh
			mesh = new THREE.Mesh(geometry, materials);
			scene.add(mesh);
			
			// WebGL renderer
			renderer = new THREE.WebGLRenderer();
			renderer.setSize(window.innerWidth, window.innerHeight);
			document.body.appendChild(renderer.domElement);
		}

		function animate() {
			requestAnimationFrame(animate);
			render();
		}

		function render() {
			renderer.render(scene, camera);
		}
		
	</script>
</body>

Here is result: enter image description here

UPDATE:

Part of code for 2nd solution suggested by @WestLangley

    var texture = new THREE.TextureLoader().load("image_small/item_1.png")
    texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
    texture.repeat.set( 10, 10 );
    var materials = [];
    materials.push(new THREE.MeshBasicMaterial({map : texture, side: THREE.DoubleSide}));
    materials.push(new THREE.MeshBasicMaterial({color: 0xff0000, side:THREE.DoubleSide}));
    materials.push(new THREE.MeshBasicMaterial({color: 0x00ff00, side:THREE.DoubleSide}));
    // Add materialIndex to face
    var l = geometry.faces.length / 2;
    for (var i = 0; i < l; i++) {
        var j = 2 * i;
        geometry.faces[j].materialIndex = i % 3;
        geometry.faces[j + 1].materialIndex = i % 3;
    }
    geometry.sortFacesByMaterialIndex();
    // Mesh
    mesh = new THREE.Mesh(geometry, materials);
    scene.add(mesh);
    // WebGL renderer
    renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

Result: enter image description here

UPDATE2:

    var texture = new THREE.TextureLoader().load("image_small/item_1.png")
    texture.flipY = true
    var materials = [];
    materials.push(new THREE.MeshBasicMaterial({map : texture, side: THREE.DoubleSide}));
    for (var i = 0; i < geometry.faces.length; i++) {
        geometry.faces[i].materialIndex = 0;
    }
    var uvs = geometry.faceVertexUvs[ 0 ]; // widthSegments*heightSegments*2

    //UV
    // (0,1) (1,1)
    // (0,0) (1,0)
    // For THREE.PlaneGeometry, UV ( 0, 0 ) is located at the bottom left, and ( 1, 1 ) the top right.
    for (var i = 0; i < uvs.length; i++) {
        if(i % 2 == 0)
        {
            // (0,1)[2] x
            // (0,0)[1] (1,0)[3]
            uvs[i][0].x = 0.0;
            uvs[i][0].y = 0.0;
            uvs[i][1].x = 0.0;
            uvs[i][1].y = 1.0;
            uvs[i][2].x = 1.0;
            uvs[i][2].y = 0.0;

        }
        else
        {   
            // (0,1)[1] (1,1)[2]
            //     x    (1,0)[3]
            uvs[i][0].x = 0.0;
            uvs[i][0].y = 1.0;
            uvs[i][1].x = 1.0;
            uvs[i][1].y = 1.0;
            uvs[i][2].x = 1.0;
            uvs[i][2].y = 0.0;

        }
    }

    var mesh = new THREE.Mesh(geometry, materials);
    scene.add(mesh);
    // WebGL renderer
    renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

Result: enter image description here

Upvotes: 1

Views: 1563

Answers (1)

WestLangley
WestLangley

Reputation: 104783

You have two options.

One is to change the UVs of your geometry so each square has UVs that range from ( 0, 0 ) lower-left to ( 1, 1 ) upper-right.

The other solution is to just set the repeat of your texture:

texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 10, 10 );

BTW, type renderer.info into the console, and you will see you are generating 100 draw calls.

Add the following to your code after you set the material indices:

geometry.sortFacesByMaterialIndex();

You should now see just 3 draw calls.

three.js r.89

Upvotes: 4

Related Questions