How to modify UV coordinates with three.js - three.js

I want to modify a JSON model's unwrap (UV coordinates) during runtime in order to move the texture over the surface of the geometry. I found faceVertexUvs in the Geometry class documentation. It contains one array, which is correct. That array contains a lot of elements, which I assume to be the UV coordinates for each vertex. Example code:
var uvs = mesh.geometry.faceVertexUvs[0];
console.log(uvs.length);
Gives me 4232 as output. So far so good. Now I would like to alter the u and v values, but the 4000+ elements of the array are strings ("1" through "4234"). I only found examples showing how to create an unwrap from scratch, in which case people push Vector2 data up into faceVertexUvs. So why am I not seeing Vector2 data in there?

Okay, I solved this one myself, hooray. I was searching for the wrong terms, because three.js makes this pretty simple. Instead of actually altering the unwrap (UV coordinates of the geometry), it is possible to set an offset on the texture itself:
texture.offset.y += 0.1;
For this to work with a seamless/tiling texture, we also have to tell three.js that the texture should wrap/repeat:
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
Source: Tunnel Effect Tutorial
Well, that was easy!

Related

Rotating an Object properly around a pivot point given axis and angle

In Three.js there seems to be quite a few ways of rotation which i personally do not find very intuitive. See e.g. the example
http://cloud.engineering-bear.com/apps/robot/robot.html
I get very strange unexpected effects when I apply rotation to multiple objects. E.g. when I rotate objects that have been added to each other and start rotating the parent the individual objects will all over sudden by placed differently in respect to each other then they originally where. I am now experimenting with grouping and would like to avoid the same effect.
See http://pi-q-robot.bitplan.com/example/robot?robot=/models/thing3088064.json for the current state of affairs and https://github.com/BITPlan/PI-Q-Robot for the source code.
So i searched for proper examples following the different API options:
rotation
function renderScene() {
stats.update();
//side1.rotation.z += 0.02;
pivot.rotation.z += 0.02;
https://jsfiddle.net/of1vfhzz/1/
https://github.com/mrdoob/three.js/issues/1958
rotateOnAxis
three.js rotate Object3d around Y axis at it center
How to rotate a 3D object on axis three.js?
ThreeJS - rotation around object's own axis
rotateAroundWorldAxis
object.rotateAroundWorldAxis(p, ax, r * Math.PI * 2 / frames);
How to rotate a object on axis world three.js?
https://stackoverflow.com/a/32038265/1497139
https://jsfiddle.net/b4wqxkjn/7/
THREE.js Update rotation property of object after rotateOnWorldAxis
rotateOnWorldAxis
object.rotateOnWorldAxis( axis, angle );
Rotate around World Axis
rotateAboutPoint
Three JS Pivot point
Rotation anchor point in Three.js
setRotationFromAxisAngle
https://threejs.org/docs/#api/en/core/Object3D.setRotationFromAxisAngle
setEulerFromQuaternion
quaternion = new THREE.Quaternion().setFromAxisAngle( axisOfRotation, angleOfRotation );
object.rotation.setEulerFromQuaternion( quaternion );
Three.js - Rotating a sphere around a certain axis
applyMatrix
this.mesh.updateMatrixWorld(); // important !
childPart.mesh.applyMatrix(new THREE.Matrix4().getInverse(this.mesh.matrixWorld))
Applying a matrix in Three.js does not what I expect
I like the jsFiddle for https://stackoverflow.com/a/56427636/1497139
var pivot = new THREE.Object3D();
pivot.add( cube );
scene.add( pivot );
I also found the following discussions
pivot issue in discourcee.three.js.org
https://discourse.threejs.org/t/rotate-group-around-pivot/3656
https://discourse.threejs.org/t/how-to-rotate-an-object-around-a-pivot-point/6838
https://discourse.threejs.org/t/set-dynamically-generated-groups-pivot-position-to-the-center-of-its-children-objects-position/6349
https://discourse.threejs.org/t/my-3d-model-is-not-rotating-around-its-origin/3339/3
https://jsfiddle.net/blackstrings/c0o3Lm45/
https://discourse.threejs.org/t/rotate-object-at-end-point/2190
https://jsfiddle.net/f2Lommf5/3594/
Questions
None of the above information is clear enough to get to the point of the problem to be solved. The graphics above are much clearer stating the problem than the proposals are stating a solution.
a)
I'd like to use the cylinder as the axis even when the cylinder is moved.I'd expect the easiest way to go would be to use rotateAroundWorldAxis - is that available in the latest revision from three.js or do i have to add it from e.g. https://stackoverflow.com/a/32038265/1497139?
b) I'd like to get a chain of objects to be rotated to later apply inverse kinematics as in
https://github.com/jsantell/THREE.IK
https://jsantell.github.io/THREE.IK/
Although i looked at the source code of that solutions I can't really find the place where the parent-child positioning and rotating is happening. What are the relevant lines of code / API functions that would make proper rotation around a chain of joints happen?
I already looked in the Bone/Skeleton API of Three.js but had the same problem there - lots of lines of code but no clear point where the rotation/positioning between child and parent happens.
Question a)
Basically it works as expected:
cylinder.position.set( options.x, 15, options.z );
pivot.position.x=options.x;
pivot.position.z=options.z;
see
https://jsfiddle.net/wf_bitplan_com/4f6ebs90/13/
Question b)
see
https://codepen.io/seppl2019/pen/zgJVKM
The key is to set the positions correctly. Instead of the proposal at https://stackoverflow.com/a/43837053/1497139 the size is computed in this case.
// create the pivot to rotate around/about
this.pivot = new THREE.Group();
this.pivot.add(this.mesh);
// shift the pivot position to fit my size + the size of the joint
this.pivot.position.set(
x,
y + this.size.y / 2 + this.pivotr,
z + this.size.z / 2
);
// reposition the mesh accordingly
this.mesh.position.set(0, this.size.y / 2, 0);

three.js bind Plane to proxy Object transformations

I'm trying to transform a Plane according to a Object3D (position and rotation). That Plane is used as a clippingPlane.
If I call Plane.applyMatrix4( Object.matrixWorld ) it just applies the matrix once, and doesn't bind the Plane to that matrix for future transformations.
However if I call the same function in a loop the transformations applied to the Plane are continuous.
EG if I call Object.rotate.z = 1 once, and then Plane.applyMatrix4( Object.matrixWorld ) in a loop, the Plane rotates 1 unit along the Z axis at every loop.
Any ideas?
Being this Plane used as a clipping plane, I also tried to transform it in the shader material of the mesh being clipped, and it maybe would be the best performance-wise, but I'm not so skilled to accomplish that.
I would just to this:
object.add( plane );
In this way, plane is a child of object. All transformations applied to object are also applied to plane. Besides, it's now very easy to transform plane relative to object.
The quickest solution I found is to reset and apply Object's .matrixWorld to the Plane. As I said before, it would be great to add useful transformation and "binding" methods to the THREE.Plane object, since it's used as clipping plane too.
Right now I did this way:
// will store the object's inverse transformations matrix in world coords
var inversePrevMatrix = new THREE.Matrix4();
function loop(){
// reset plane previous transformations
plane.applyMatrix4( inversePrevMatrix );
// apply actual object matrix in world coordinates
plane.applyMatrix4( object.matrixWorld );
// set prevMatrix
inversePrevMatrix.getInverse( object.matrixWorld );
}

How to plot country names on the globe, so the mesh will be aligned with the surfaces

I'm trying to plot country names of the globe, so the text meshes will be aligned with the surface, but I'm failing to calculate proper rotations. For text I'm using THREE.TextGeometry. The name appears on the click of the mesh of the country at the point of intersection using raycasting. I'm lacking knowledge of how to turn these coordinates to proper rotation angles. I'm not posting my code, as it's complete mess and I believe for a knowldgeable person will be easier to explain how to achieve this in general.
Here is desired result:
The other solution, which I tried (and which, of course, is not the ultimate), based on this SO answer. The idea is to use the normal of the face you intersect with the raycaster.
Obtain the point of intersection.
Obtain the face of intersection.
Obtain the normal of the face (2).
Get the normal (3) in world coordinates.
Set position of the text object as sum of point of intersection (1) and the normal in world coordinates (4).
Set lookAt() vector of the text object as sum of its position (5) and the normal in world coordinates (4).
Seems long, but actually it makes not so much of code:
var PGHelper = new THREE.PolarGridHelper(...); // let's imagine it's your text object ;)
var PGlookAt = new THREE.Vector3(); // point of lookAt for the "text" object
var normalMatrix = new THREE.Matrix3();
var worldNormal = new THREE.Vector3();
and in the animation loop:
for ( var i = 0; i < intersects.length; i++ ) {
normalMatrix.getNormalMatrix( intersects[i].object.matrixWorld );
worldNormal.copy(intersects[i].face.normal).applyMatrix3( normalMatrix ).normalize();
PGHelper.position.addVectors(intersects[i].point, worldNormal);
PGlookAt.addVectors(PGHelper.position, worldNormal);
PGHelper.lookAt(PGlookAt);
}
jsfiddle exmaple
The method works with meshes of any geometry (checked with spheres and boxes though ;) ). And I'm sure there are another better methods.
very interesting question.I have tried this way, we can regard the text as a plane. lets define a normal vector n from your sphere center(or position) to point on the sphere surface where you want to display text. I have a simple way to make normal vector right.
1. put the text mesh on sphere center. text.position.copy(sphere.position)
2. make text to the point on sphere surface, text.lookAt(point)
3.relocate text to the point. text.position.copy(point)

ThreeJS: PlaneBufferGeometry, raycasting and faces

Made a buffered plane, set its vertices with:
var vertices = tg.attributes.position.array;
geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3));
Now i want to raycast to a face to get it's Z value:
var z = intersects[i].object.geometry.vertices[intersects[i].face.a].z;
This worked on a standard geometry as it had faces and lot of other things i'm trying to save from memory.
My question comes from the index: intersects[i].face.a. What do i have to add? There seem not to be a method to add "faces" to the buffered geometry. Right now there is just one face for the whole geometry at:
object.face.(a,b,c)
Perhaps there is another way of clicking on a face and getting it's vertex value when using buffered geoms.
Tips? Thanks!
Buffer geometries contain attributes of positions in an array.
If you want to obtain information about z-value of a specific vertex from a buffer geometry's vertices then you can do it like this:
intersects[i].object.geometry.attributes.position.array[intersects[i].face.a * 3 + 2]
also you can use the z-coordinate of the point of intersection (which is in world's coordinates):
intersects[i].point.z;
jsfiddle example (see function showDetails(intersect), the green plane is THREE.PlaneGeometry, the blue plane is THREE.PlaneBufferGeometry)

Setting the projectionMatrix of a Perspective Camera in Three.js

I'm trying to set the ProjectionMatrix of a Three.js Perspective Camera to match a projection Matrix I calculated with a different program.
So I set the camera's position and rotation like this:
self.camera.position.x = 0;
self.camera.position.y = 0;
self.camera.position.z = 142 ;
self.camera.rotation.x = 0.0;// -0.032
self.camera.rotation.y = 0.0;
self.camera.rotation.z = 0;
Next I created a 4x4 Matrix (called Matrix4 in Three.js) like this:
var projectionMatrix = new THREE.Matrix4(-1426.149, -145.7176, -523.0170, 225.07519, -42.40711, -1463.2367, -23.6839, 524.3322, -0.0174, -0.11928, -0.99270, 0.43826, 0, 0, 0, 1);
and changed the camera's projection Matrix entries like this:
for ( var i = 0; i < 16; i++) {
self.camera.projectionMatrix.elements[i] = projectionMatrix.elements[i];
}
when I now render the scene I just get a black screen and can't see any of the objects I inserted. Turning the angle of the Camera doesn't help either. I still can't see any objects.
If I insert a
self.camera.updateProjectionMatrix();
after setting the camera's projection Matrix to the values of my projectionMatrix the camera is set back to the original Position (x=0,y=0,z=142 and looking at the origin where I created some objects) and the values I set in the camera's matrix seem to have been overwritten. I checked that by printing the cameras projection Matrix to the console. If I do not call the updateProjectionMatrix() function the values stay as I set them.
Does somebody have an idea how to solve this problem?
If I do not call the updateProjectionMatrix() function the values stay as I set them.
Correct, updateProjectionMatrix() calculates those 16 numbers you pasted in your projection matrix based on a bunch of parameters. Those parameters are, the position and rotation you set above, plus the parameters you passed (or default) for the camera. (these actually make the matrixWorld and its inverse.
In case of a perspective camera, you don't have much - near, far, fov and aspect. Left,right,top,bottom are derived from these, with an orthographic camera you set them directly. These are then used to compose the projection matrix.
Scratch a pixel has a REALLY good tutorial on this subject. The next lesson on the openGL projection matrix is actually more relevant to WebGL. left right top and bottom are made from your FOV and your aspect ratio. Add near and far and you've got yourself a projection matrix.
Now, in order for this thing to work, you either have to know what you're doing, or get really lucky. Pasting these numbers from somewhere else and getting it to work is short of winning the lottery. Best case scenario, you can have your scale all wrong and clipping your scene. Worst case, you've mixed a completely different matrix, different XYZ convention, and there's no way you'll get it to work, or at least make sense.
Out of curiosity, what are you trying to do? Are you trying to match your camera to a camera from somewhere else?

Resources