How to programmatically undo positional translation to pivot point? - three.js

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().

Related

Efficient way to translate a camera dolly

I currently have a VR camera attached to a dolly in order to allow for translation and rotation.
I'm trying to translate the dolly, based on gamepad inputs, relative to the orientation of the camera (which is linked to a VR headset.)
I'm also trying to avoid letting the dolly pitch up or down relative to the camera.
My current code looks something like this:
this.camerDirectionVector = new THREE.Vector3()
this.camera.getWorldDirection(this.cameraDirectionVector)
this.moveVec.y = 0
this.dolly.translateOnAxis(this.cameraDirectionVector, this.gamepad.axes[0] * this.moveSpeed)
This works great for moving the dolly in the direction the camera is pointing (minus y rotation).
What I can't figure out is how to also translate the dolly "left and right" relative to the camera based off an additional gamepad input.
Based on the comments on the question, I think I understand. If I don't, please leave a comment, and I'll update this answer.
My understanding is that you want to be able to move left and right, with respect to the camera, all without altering the dolly's up direction.
This is actually easier than it sounds, and is even easier because you are already comfortable translating along an axis.
First, understand that the camera has its own spatial frame of reference, where it sits at the origin, with a +Y up direction, and it looks down the -Z axis. With this in mind, you already know the "left" and "right" axes: -X (-1, 0, 0) and +X (1, 0, 0).
But the camera (especially in VR) might not be so nicely aligned in world space, so you need to convert these nice uniform axes into world axes. Three.js makes this very easy using Object3D.localToWorld.
(Note: Object3D.localToWorld is destructive to the input Vector3.)
Here's a function to get the world-aligned left axis:
const getLeftWorld = (function(){
const localLeft = new THREE.Vector3(-1, 0, 0);
return function(vectorRef){ // you can give it a vector to overwrite
let out = vectorRef || new THREE.Vector3();
out.copy(localLeft);
camera.localToWorld(out);
return out;
};
))();
You can create a similar function for the "right" axis.
With your new world-aligned left axis in hand, you can translate the dolly along it, using the "speed" given by your controller input. Translation won't change the pitch of the dolly, though it may change the elevation, depending on how the camera is tipped at the time of computation (but you can just zero-out the y component like you did before, if you want).
This is the solution that ended up working best for me. I've adapted it from Brian Peiris code here: https://github.com/brianpeiris/three-firstperson-vr-controls/blob/master/FirstPersonVRControls.js#L125
// Create a dolly
this.dolly = new THREE.Group()
this.dolly.add(this.camera)
this.scene.add(this.dolly)
// Some variables for movement translations
this.dummy = new THREE.Object3D()
this.dummyDirection = new THREE.Vector3()
this.ZAXIS = new THREE.Vector3(0, 0, 1)
this.moveSpeed = 0.075
// Get controller stick positions
const stickForwardBack = this.leftStick[3]
const stickLeftRight = this.leftStick[2]
// In THREE.js when using the WebXR API, the camera rotation will not update to match the headset orientation.
// You'll need to get pose information from the XRSession or get the xr camera using the following method.
const xrCamera = globals.renderer.xr.getCamera(this.camera)
this.dummy.position.set(0, 0, 0)
this.dummy.quaternion.copy(xrCamera.quaternion)
this.collapseY(this.dummy.quaternion)
// Translate the dummy object forwards/backwards left/right relative to the direction the camera is facing
this.dummy.translateZ(stickForwardBack * this.moveSpeed)
this.dummy.translateX(stickLeftRight * this.moveSpeed)
// Add the dummy position to the dolly
this.dolly.position.add(this.dummy.position)
// Flatten out up and down rotation
collapseY(quaternion) {
this.dummyDirection.set(0, 0, 1)
this.dummyDirection.applyQuaternion(quaternion)
this.dummyDirection.y = 0
this.dummyDirection.normalize()
quaternion.setFromUnitVectors(this.ZAXIS, this.dummyDirection)
}

My mesh flips for a rotation smaller than math.pi

I am coming back since I am having this geometric problem that I am not familiar with on Unity.
For a f-zero style game, I have a collider box (white on the screen captures) which is the origin of my raycast, and is bound to the movement of the vehicle.
In the shown code, this is this.collider. I control its rotation via a traditional applymatrix and there is no problem.
Then, on top of that, I have the rendered body of the vehicle in this.meshes. It inherits the rotation of the collider box, but gets some extra rotation on its vertical axis to give a visual sliding dynamic during the hard turns.
It is separate from the collider to keep the vector.forward of the movement (and the raycast) not affected by the extra-rotation. This is purely visual.
My question is: what is the best way to implement it?
I tried different things, but, basically, if I copy the position and rotation of the collider, no problem. As soon as I try to add some extra rotation = this.driftRotation, my body flips when rotation.y value is less than -math.pi. I can adjust the value of the rotation by incrementing Math.PI (like in Unity), but it doesn't work here.
No clean solution found with applyMatrix neither, and not a lot of google answers on "vertical rotation flip mesh"... though I'm pretty sure this pissue is common.
Some code:
this.meshes.position.set(
this.collider.position.x,
this.collider.position.y,
this.collider.position.z);
this.meshes.rotation.x = this.collider.rotation.x;
this.meshes.rotation.y = this.collider.rotation.y + this.driftRotation;
this.meshes.rotation.z = this.collider.rotation.z;
Enclosed more explicit pictures:
Thank you
Marquizzo, that's precisely the point: the 3rd px follows the 2nd one, so I'm still turning right but rotation suddenly flips (again, when rotation.y reaches -PI).
Anyway, I fixed it by not trying to directly change rotation.y value, but playing with matrix. Just takes time to understand what does what.
For those who may face a similar pb, here is my temp solution, until I find sthing more performant:
this.meshes.matrix.identity();
if (Math.abs(driftAmount) > 0)
{
this.driftAxis.copy(this.driftDirection);
this.driftValue = js.Utils.lerp(this.driftValue, Math.sign(driftAmount) * 0.4, 0.05);
this.meshes.matrix.makeRotationAxis(this.driftAxis, this.driftValue);
}
else if (Math.abs(this.driftValue) > 0)
{
this.driftAxis.copy(this.driftDirection);
this.driftValue = js.Utils.lerp(this.driftValue, 0, 0.1);
if (Math.abs(this.driftValue) < 0.001)
{
this.driftValue = 0;
}
this.meshes.matrix.makeRotationAxis(this.driftAxis, this.driftValue);
}
this.meshes.applyMatrix(this.collider.matrix);
I had to add a driftAxis along a driftDrection, which is my axis for my vertical rotation.
For ref. I think this subject is +/- bound to the issue I had:
https://github.com/mrdoob/three.js/issues/1460
Now I have another issue, how to add another rotation to this.meshes on another axis, the forward one, for a rolling effect, because if I just add another makeRotationAxis in this code it just skips the first one. But that sounds less difficult to figure out, there must exist the equivalent of combineMatrix something...

Threejs, What is the proper way to normalize a vector for correct direction on arrow helper

This is most likely a math issue. I have a line geometry that feeds its vertices positions to an arrow helper. The arrow helper runs normalize() on its direction vector to render its output. If I lay them over each other in the view the arrow vector shoots upward while the line is perfectly drawing to the right of the screen. I am expecting the arrow helper to be in sync with the line geometry.
var origin = new THREE.Vector3(0, 10, 0);
var direction = new THREE.Vector3(80, 10, 0);
direction.normalize();
/*
direction after its normalized
Object
x: 0.9922778767136676
y: 0.12403473458920845
z: 0
*/
http://jsbin.com/nazapabaxaco/1/edit?html,js,output
Mathematically this is actually correct for the arrow helper vector. But its not the expected result. If I increase the distance X to say 500 the two converge again but not completely, they are still out of sync and just shooting apart at a really long distance.
The vector in use would be coming from a moving object and not the camera's unproject method so the points need to remain dynamically fed into.
You compute the direction vector by subtracting one line segment endpoint from the other, and then normalizing.

How do I create a bouncing or jumping motion with Phaser.io in a Tween?

I have the following code:
var lion = game.add.sprite(2, 2,'lion');
var jump = game.add.tween(lion);
jump.to({x: 1000, y: 1000 }, 10000, Phaser.Easing.Linear.None);
// ...
jump.start();
I create a sprite and would like to make it move between two points, here I am moving the lion from the top left corner to some point at the bottom right (1000,1000). Is it possible to add a bouncing motion to this movement?
At the moment the lion is moving in a straight line, but I would like to make it look as if the lion were jumping, like this:
How would I achieve this? Are tweens actually capable of producing a complex path like this?
Although the API is tricky and pooly documented (imho), I managed to find a good point to achieve this behavior. It took me 2 days to wrap my head around how tweening works and where to apply my changes.
When a tween is created, you can pass it an easing function. The easing I want applys to the Y axis only (the bouncing motion) and the movement to the right applys to the X axis only. Therefore I have to use two individual Tweens:
function vanHalen (v) { // Might as well jump
game.debug.spriteInfo(lion, 32, 32);
return Math.sin(v * Math.PI) * 1;
};
function goLion() {
var move = game.add.tween(lion);
var jump = game.add.tween(lion);
// "Move" is a linear easing function that will move the sprite to (1000,y). It takes the Lion 2 Seconds to get there.
move.to({x: 1000}, 2000);
// The Jump is a function that works on the y axis, uses a customized easing function to "bounce".
jump.to({y: 30}, 500, vanHalen, true, 0, Number.MAX_VALUE, 0);
move.start();
};
The jumping starts automatically and never ends. When the movement to the right is over, the lion will continue bouncing in one place.
The easing function receives a progress value (between 0 and 1), that indicates how far the tween has moved (in percent).

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

Resources