9

I'm making use of the THREE.ImageUtils.loadTexture function to load an image and then paste it onto each of the objects in my scene like so:

_.forEach(bags, (bag) => {
  if(bag['children'][0] && bag['children'][0].material) {
    let material = new THREE.MeshPhongMaterial({map: tex});
    bag['children'][0].material = material;
  }
});

I've looked into tex.scale.x,tex.scale.y & tex.offset.x, tex.offset.y but these don't seem to be what I need. Here's what the object looks like at the moment

enter image description here

but I'm after stricter positioning on each side. Something like this (pseudocode) which would place the top-left corner on that required side at that position.

sideA.image.setSize(x,y);

Does anyone have any examples of this?

EDIT

Thanks for the feedback so far guys. @Martin I've setup a basic PlaneGeometry shape to test this out. So this shapes face is basic two triangles attached to each other; I've attached an image of what the uv settings that you posted do:

enter image description here

So from what you said I can't extend one of the triangles as that would break the geometry used so I need to manipulate both the triangles to move the image around properly. Correct?

Katana24
  • 8,706
  • 19
  • 76
  • 118
  • Needed information on which way have you loaded the obj(mesh) and how much control do you have over the file from textures... You could make a mesh with a texture for each side and then ajust as desired or you will need to make your own uv co-ordinates Try this something i was working on http://stackoverflow.com/questions/29877032/three-js-webgl-custom-shader-sharing-texture-with-new-offset/30243114#30243114 – joshua Jan 10 '17 at 12:24

1 Answers1

6

Unfortunately, there is no easy way to do this in three.js.

In order to get proper control over the texturing of non-trivial meshes, you will probably need to do some UV-unwrapping of the model first. This should™ normally be done by the person providing the meshes, because it is way easier to do it in tools like blender, 3dstudio, cinema4d and the likes.

However, I assume that you don't have that person available for some reason and need to do this in javascript. It's more fun this way. Let's further assume, that we just want to map the image to the front- and backside of the bag, the inside and edges will not be textured.

In three.js and most other 3D-applications, the exact mapping of the texture happens by providing texture-coordinates (aka UV-coordinates) for each vertex for every face. These UV-coordinates (ranged from 0 to 1) specify the position in the texture that belongs to the vertex. The point (0, 0) is the bottom-left corner of the image and (1, 1) the top-right corner, independent of the image's aspect-ratio.

So, for a simple quad made from two triangles this would be something like this:

const geometry = new THREE.Geometry();
const vertices = [
   // the four vertices ABCD
   [0,0,0], [0,1,0], [1,0,0], [1,1,0]
].map(v => new THREE.Vector3(...v));

// setup two triangles ADB and ACD:
//
// B---D
// | / |
// A---C

const normal = new THREE.Vector3(0,0,1);
const faces = [
  new THREE.Face3(0, 3, 1, normal),
  new THREE.Face3(0, 2, 3, normal)
];

geometry.vertices = vertices;
geometry.faces = faces;

So, this is just the geometry itself, but it's important to know how it was built for the next step - the UV-coordinates:

const uvs = geometry.faceVertexUvs[0];
const vertexUvs = [
  [0,0], [0,1], [1,0], [1,1]
].map(p => new THREE.Vector2(...p));

// we need to set the UV-coordinates for both faces separately, using 
// the same scheme as before:
uvs[0] = [vertexUvs[0], vertexUvs[3], vertexUvs[1]];
uvs[1] = [vertexUvs[0], vertexUvs[2], vertexUvs[3]];

And that's it basically. This is roughly what THREE.PlaneGeometry does. Some more points about that:

  • three.js is able to handle two different sets of uv-coordinates, that's why there is the geometry.faceVertexUvs[0].
  • the indices within that array correspond to the indices in the faces-array, so uvs[0] is the first face and uvs[1] the second.
  • each of those is again an array of length 3 corresponding to the vertices of that face.

Now in the case of your bags it should be possible to find out which two faces are the front-facing and back-facing part. You will need to find them and edit their UV-coordinates to match the positions in the image you want to see (I don't know how exactly those models look, so I can't help you much further here). All the other UV-coordinates could be set to some safe value, like (0, 0), then everything that is not the front- or back-side will have the color of the bottom-left pixel of your texture.

If you want to texture them as well, you should really consider doing the texture-map in proper 3d-editing software.

EDIT (2017-01-15)

Looking at the image with the angular-logo, the top-left triangle is flipped. This is because three.js is using a different face- or vertex-order than I did in my example.

It helps a lot to understand these things if you just play around with it in the browser-console or debugger (this is what i just did). According to that, the plane-geometry from three.js has the following structure:

// A---B
// | / |
// C---D

vertices = [A, B, C, D]
faces = [Face3(A, C, B), Face3(C, B, D)]

Notable is here that, for whatever reason, the first face is counterclockwise and the second face in clockwise-order (i believe that is kind of bad practice and triangles should always be counterclockwise). Have a look at the faceVertexUVs for the geometry to see how it should look like for this case (just type new THREE.PlaneGeometry(1,1).faceVertexUvs[0] into the browser-console)

Now for the other point, scaling the textures. One thing you could try is to use UV-values greater than 1 for the vertices. This would cause the texture to be drawn smaller. However, this will only work properly if

  • the texture wrap-mode is clamp-to-edge (texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping;)
  • all textures have a 1px wide border in a neutral color

Otherwise (and this is the way I would recommend) you should use a canvas as texture and prepare the image before actually using it. I made a simple example here: http://codepen.io/usefulthink/pen/GrjmrM?editors=0010

Martin Schuhfuß
  • 6,814
  • 1
  • 36
  • 44
  • Perhaps the texture coords of this model are positioned to allow skinning - the use of one texture that wraps the entire object - rather than 4 or 5 separated textures. Hake a look with Lithunwrap or some other (and newer) modelling SW. – Monza Jan 10 '17 at 00:39
  • Well, we have to guess based on the image OP gave, and that suggests some very simple cube'ish-mapping with a single texture being used. Might be good for small repeat-textures though. Also I agree that a single texture should be used for this, but needs to be properly unwrapped to be usable with undistorted images. – Martin Schuhfuß Jan 10 '17 at 01:11
  • Thanks for the answer Martin and sorry it's taken me so long to get back to you. I believe that what you are describing is essentially what I'm after but the results weren't correct because the sides the images are being added to are made up of two triangles therefore the image would unwrap onto each of them respectively instead of collectively. I've started a new question which kind of follows on from this one if you want to follow up http://stackoverflow.com/questions/41673535/why-do-two-faces-appear-invisible-in-threejs – Katana24 Jan 16 '17 at 09:52
  • Ok - I just saw your edit there, I'll give that a shot and report back. – Katana24 Jan 16 '17 at 15:23
  • @MartinSchuhfuß Hi martin - is there a way to dynamically move the image about the canvas/texture while it is on the object? Or would it really need to have a new texture added every time we attempt to move its position? – Katana24 Jan 18 '17 at 11:05
  • If you use the canvas-as-texture option you can update the canvas any time and just need to invalidate the texture using `texture.needsUpdate = true` for every change. It's probably way easier than messing around with UV-coordinates all the time. – Martin Schuhfuß Jan 18 '17 at 12:56