three.js parent child target tracking - matrix

So, here is a vector, matrix, rotation, quaternion question for the three.js masters!
I have a parent object at position(0,0,275) with direction(0,0,-1), +y up and rotation(0,0,0).
Attached to the parent is a child object with relation:
rel_position(.5, 0, 0)
rel_rotation(0, 0, 0)
I also have a target in the scene:
position(0, 100, 0)
Consider a ship(parent) with pitch, yaw and roll. It has a turret(child) with pitch and yaw. The turret needs to track the target at a given rotational speed (rad/s).
I have spent 5 days trying to get a proper tracking algorithm. The best I can do is remove the matrixAutoUpdate feature on the parent and child updating the matrix manually. Then use the child.matrixWorld to create rotation matrices or quaternions for the current child rotation. Then I can create a secondary Object3D which looks at the target from the child position. I can take the difference between the child and the secondary Object3D and slerp the quaternion on a rad/s basis. However, when I add roll to the parent or the target is rotated outside of octant (+, +, +) the child rotation (or quaternion) calculations crash causing wild rotations.
Any direction on a tracking algorithm for a child Object3D would be immensely appreciated. Your efforts will be cited in my upcoming project.
Thank you for your time!

So here is the answer! #Stemkoski, thanks for the direction. I took a similar approach for a final solution that came to light yesterday. It is a bit a costly in computational power due to inverting a matrix, but it works well. First, create a new matrix as follows:
var m = new THREE.Matrix4().getInverse(parent.matrix).multiply(target.matrix);
This rotates the reference frame of the XYZ axis to new coordinates X'Y'Z' based on the rotation of the parent object. The result is an Object3D matrix of the target in the reference frame of X'Y'Z'. Now by creating a new Vector3 from the matrix 'm' we can get a relative position using:
var rel_pos = new THREE.Vector3().getPositionFromMatrix(m, 'XYZ');
Now create a Faux object to look directly at target from the position of the child(turret)
var child_faux = new THREE.Object3D(); //can be created outside the loop
child_faux.position.copy(child.position); //copy the child local position
child_faux.lookAt(rel_pos); //look at the target
child_faux.updateMatrix(); //Update the Matrix4 element for use in a second
Finally, by setting the child_fuax to lookAt an object we have automatically set the child_faux.rotation. We can obtain the relative rotation between the current child.rotation and the child_faux.rotation using the following:
var diff = turret_target.rotation.sub(turret.rotation);
From here we can set an easy algorithm to rotate the turret (+ or -)(x and y) until the diff (x and y) goes to 0.
var trackrate = .2 * delta;
var diff = turret_target.rotation.sub(turret.rotation);
turret.rotation.x += diff.x/Math.abs(diff.x) * trackrate;
turret.rotation.y += diff.y / Math.abs(diff.y) * trackrate;
turret.updateMatrix();
There we have it...A target tracking algorithm for a child object rotated in a parent group! Thanks for reading!

Perhaps some combination of the lookAt and worldToLocal methods would work? Since you need to convert the position of the target object into local coordinates for the turret, and then change the rotation of the turret accordingly, how about this:
turret.lookAt( turret.worldToLocal( target.position.clone() ) )

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);

How to programmatically undo positional translation to pivot point?

I think this is ultimately a pretty simple question, but it's hard to describe, thus, I provide a working example here (in the sample press 'z' to see rotation with unwanted translation and 'x' keys to rotate with a compensating re-position).
Basically, I am trying to rotate an object (a thumbstick) about the z-axis of a complex model loaded via gltf (a model of the oculus rift touch controller). It's easy to rotate about the x-axis because it's 90 deg. orthogonal to the x-axis. About the z-axis, it's harder because the plane the thumbstick is attached to is angled at 30 deg. I realize that if the thumbstick were using local coordinates, this wouldn't be a problem, but 'thumb.rotation.z' does not seem to be using local coordinates and is rotating about the model's (as a whole), or maybe even the scene's global y and z (?). Anyway, after a bunch of futzing around, I was able to get things to work by doing the following:
// occulus plane is angle at 30 deg, which corresponds to
// 5 units forward to 3 units down.
var axis = new THREE.Vector3(0, 5, -3).normalize();
factory.thumbstick.geometry.center();
var dir = (evt.key === 'x' ? 1 : -1);
thumb.rotateOnAxis(axis, factory.ONE_DEG * 5.0 * dir);
Basically, I'm rotating about a "tilted" axis, and then calling 'center' to make thumbstick centered on the pivot point, so it rotates about the pivot point, rather than around the pivot point (like the earth orbiting the sun).
Only problem is that when you call 'geometry.center()' and then call 'rotateOnAxis', it translates the thumbstick to the pivot point:
Note: the position on the thumbstick object is (0,0,0) before and after the calls.
I have empirically determined that if I alter the position of the thumbstick after the translation like so:
// magic numbers compensating position
var zDisp = 0.0475;
var yDisp = zDisp / 6.0
thumb.position.x = 0.001;
thumb.position.y = -yDisp;
thumb.position.z = zDisp;
Then it (almost) returns back to it's original position:
Problem is these numbers were just determined by interactively and repeatedly trying to re-position the thumbstick i.e. empirically. I simply cannot find a programmatic, analytical, api kind of way to restore the original position. Note: saving the original position doesn't work, because it's zero before and after the translation. Some of the things I tried were taking the difference between the bounding spheres of the global object and the thumbstick object, trying to come up with some 'sin x- cos x' relation on one distance etc. but nothing works.
My question is, how can I progammatically reverse the offset due to calling 'geometry.center()' and rotateOnAxis (which translates to the pivot point), without having to resort to hacked, empircal "magic" numbers, that could conceivably change if the gltf model changes.
Of course, if someone can also come up with a better way to achieve this rotation, that would be great too.
What's throwing me is the (peceived?) complexity of the gltf model itself. It's confusing because I have a hard time interpreting it and it's various parts: I'm really not sure where the "center" is, and in certain cases, it appears with the 'THREE.AxesHelper' I'm attaching that what it shows as 'y' is actually 'z' and sometimes 'up' is really 'down' etc, and it gets confusing fast.
Any help would be appreciated.
The breakthrough for me on this was to re-frame the problem as how do I change the pivot point for the thumbstick, rather than how do I move the thumbstick to the (default and pre-existing) pivot point. To paraphrase JFK, "ask not how you can move to the pivot, but ask how the pivot can move to you" :-)
After changing my angle of attack, I pretty quickly found the aforementioned link, which yielded my solution.
I posted an updated glitch here, so now pressing z works as I expected. Here is the relevant code portion:
factory.onModelLoaded = function(evt) {
console.log(`onModelLoaded: entered`);
factory.thumbstick = this.scene.children[1].children[2]
let thumb = factory.thumbstick;
// make the thumb red so it's easier to see
thumb.material = (new THREE.MeshBasicMaterial({color: 0xFF7777}));
// use method from https://stackoverflow.com/questions/28848863/threejs-how-to-rotate-around-objects-own-center-instead-of-world-center/28860849#28860849
// to translate the pivot point of the thumbstick to the the thumbstick center
factory.thumbParent = thumb.parent;
let thumbParent = factory.thumbParent;
thumbParent.remove(thumb);
var box = new THREE.Box3().setFromObject( thumb );
box.getCenter( thumb.position ); // this basically yields my prev. "magic numbers"
// thumb.position.multiplyScalar( - 1 );
var pivot = new THREE.Group();
thumbParent.add( pivot );
pivot.add( thumb );
thumb.geometry.center();
// add axeshelp after centering, otherwise the axes help, as a child of thumb,
// will increase the bounding box of thumb, and positioning will be wrong.
axesHelper = new THREE.AxesHelper();
thumb.add(axesHelper);
}
Which allows my "z" handler to just rotate without having to do translation:
case 'z':
case 'Z':
var axis = new THREE.Vector3(0, 5, -3).normalize();
var dir = (evt.key === 'z' ? 1 : -1);
thumb.rotateOnAxis(axis, factory.ONE_DEG * 5.0 * dir);
break;
Interestingly, it's the call to box.getCenter() that generates numbers very close to my "magic numbers":
box.getCenter()
Vector3 {x: 0.001487499801442027, y: -0.007357006114165027, z: 0.04779449797522323}
My empirical guess was {x: 0.001, y: -0.00791666666, z: 0.0475} which is %error {x: 32.7%, y: 7.6%, z: 0.61%}, so I was pretty close esp. on the z component, but still not the "perfect" numbers of box.getCenter().

Get screen coordinates of a vertex in a THREE.js Points object using bufferGeometry

I want to have a DOM node track a particle in my THREE.js simulation. My simulation is built with the Points object, using a bufferGeometry. I'm setting the positions of each vertex in the render loop. Over the course of the simulation I'm moving / rotating both the camera and the Points object (through its parent Object3d).
I can't figure out how to get reliable screen coordinates for any of my particles. I've followed the instructions on other questions, like Three.JS: Get position of rotated object, and Converting World coordinates to Screen coordinates in Three.js using Projection, but none of them seem to work for me. At this point I can see that the calculated projections of the vertices are changing with my camera movements and object rotations, but not in a way that I can actually map to the screen. Also, sometimes two particles that neighbor each other on the screen will yield wildly different projected positions.
Here's my latest attempt:
const { x, y, z } = layout.getNodePosition(nodes[nodeHoverTarget].id)
var m = camera.matrixWorldInverse.clone()
var mw = points.matrixWorld.clone()
var p = camera.projectionMatrix.clone()
var modelViewMatrix = m.multiply(mw)
var position = new THREE.Vector3(x, y, z)
var projectedPosition = position.applyMatrix4(p.multiply(modelViewMatrix))
console.log(projectedPosition)
Essentially I've replicated the operations in my shader to derive gl_Position.
projectedPosition is where I'd like to store the screen coordinates.
I'm sorry if I've missed something obvious... I've tried a lot of things but so far nothing has worked :/
Thanks in advance for any help.
I figured it out...
var position = new THREE.Vector3(x, y, z)
var projectedPosition = position.applyMatrix4(points.matrixWorld).project(camera)

Spawn particle at edge of screen

I've searched far and wide, so if there's a similar question please forgive me but I just couldn't find it.
To put what I'm trying to do in context: I want to create an infinitely-generated field of stars that disappear as they go offscreen and reappear at the edge of the screen where the camera is moving. I'm working with a top-down view, so it must be pretty simple to achieve this, but alas I haven't a clue.
I'm using the following code to determine whether a star has gone off-screen and then replace it:
//update camera frustum
camera.projScreenMatrix.multiplyMatrices(
camera.projectionMatrix,
camera.matrixWorldInverse
);
camera.frustum.setFromMatrix(camera.projScreenMatrix);
//loop through stars
var stars=scene.stars.geometry.vertices;
for(var i=0;i<stars.length;i++) {
if(!camera.frustum.containsPoint(stars[i])) {
stars[i]=new THREE.Vector3(
// fill in the blank
);
scene.stars.geometry.verticesNeedUpdate=true;
}
}
Since I'm using a perspective camera, I know I'll need to somehow factor in camera.fov and other perspective elements, but as you can tell I'm no expert on the third dimension.
Assuming I have an angle or normalized vector telling me the direction the view is panning, how would I go about creating a vertex along the edge of the screen regardless of its Z position?
If I'm not clear enough, I'll be happy to clarify. Thanks.
I know this is an old question, but I came across it while looking for an answer and found a simple, trigonometry reliant method to get the left edge of the camera frustum, and I'm sharing it in case someone else might find it useful:
// Get half of the cameras field of view angle in radians
var fov = camera.fov / 180 * Math.PI / 2;
// Get the adjacent to calculate the opposite
// This assumes you are looking at the scene
var adjacent = camera.position.distanceTo( scene.position );
// Use trig to get the leftmost point (tangent = o / a)
var left = Math.tan( fov ) * adjacent * camera.aspect;
Basically, this gets the leftmost point, but if you don't multiply by the aspect ratio you should get a point in a circle around your camera frustum, so you could translate a point any direction away from the cameras focus and it would always be outside the frustum.
It works by assuming that the imaginary plane that is the camera is perpendicular to the line connecting the camera and its focus, so there is a straight angle. This should work if you want objects further away as well (so if you want them at a further point from the camera you just need to increase the distance between the focus and the camera).
Well, countless headaches and another question later, I've come up with a fairly makeshift answer. Just in case by some unlikely chance someone else has the same question, the following function plots a point on the scene relative to the camera's current view with whatever Z specified:
//only needs to be defined once
var projector=new THREE.Projector();
//input THREE.Vector3
function(vector) {
var z=vector.z;
vector.z=0;
projector.unprojectVector(vector,camera);
return camera.position.clone().add(
vector
.sub(camera.position)
.normalize()
.multiplyScalar(
-(camera.position.z-z)/vector.z
)
);
The x and y, in this case, both range from -1 to 1 for bottom-left to top-right. You can use position/window.Width and position/window.Height for extra precision (using mouse coordinates or what have you).

Three.js—rotation around arbitrary line

I am starting with Three.js so I might have misunderstood some basics of the concept. I have a usual 3d scene with a hierarchy like this:
.
+-container #(0,0,0) (Object3d, no own geometry)
+-child 1 #(1,1,1)
+-child 2 #(1, -2, 5)
+-child 3 #(-4, -2, -3)
.
.
. more should come
all »children« of the »container« are imported models from Blender. What I would like to do is to rotate the whole container around a pivot axis based on the current selection, which should be one of the children.
Image three cubes in Blender, all selected with the 3d cursor at center of first in location and center of transformation. A rotation transforms all cubes, but the rotation is relative to the first in selection.
In terms of three.js, what would like to do is to rotate the container, so that the rotation is applied to all children.
To do that I think that the following steps should do the trick:
create a matrix,
translate that matrix by the negative of the selected objects position
rotate that matrix
translate the matrix back to the selected objects position
apply the transform to the container
I have tried the following code but the result is just wrong:
var sp = selection.position.clone(),
m = new THREE.Matrix4();
selection.localToWorld(sp);
m.setPosition(sp.clone().negate());
//I've used makeRotationX for testing purposes, should be replaced with quaternion rotation later on…
m = m.multiply(new THREE.Matrix4().makeRotationX(2*180/Math.PI));
m = m.multiply(new THREE.Matrix4().makeTranslation(sp.x,sp.y,sp.z));
this._container.applyMatrix(m);
Thanks for help!
UPDATE
sign error—this works:
var sp = selection.position.clone(),
m = new THREE.Matrix4();
m.makeTranslation(sp.x,sp.y,sp.z);
m.multiply(new THREE.Matrix4().makeRotationX(0.1));
m.multiply(new THREE.Matrix4().makeTranslation(-sp.x,-sp.y,-sp.z));
this._container.applyMatrix(m);
BUT that code does not really look that good, creating three matrices for that single operating seems to bit of overhead, what is the usual »three.js-way«?
UPDATE #2
Due to the comment here is an image describing what I would like to do:
The »arrows« at the origin stand for the parent container and the cube, the sphere and the cone are its »children«. The red line shows the line I would like rotate the parent around, this way the rotation is applied to all children.
rotateOnAxis() takes a Vector as axis, so the line the objects rotates around crosses its origin.

Resources