ThreeJS oribital camera short rotation - javascript

I have an orbital camera that orbits are a globe. There are several markers on the globe that the user can click on, and the camera will move to that point.
Using TweenMax for the animation like this -
TweenMax.to(currentPos, 3, {
theta:targetPos.theta,
phi:targetPos.phi,
radius:targetPos.radius,
ease:Circ.easeIn,
onComplete:btnZoomComplete,
onUpdateParams:["{self}"],
onComplete:SataliteTweenComplete,
onUpdate: function(tween) {
controls.setThetaPhi(tween.target.theta, tween.target.phi, tween.target.radius);
}
});
This works great, however is doesn't take into consideration the shortest route to get there. So it can quite often go 'round the back' of the globe.
ThreeJS seems to measure the angle in a really strange unit system:
0, 1.57 (equivalent to 90 degrees), 3.14 (eq 180dg), then after 3.14 is jumps to -3.14, -1.57 (eq to 270dg), then back to 0... So this blowing my mind on how to work it out.
For example, say the camera is at 2.6 and it needs to go over to -2.61, at the moment the camera will animate CCW (2.6 to -2.16), where as visual it needs to animate CW, which would move from 2.6 to 3.14, -3.14 then to -2.61.
Any help on this would be really appreciated.
I guess there are two problems, how to work out which way to go round, but then how to actually animate across from 2.6 -> 3.14, jump to -3.14 seamlessly -> -2.61

So that "strange unit-system" is just radians and it's quite common to measure theta/phi values in a range from -180° to 180° and -90° to 90° (think latitude/longitude, same thing). The conversion is simple:
angleDegrees = radians / Math.PI * 180;
radians = angleDegrees / 180 * Math.PI;
Now the tweening-library will just interpolate from one value to the other and doesn't know what these values represent. So it simply can't know how to handle the shortest path when it comes to rotations. However, you can do this before starting the tween.
Say we animate from 2.6 to -2.6 (or 149° to -149°).
var from = 2.6, to = -2.6;
The direction and angular distance for the animation can be calculated as
var distance = to - from;
// === -5.2
A negative value here means counterclockwise, and 5.2 (~298°) is the "distance" the camera will travel. Now keep in mind that any angle plus or minus 360° (2 * Math.PI) will essentially land you at the same position. So lets try:
var distance = (to + 2 * Math.PI) - from;
// === 1.083185307179586 (~62°)
So, if you rotate from your position at 2.6 to -2.6 + 2 * Math.PI (or, from 149° to -149° + 360° = 211°), you will get a clockwise animation with a shorter path.
To make sure that all values stay in their allowed range, we change the onUpdate-function a little bit to wrap around properly:
controls.setThetaPhi(
tween.target.theta % Math.PI,
tween.target.phi % Math.PI,
tween.target.radius);
You will probably also want to update the currentPos value with the actual values before the animation starts and below computation happens.
What's left to do is solving this for the general case, so to find out when to do the clockwise and counterclockwise rotation. To see if the other way around would be shorter, we just need to see if the distance would be greater than 180°:
if (Math.abs(to - from) > Math.PI) {
if (to > 0) { // if to is positive we remove a full-circle, add it otherwise
to = to - 2 * Math.PI;
} else {
to = to + 2 * Math.PI;
}
}

Related

Rotating to point at something in radians without wrapping around zero

Let's say I have a circle with a line sticking out of it.
I want that line to point at the center of the window, no matter where the circle moves to.
But, I want that line to slowly move to that angle. I don't want the rotation to be calculated and set every single frame, but rather calculated and tweened to that direction.
The issue I'm having with this is that if you move to make the line rotate around where the radians meet 0, it will do a full 360 (or 3.14 in rads ;) to get to that point.
I have spent a while trying to think of how to explain this best, here is a codepen that can hopefully help clarify what I'm asking
// CenterX/Y is the center of the screen.
// dotX/Y is the center of the circle.
var angleToCenter=Math.atan2(centerY-dotY,centerX-dotX);
if (angleToCenter<currentAngle) {
currentAngle-=0.05;
} else {
currentAngle+=0.05;
}
if you move to the right of the screen, then go above or below the center, you will see the line move in a full circle to try to get to the calculated direction. How do I avoid this? I want the line to seamlessly rotate to point at the center, via the shortest possible way, not by doing a full circle.
Great question. Very different.
I would have an inverse (-1) relationship defined for any location below the black circle. Such that, if the red circle crosses a horizontal axis - whose boundry is defined by the black circle - the mathematical result to your equation is inversed.
This would make 0 degrees as we typically think of it, now positioned at 180 degrees.
Reasoning: Looking at your CodePen it's obvious that the stem is going "the long way around", but you want it to go the "short way around". The most intuitive way to make that happen would seem to be to inverse the red-circles calculated rotation. The simplest method I can think of would be to inverse the polarity of the circle.
The problem lies in the point where angleToCenter switches from Math.PI to -Math.PI (and vice versa).
Therefore I'd suggest you create an "epsilon angle distance", in which the angles will be hard-coded:
var angleToCenter = Math.atan2(centerY - dotY, centerX - dotX);
var epsilon = 0.05;
if (Math.abs(angleToCenter - Math.PI) <= epsilon / 2 || Math.abs(angleToCenter + Math.PI) <= epsilon / 2) {
if (dotY > centerY) {
currentAngle = -Math.PI;
} else if (dotY < centerY) {
currentAngle = Math.PI;
}
} else if (angleToCenter < currentAngle - epsilon) {
currentAngle -= epsilon;
} else {
currentAngle += epsilon;
}
For the full edit, you can check my fork to your CodePan

Calculating a quaternion from two combined angles

I'm creating a script that rotates a THREE.js camera arround based on a mobile phones gyroscope input. It's currently working pretty well, except that every time I rotate my phone over a quadrant, the camera will turn 180 degrees instead of continuing as intended. This is the code that I currently use:
private onDeviceOrientation = ( event ) => {
if( event.alpha !== null && event.beta !== null && event.gamma !== null ) {
let rotation = [
event.beta,
event.alpha,
event.gamma
],
this.orientation = new THREE.Vector3(rotation[0], rotation[1], rotation[2]);
this.viewer.navigation.setTarget(this.calcPosition());
}
};
private calcPosition = () => {
const camPosition = this.viewer.navigation.getPosition(),
radians = Math.PI / 180,
aAngle = radians * - this.orientation.y,
bAngle = radians * + this.orientation.z,
distance = this.calcDistance();
let medianX = Math.cos(bAngle) * Math.sin(aAngle);
let medianY = Math.cos(bAngle) * Math.cos(aAngle);
let nX = camPosition.x + (medianX * distance),
nY = camPosition.y + (medianY * distance),
nZ = camPosition.z + Math.sin(bAngle) * distance;
return new THREE.Vector3(nX, nY, nZ);
};
window.addEventListener('deviceorientation', this.onDeviceOrientation, false);
Soafter doing some research I found that I need to use a Quaternion prevent the switchen when going into a new quadrant. I have no experience with Quaternions, so I was wondering what the best way would be to combine the two Vector3's in the code above into a singel Quaternion.
[Edit]
I calculate the distance using this method:
private calcDistance = (): number => {
const camPosition = this.viewer.navigation.getPosition();
const curTarget = this.viewer.navigation.getTarget();
let nX = camPosition.x - curTarget.x,
nY = camPosition.y - curTarget.y,
nZ = camPosition.z - curTarget.z;
return Math.sqrt((nX * nX) + (nY * nY) + (nZ * nZ));from squared averages
};
And I follow the MDN conventions when working with the gyroscope.
[Edit #2]
Turns out I had my angle all wrong, I managed to fix it by calculating the final position like this:
let nX = camPosition.x - (Math.cos(zAngle) * Math.sin(yAngle)) * distance,
nY = camPosition.y + (Math.cos(zAngle) * Math.cos(yAngle)) * distance,
nZ = camPosition.z - (Math.cos(xAngle) * Math.sin(zAngle)) * distance;
Here is the closest I can give you to an answer:
First of all, you don't need a quaternion. (If you really find yourself needing to convert between Euler angles and quaternions, it is possible as long as you have all the axis conventions down pat.) The Euler angle orientation information you obtain from the device is sufficient to represent any rotation without ambiguity; if you were calculating angular velocities, I'd agree that you want to avoid Euler angles since there are some orientations in which the rates of change of the Euler angles go to infinity. But you're not, so you don't need it.
I'm going to try to summarize the underlying problem you're trying to solve, and then tell you why it might not be solvable. 🙁
You are given the full orientation of the device with a camera, as yaw, pitch, and roll. Assuming yaw is like panning the camera horizontally, and pitch is like tilting the camera vertically, then roll is a degree of freedom that doesn't change affect direction the camera is pointing, but it does affect the orientation of the images the camera sees. So you are given three coordinates, where two have to do with the direction the camera is pointing, and one does not.
You are trying to output this information to the camera controller but you are only allowed to specify the target location, which is the point in space that the camera is looking. This is to be specified via three Cartesian coordinates, which you can calculate from the direction the camera is pointing (2 degrees of freedom) and the distance to the target object (one degree of freedom).
So you have three inputs and three outputs, but only two of those have anything to do with each other. The target location has no way to represent the roll direction of the camera, and the orientation of the camera has no way to represent the distance to some target object.
Since you don't have a real target object, you can just pick an arbitrary fixed distance (1, for example) and use it. You certainly don't have anything from which to calculate it... if I follow your code, you are defining distance in terms of the target location, which is itself defined in terms of the distance from the previous step. This is extra work for no benefit at best (the distance drifts around some initial value), and numerically unstable at worst (the distance drifts toward zero and you lose precision or get infinities). Just use a fixed value for distance and make it simple.
So now you probably have a system that points a camera in a direction, but you cannot tell it what the roll angle is. That means your camera controller is apparently just going to choose it for you based on the yaw and pitch angles. Let's say it always picks zero degrees (that would be the least crazy thing it could do). This will cause discontinuities when the roll angle and yaw angle line up (when the pitch is at ±90°): Imagine pointing a physical camera at the northern horizon and yawing around westward, past the western horizon, and settling on the southern horizon. The whole time, the roll angle of the camera is 0°, so there's no problem. But now imagine pointing it at the northern horizon, and pitching upward, past the zenith, and continuing to pitch backward until you are facing the southern horizon. Now the camera is upside down; the roll angle is 180°. But if the camera controller doesn't change the roll angle from 0°, then it will do a nonphysical "flip" right when you pass the zenith. The problem is that there really is no way to synthesize a roll angle based purely on position and not have this happen. We've just demonstrated that there are two ways to move your camera from pointing north to pointing south, where the roll angle is completely different at the end.
So you're stuck, I guess. Well, maybe not. Can you rotate the image from the camera based on the roll angle of the device orientation? That is, add the roll back into the displayed image? If so, you may have a solution. Let's say the roll angle of the camera controller is always at zero. Then you just rotate the image by the desired roll angle (something derived from beta I guess?) and you're done. If the camera controller has some other convention for choosing the roll angle, you will need to figure that out, undo it, and add the roll angle back on.
Without the actual system in front of me I probably can't help you debug your way to a solution. So I think this is where my journey with this question must end. Good luck!
Summary:
You don't need a quaternion
Pick a fixed distance to your simulated target
Add the roll angle by rotating the image before displaying it
Good luck!

Coming up with an Algorithm

I have a circle in my canvas. The mouse position is calculated in relation to the canvas. I want the circle to move when the mouse is at <=100px distance from it. The minimum distance to start moving is 100px, at 0.5px/tick. It goes up to 2px/tick at 20px distance.
Basically, the closer the mouse is to the circle, the faster the circle should move.
What I have so far moves the circle when distance is less or equal to 100 -- (I'm using easeljs library)
function handleTick() {
distance = calculateDistance(circle, mX, mY);
if (distance<=100) {
circle.x += 0.3;
stage.update();
}
}
What I want
function handleTick() {
distance = calculateDistance(circle, mX, mY);
if (distance<=100) {
circleSpeed = // equation that takes distance and outputs velocity px/tick.
circle.x += circleSpeed;
stage.update();
}
}
So I thought this was a mathmatical problem and posted it on math exchange, but so far no answers. I tried googling several topics like: "how to come up with an equation for a relation" since I have the domain (100, 20) and the range (0.5, 2). What function can relate them?
Thing is I'm bad at math, and these numbers might not even have a relation - I'm not sure what I'm looking for here.
Should I write a random algorithm "circleSpeed = 2x + 5x;" and hope it does what I want? Or is it possible to do as I did - "I want these to be the minimum and maximum values, now I need to come up with an equation for it"?
A pointer in the right direction would be great because so far I'm shooting in the dark.
If I understand it correctly, you want circleSpeed to be a function of distance, such that
circleSpeed is 0.5 when distance is 100.
circleSpeed is 2 when distance is 20.
There are infinity functions which fulfill that, so I will assume linearity.
The equation of the line with slope m and which contains the point (x₀,y₀) is
y = m (x-x₀) + y₀
But in this case you have two points, (x₁,y₁) and (x₂,y₂), so you can calculate the slope with
y₂ - y₁
m = ───────
x₂ - x₁
So the equation of the line is
y₂ - y₁
y = ─────── (x - x₁) + y₁
x₂ - x₁
With your data,
0.5 - 2
y = ──────── (x - 20) + 2 = -0.01875 x + 2.375
100 - 20
Therefore,
circleSpeed = -0.01875 * distance + 2.375
I assume you want a linear relation between the distance and speed?
If so, you could do something like circleSpeed = (2.5 - 0.5(distance/20)).
That would, however set the speed linearly from 0 to 2.5 on the range (100 to 0), but by using another if like this if (distance < 20) circleSpeed = 2 you would limit the speed to 2.0 at 20 range.
It's not 100% accurate to what you asked for, but pretty close and it should look ok I guess. It could possibly also be tweaked to get closer.
However if you want to make the circle move away from the mouse, you also need to do something to calculate the correct direction of movement as well, and your problem gets a tiny bit more complex as you need to calculate speed_x and speed_y
Here is a simple snippet to animate the speed linearly, what that means is that is the acceleration of the circle will be constant.
if distance > 100:
print 0
elseif distance < 20:
print 2
else:
print 2 - (distance -20 ) * 0.01875
Yet other relationships are possible, (other easings you might call them) but they will be more complicated, hehe.
EDIT: Whoops, I’d made a mistake.

Can't find the angle object is moving in radians

I have been making a mod for a game called Minecraft PE and I'm using it to learn. Before I show my code I want you to know that Y is the vertical axis and X and Z is horizontal. Here is some code I used:
Math.asin(Math.sin((fPosXBeforeMoved - sPosX) /
Math.sqrt(Math.pow(fPosXBeforeMoved - sPosX, 2) +
Math.pow(fPosZBeforeMoved - sPosZ, 2))));
I didn't use tan because sometimes it returns something like NaN at a certain angle. This code gives us the sine of the angle when I clearly used Math.asin. angle is a value between -1 and 1, and it works! I know it works, because when I go past the Z axis I was expecting and it did switch from negative to positive. However, I thought it's supposed to return radians? I read somewhere that the input is radians, but my input is not radians. I really want the answer to how my own code works and how I should have done it! I spent all day learning about trigonometry, but I'm really frustrated so now I ask the question from where I get all my answers from!
Can someone please explain how my own code works and how I should modify it to get the angle in radians? Is what I've done right? Am I actually giving it radians and just turned it into some sort of sine degrees type thing?
OK, let's give a quick refresher as to what sin and asin are. Take a look at this right-angle triangle in the diagram below:
Source: Wikipedia
By taking a look at point A of this right-angle triangle, we see that there is an angle formed between the line segment AC and AB. The relationship between this angle and sin is that sin is the ratio of the length of the opposite side over the hypotenuse. In other words:
sin A = opposite / hypotenuse = a / h
This means that if we took a / h, this is equal to the sin of the angle located at A. As such, to find the actual angle, we would need to apply the inverse sine operator on both sides of this equation. As such:
A = asin(a / h)
For example, if a = 1 and h = 2 in our triangle, the sine of the angle that this right triangle makes between AC and AB is:
sin A = 1 / 2
To find the actual angle that is here, we do:
A = asin(1 / 2)
Putting this in your calculator, we get 30 degrees. Radians are another way of representing angle, where the following relationship holds:
angle_in_radians = (angle_in_degrees) * (Math.PI / 180.0)
I'm actually a bit confused with your code, because you are doing asin and then sin after. A property between asin and sin is:
arcsin is the same as asin. The above equation states that as long as x >= -Math.PI / 2, x <= Math.PI / 2 or x >= -90, x <= 90 degrees, then this relationship holds. In your code, the argument inside the sin will definitely be between -1 to 1, and so this actually simplifies to:
(fPosXBeforeMoved - sPosX) / Math.sqrt(Math.pow(fPosXBeforeMoved - sPosX, 2) +
Math.pow(fPosZBeforeMoved - sPosZ, 2));
If you want to find the angle between the points that are moved, then you're not using the right sides of the triangle. I'll cover this more later.
Alright, so how does this relate to your question? Take a look at the equation that you have in your code. We have four points we need to take a look at:
fPosXBeforeMoved - The X position of your point before we moved
sPosX - The X position of your point after we moved
fPosZBeforeMoved - The Z position of your point before we moved
sPosZ - The Z position of your point after we moved.
We can actually represent this in a right-angle triangle like so (excuse the bad diagram):
We can represent the point before you moved as (fPosXBeforeMoved,fPosZBeforeMoved) on the XZ plane, and the point (sPosX,sPosZ) is when you moved after. In this diagram X would be the horizontal component, while Z would be the vertical component. Imagine that you are holding a picture up in front of you. X would be the axis going from left to right, Z would be the axis going up and down and Y would be the axis coming out towards you and going inside the picture.
We can find the length of the adjacent (AC) segment by taking the difference between the X co-ordinates and the length of the opposite (AB) segment by taking the difference between the Z co-ordinates. All we need left is to find the length of the hypotenuse (h). If you recall from school, this is simply done by using the Pythagorean theorem:
h^2 = a^2 + b^2
h = sqrt(a^2 + b^2)
Therefore, if you refer to the diagram, our hypotenuse is thus (in JavaScript):
Math.sqrt(Math.pow(fPosXBeforeMoved - sPosX, 2) + Math.pow(fPosZBeforeMoved - sPosZ, 2));
You'll recognize this as part of your code. We covered sin, but let's take a look at cos. cos is the ratio of the length of the adjacent side over the hypotenuse. In other words:
cos A = adjacent / hypotenuse = b / h
This explains this part:
(sPosX - fPosXBeforeMoved) / Math.sqrt(Math.pow(sPosX - fPosXBeforeMoved, 2) +
Math.pow(sPosZ - fPosZBeforeMoved, 2));
Take note that I swapped the subtraction of sPosX and fPosXBeforeMoved in comparison to what you had in your code from before. The reason why is because when you are examining the point before and the point after, the point after always comes first, then the point before comes second. In the bottom when you're calculating the hypotenuse, this doesn't matter because no matter which order the values are subtracted from, we take the square of the subtraction, so you will get the same number anyway regardless of the order. I decided to swap the orders here in the hypotenuse in order to be consistent. The order does matter at the top, as the value being positive or negative when you're subtracting will make a difference when you're finding the angle in the end.
Note that this division will always be between -1 to 1 so we can certainly use the inverse trigonometric functions here. Finally, if you want to find the angle, you would apply the inverse cosine. In other words:
Math.acos((sPosX - fPosXBeforeMoved) / Math.sqrt(Math.pow(sPosX - fPosXBeforeMoved, 2)
+ Math.pow(sPosZ - fPosZBeforeMoved, 2)));
This is what I believe you should be programming. Take note that this will return the angle in radians. If you'd like this in degrees, then use the equation that I showed you above, but re-arrange it so that you are solving for degrees instead of radians. As such:
angle_in_degrees = angle_in_radians * (180.0 / Math.PI)
As for what you have now, I suspect that you are simply measuring the ratio of the adjacent and the hypotenuse, which is totally fine if you want to detect where you are crossing over each axis. If you want to find the actual angle, I would use the above code instead.
Good luck and have fun!

What's the significance of 1/cos(x) in this code for a 3d canvas game?

I've been experimenting with HTML5 canvases lately and came across this 3d example with relatively little code behind it. I was hoping to find a good introduction to 3d rendering, but I'm having more trouble understanding the geometry behind the code than I was expecting to. I set up a JSbin and copied over the code that was used on his website to play with. I'm stuck at understanding the meaning of
deltaX=1/Math.cos(theta);
which is later used in:
if (deltaX>0) {
stepX = 1;
distX = (mapX + 1 - x) * deltaX;
}
else {
stepX = -1;
distX = (x - mapX) * (deltaX*=-1);
}
Source
My best guess is that it's used for the relation cos(x) = adjacent/hypotenuse in a right triangle, but I don't understand where the triangle would fit in, if at all.
If you draw a line from the origin (0, 0) with direction theta (measured from the x-axis), then
deltaX = 1/cos(theta) is the distance on this line until the vertical line x = 1 is met, and
deltaY = 1/sin(theta) is the distance on this line until the horizontal line y = 1 is met.
It is indeed a triangle relation. In the first case, the triangle has the points (0, 0), (1, 0) and the point (1, y) where the line meets the vertical line x=1.
(mapX, mapY) is a grid point with integer coordinates, and (x, y) is a point in the square [mapX, mapX+1) x [mapY, mapY+1).
distX computes the distance of the next vertical grid line in theta-direction, and distY the distance of the next horizontal grid line.
Remark: The computation fails if the direction is a multiple of π/2, i.e. the direction is exactly right, up, left, or down, because sin(theta) = 0 or cos(theta) = 0 in that case. This probably does not happen in your program, because the playerDirection starts with 0.4 and is incremented or decremented by 0.07.

Categories