Cesiumjs calculating pitch, yaw, heading from vector - javascript

I have one of Cesium's models loaded into the scene, and I have two points which I want to use in order to calculate the orientation of the model and this is the function I created.
// calculate the direction which the model is facing
calculateOrientation({ position, nextPosition }) {
let dir = new Cesium.Cartesian3();
let normalizedDir = new Cesium.Cartesian3();
Cesium.Cartesian3.subtract(nextPosition, position, dir);
Cesium.Cartesian3.normalize(dir, normalizedDir);
var heading = Math.acos(normalizedDir.x);
var pitch = Math.acos(normalizedDir.y);
var roll = 0;
var hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
return orientation;
}
But the rotations I get don't make any sense. Is my math wrong?
UPDATE
After the first answer by #Keshet, I looked up how to find an angle between a plane and a vector. I figured if I find the angle between the normal of each plane and -90, I should get the correct angle, but I am not sure if this is correct.
Also I don't know how Cesium Axis work, and I cant find any document describing it. For example the XY plane and etc.
let dir = new Cesium.Cartesian3();
let xyNormal = new Cesium.Cartesian3(0,0,1);
let xzNormal = new Cesium.Cartesian3(0,1,0);
let yzNormal = new Cesium.Cartesian3(1,0,0);
Cesium.Cartesian3.subtract(nextPosition, position, dir);
let xyAngle = Cesium.Math.PI_OVER_TWO - Cesium.Cartesian3.angleBetween(dir, xyNormal);
let xzAngle = Cesium.Math.PI_OVER_TWO - Cesium.Cartesian3.angleBetween(dir, xzNormal);
let yzAngle = Cesium.Math.PI_OVER_TWO - Cesium.Cartesian3.angleBetween(dir, yzNormal);
UPDATE 2
Following #IIan suggestion using atan2, here is the code:
Cesium.Cartesian3.subtract(position, nextPosition, dir);
// create the mapped to plane vectors, and get the
// normalized versions
let xyMappedVectorNormalized = new Cesium.Cartesian3(0, 0, 0);
let xyMappedVector = new Cesium.Cartesian3(dir.x, dir.y, 0);
let xzMappedVectorNormalized = new Cesium.Cartesian3(0, 0, 0);
let xzMappedVector = new Cesium.Cartesian3(dir.x, 0, dir.z);
let yzMappedVectorNormalized = new Cesium.Cartesian3(0, 0, 0);
let yzMappedVector = new Cesium.Cartesian3(0, dir.y, dir.z);
Cesium.Cartesian3.normalize(xyMappedVector, xyMappedVectorNormalized);
Cesium.Cartesian3.normalize(xzMappedVector, xzMappedVectorNormalized);
Cesium.Cartesian3.normalize(yzMappedVector, yzMappedVectorNormalized);
// calculate the angles
let xyAngle = Math.atan2(xyMappedVectorNormalized.y, xyMappedVectorNormalized.x);
let xzAngle = Math.atan2(xzMappedVectorNormalized.z, xzMappedVectorNormalized.x);
let yzAngle = Math.atan2(yzMappedVectorNormalized.z, yzMappedVectorNormalized.y);

First, we need to explain what the heading, pitch, and roll angles represent.
The heading angle represents the angle in radians with respect to the XY - plane
The pitch angle represents the angle in radians with respect to the XZ - plane
The roll angle respresents the angle in radians with respect to the YZ plane
You cannot simply use the x / y to compute the heading / pitch
var heading = Math.acos(normalizedDir.x);
var pitch = Math.acos(normalizedDir.y);
You need to get the total angle on each of these planes.
On the XY plane, you will use the normalized |(X, Y)|
on the XZ plane, you will use the normalized |(X, Z)|
on the YZ plane you will use the normalized |(Y, Z)|
Update
|(X, Y)| represents a point on the Unit circle.
where (sin(theta), cos(theta)) = |(X, Y)|
IE when theta = 0, |(X, Y)| = (1, 0)
when theta = PI/2, |(X, Y)| = (0, 1)
this theta would be then the angle you use for the heading
then subsequently, you can call the arctan2 function to calculate the angle with respect to the plane.
atan2(y, x) = theta where [x, y] are calculated from above using the corresponding normalized 2D vector
To note: atan2 gives angles bounded from (-PI, PI]
Example
If your 3D vector is (1, 2, 3) -- on the XY plane X = 1, Y = 2.
Then if you normalize (1, 2) => (1 / sqrt(5), 2 / sqrt(5))
Then you can use atan2(2 / sqrt(5), 1 / sqrt(5) to calculate the angle in radians for the heading

Related

Calculate new circle offset based on angle of rotation?

I have a multiplayer Javascript game where the player is a circle, and is able shoot/"eject" circle bullets in the direction that the player is rotated. My code is working perfectly, except it shoots from the middle of the player. I would like it so that the circles are shot from the top right position of the player, where the gun is located. The issue is that when the players rotation changes, you cannot simply add (1, -1) to the position of the player.
Here is my code:
GameServer.prototype.ejectMass = function(client) {
for (var i = 0; i < client.cells.length; i++) {
var cell = client.cells[i]; // the player
var angle = this.toRad(client.rotation); // rotation of the player
var d = new Vec2(Math.sin(angle), Math.cos(-angle)).scale(-180);
var sq = (~~d.sqDist(d));
d.x = sq > 1 ? d.x / sq : 1;
d.y = sq > 1 ? d.y / sq : 0;
// cell.position is the players position
var pos = new Vec2(
cell.position.x + d.x * cell._size,
cell.position.y + d.y * cell._size
);
var ejected = 0;
// Create cell and add it to node list
ejected = new Entity.EjectedMass(this, null, pos, this.config.ejectSize * cell.ejectSize); // the bullet being shot
ejected.setBoostDefault(-this.config.ejectVelocity * cell.ejectSpeed, angle);
ejected.ejectedOwner = cell; // set the person who shot the bullet
this.addNode(ejected); // add the bullet into the game
if (typeof ejected !== 'undefined') {
setTimeout(this.removeNode.bind(this, ejected), 1000); // remove the ejected bullet after 1 second
}
}
};
And here is an illustration of the current way it is working:
Assuming that the player (circle) is at its own local origin then the position of the gun is relative to the player's origin. Assuming the coordinate system is that of the canvas with forward along the x axis from left to right, and clockwise 90deg (left of player) is the Y axis going down.
Image: C is local circle origin (0,0) with Forward along the red arrow from C, Gx and Gy are the local coordinates of the gun from the circle center C. Top left shows the canvas coordinate (world) system origin. In code below, The player position is relative to that world origin. The final gunPos is also give relative to the world coordinates. B vec is the bullets bullet.delta vector
const bulletSpeed = 10;
var gunPos = {x : 10, Y : 10} // ten units forward ten units left of circle center
var player = {rotation : ?, x : ?, y : ?} // unknown player position and rotation
// get the unit vector of the rotated x axis. Along player forward
var xAx = Math.cos(player.rotation);
var xAy = Math.sin(player.rotation);
// transform the gunpos to absolute position (world coordinates) of rotated player
var rotatedGunPos = {};
rotatedGunPos.x = gunPos.x * xAx - gunPos.y * xAy + player.x;
rotatedGunPos.y = gunPos.x * xAy + gunPos.y * xAx + player.y;
// and fire the bullet from
var bullet = {}
bullet.x = rotatedGunPos.x;
bullet.y = rotatedGunPos.y;
// bullet vector is
bullet.deltaX = xAx * BULLET_SPEED;
bullet.deltaY = xAy * BULLET_SPEED;
You didn't provide enough details about your layout such as what are orientations of X- and Y-axis? Where is 0 angle? Is angle clockwise or counterclockwise? Still the basic idea is the same. Let's assume that X-axis is to the right and Y-axis is down as it looks like from your attached image and adding (1, -1) to get top-right corner. Also assume that angle = 0 for X-axis and angle is clockwise i.e. angle = Pi/2 is aligned with positive direction of Y-axis = down. When the gun is pointed Up i.e. angle = -Pi/2 your starting point is (1, -1) which is at distance sqrt(2) and additionally rotated to Pi/4 corresponding to gun orientation. This is all you need to know.
var angle = this.toRad(client.rotation); // rotation of the player
var gunStartAngle = angle + Math.PI/4;
var sqrt2 = Math.sqrt(2);
// cell.position is the players position
var pos = new Vec2(
cell.position.x + cell._size * sqrt2 * Math.cos(gunStartAngle),
cell.position.y + cell._size * sqrt2 * Math.sin(gunStartAngle)
);
Obviously if your layout is different, you should fix the details of the math but the idea remains the same.

Javascript, Math: calculate the area of a flat, 2D surface that is situated in 3D

I want to be able to calculate the surface area of a 2D polygon of any shape, given a set of 3D vertices. For example, what is the surface area of this figure?
var polygon = new Polygon([new Point(0,0,0), new Point(5,8,2), new Point(11,15,7)])
polygon.areaIfPolygonIs3D()
--> some predictable result, no matter how many vertices the polygon has...
Keep in mind that polygons only have one surface. They are flat but could be triangle shaped or trapezoid shaped or randomly shaped, and could be floating at a 3D angle... imagine them as pieces of paper turned any which way in 3D space.
What I've tried to do so far is rotate the thing flat, and then use a basic formula for calculating the area of a 2D irregular polygon which is currently working in my code (formula: http://www.wikihow.com/Calculate-the-Area-of-a-Polygon). I had such a hard figuring out how to rotate all the vertices so the polygon lays flat (all "z" values are 0) that I abandoned that path, though I'm open to trying it if someone can get there. (Perhaps there is a bug in Point.rotateBy().)
I can work with Points, and Edges (created with point.to(point)), and Edges have 'theta' (edge.theta()) and 'phi' (edge.phi()).
In any case, if someone can fill in what goes here and help me after a full days effort of trying to relearn all the geometry I forgot from high school, that would be much appreciated!
var locatorRho = function(x,y,z) {
return Math.sqrt(x*x + y*y + z*z);
}
var locatorTheta = function(x,y) {
return Math.atan2(y,x);
};
var locatorPhi = function(x,y,z) {
return z == 0 ? Math.PI_2 : Math.acos(z/locatorRho(x, y, z));
}
// rotates a point according to another point ('locator'), and their 2D angle ('theta') and 3D angle ('phi')
Point.prototype.rotateBy = function(locator, theta, phi) {
phi = (phi == undefined ? 0 : phi);
var relativeX = this.x() - locator.x();
var relativeY = this.y() - locator.y();
var relativeZ = this.z() - locator.z();
var distance = locatorRho(relativeX, relativeY, relativeZ);
var newTheta = locatorTheta(relativeX, relativeY) + theta;
var newPhi = locatorPhi(relativeX, relativeY, relativeZ) + phi;
this._x = locatorX(distance, newTheta, newPhi) + locator.x();
this._y = locatorY(distance, newTheta, newPhi) + locator.y();
this._z = locatorZ(distance, newPhi) + locator.z();
}
Polygon.prototype.signedArea = function() {
var vertices = this.vertices();
var area = 0;
for(var i=0, j=1, length=vertices.length; i<length; ++i, j=(i+1)%length) {
area += vertices[i].x()*vertices[j].y() - vertices[j].x()*vertices[i].y();
}
return 0.5*area
}
Polygon.prototype.areaIfPolygonIs2D = function() {
return Math.abs(rotatedFlatCopy.signedArea())
}
Polygon.prototype.areaIfPolygonIs3D = function() {
... help here I am so stuck ...
}
var vertices = [some number of Points, e.g., new Point(x,y,z)]
var polygon = new Polygon(vertices)
var polygon.areaIfPolygonIs3D()
--> result
If your polygon plane is not parallel to Z axis, you can calculate area projection with known approach using X and Y coordinates only, then divide result by cosine of angle between Z axis and normal N to that plane
Area = Sum[x1*y2-x2*y1 +...] ////shoelace formula
True_Area = Area / Cos(Angle between N and Z axis)) =
Area / DotProduct((N.x,N.y,N.z), (0,0,1)) =
Area / N.z
//// if N is normalized (unit)
Use the shoelace formula three times, on the 2D vertices (X, Y), (Y, Z) and (Z, X). The desired area is given by √Axy²+Ayz²+Azx² (provided the polygon is flat).

Rotating One Object Around Another In createJS/easelJS

In easelJS, what is the best way to rotate an object around another? What I'm trying to accomplish is a method to rotate the crosshair around the circle pictured below, just like a planet orbits the sun:
I've been able to rotate objects around their own center point, but am having a difficult time devising a way to rotate one object around the center point of a second object. Any ideas?
Might make sense to wrap content in a Container. Translate the coordinates so the center point is where you want it, and then rotate the container.
To build on what Lanny is suggesting, there may be cases where you don't want to rotate the entire container. An alternative would be to use trigonometric functions and an incrementing angle to calculate the x/y position of the crosshair. You can find the x/y by using an angle (converted to radians) and Math.cos(angleInRadians) for x and Math.sin(angleInRadians) for y, the multiply by the radius of the orbit.
See this working example for reference.
Here's a complete snippet.
var stage = new createjs.Stage("stage");
var angle = 0;
var circle = new createjs.Shape();
circle.graphics.beginFill("#FF0000").drawEllipse(-25, -25, 50, 50).endFill();
circle.x = 100;
circle.y = 100;
var crosshair = new createjs.Shape();
crosshair.graphics.setStrokeStyle(2).beginStroke("#FF0000").moveTo(5, 0).lineTo(5, 10).moveTo(0, 5).lineTo(10, 5).endStroke();
stage.addChild(circle);
stage.addChild(crosshair);
createjs.Ticker.addEventListener("tick", function(){
angle++;
if(angle > 360)
angle = 1;
var rads = angle * Math.PI / 180;
var x = 100 * Math.cos(rads);
var y = 100 * Math.sin(rads);
crosshair.x = x + 100;
crosshair.y = y + 100;
stage.update();
});
Put another point respect to origin point with the same direction
var one_meter = 1 / map_resolution;
// get one meter distance from pointed points
var extra_x = one_meter * Math.cos(temp_rotation);
var extra_y = one_meter * Math.sin(-temp_rotation);
var new_x = mapXY.x + extra_x;
var new_y = mapXY.y + extra_y;
var home_point = new createjs.Shape().set({ x: new_x, y: new_y });
home_point.graphics.beginFill("Blue").drawCircle(0, 0, 10);
stage.addChild(home_point);
stage.update();

THREE.js: Need Help rotating with Quaternions

I'm looking to understand quaternions for three.js, but for all the tutorials, I haven't been able to translate them into the application I need. This is the problem:
Given a sphere centered at (0,0,0), I want to angle an object on the sphere's surface, that acts as the focal point for the camera. This point is to be moved and rotated on the surface with keyboard input.
Setting the focal point into a chosen orbit is easy of course, but maintaining the right rotation perpendicular to the surface escapes me. I know quaternions are neccessary for smooth movement and arbitrary axis rotation, but I don't know where to start.
The second part then is rotating the camera offset with the focal point. The snippet I found for this does not have the desired effect anymore, as the cameraOffset does not inherit the rotation:
var cameraOffset = relativeCameraOffset.clone().applyMatrix4( focalPoint.matrixWorld );
camera.position.copy( focalPoint.position.clone().add(cameraOffset) );
camera.lookAt( focalPoint.position );
Update 1: Tried it with fixed camera on the pole and rotating the planet. But unless I'm missing something important, this fails as well, due to the directions getting skewed completely when going towards the equator. (Left becomes forward). Code in update is:
acceleration.set(0,0,0);
if (keyboard.pressed("w")) acceleration.x = 1 * accelerationSpeed;
if (keyboard.pressed("s")) acceleration.x = -1 * accelerationSpeed;
if (keyboard.pressed("a")) acceleration.z = 1 * accelerationSpeed;
if (keyboard.pressed("d")) acceleration.z = -1 * accelerationSpeed;
if (keyboard.pressed("q")) acceleration.y = 1 * accelerationSpeed;
if (keyboard.pressed("e")) acceleration.y = -1 * accelerationSpeed;
velocity.add(acceleration);
velocity.multiplyScalar(dropOff);
velocity.max(minV);
velocity.min(maxV);
planet.mesh.rotation.x += velocity.x;
planet.mesh.rotation.y += velocity.y;
planet.mesh.rotation.z += velocity.z;
So I'm still open for suggestions.
Finally found the solution from a mixture of matrices and quaternions:
//Setup
var ux = new THREE.Vector3(1,0,0);
var uy = new THREE.Vector3(0,1,0);
var uz = new THREE.Vector3(0,0,1);
var direction = ux.clone();
var m4 = new THREE.Matrix4();
var dq = new THREE.Quaternion(); //direction quad base
var dqq; //final direction quad
var dq2 = new THREE.Quaternion();
dq2.setFromAxisAngle(uz,Math.PI/2); //direction perpendicular rot
//Update
if (velocity.length() < 0.1) return;
if (velocity.x) { focalPoint.translateY( velocity.x ); }
if (velocity.y) { focalPoint.translateX( velocity.y ); }
//create new direction from focalPoint quat, but perpendicular
dqq = dq.clone().multiply(focalPoint.quaternion).multiply(dq2);
velocity.multiplyScalar(dropOff);
//forward direction vector
direction = ux.clone().applyQuaternion(dqq).normalize();
//use Matrix4.lookAt to align focalPoint with the direction
m4.lookAt(focalPoint.position, planet.mesh.position, direction);
focalPoint.quaternion.setFromRotationMatrix(m4);
var cameraOffset = relativeCameraOffset.clone();
cameraOffset.z = cameraDistance;
cameraOffset.applyQuaternion(focalPoint.quaternion);
camera.position = focalPoint.position.clone().add(cameraOffset) ;
//use direction for camera rotation as well
camera.up = direction;
camera.lookAt( focalPoint.position );
This is the hard core of it. It pans (and with some extension rotates) around the planet without the poles being an issue.
I'm not sure to understand your problem.
But for help, I draw a boat on a sphere with the code below.
var geometry = new THREE.ShapeGeometry(shape);
var translation = new THREE.Matrix4().makeTranslation(boat.position.x, boat.position.y, boat.position.z);
var rotationZ = new THREE.Matrix4().makeRotationZ(-THREE.Math.degToRad(boat.cap));
var rotationX = new THREE.Matrix4().makeRotationX(-THREE.Math.degToRad(boat.latitude));
var rotationY = new THREE.Matrix4().makeRotationY(Math.PI / 2 + THREE.Math.degToRad(boat.longitude));
var roationXY = rotationY.multiply(rotationX);
geometry.applyMatrix(rotationZ);
geometry.applyMatrix(roationXY );
geometry.applyMatrix(translation);
First, I apply a rotation on Z to define boat cap
Then, I apply
rotation on Y,X to to set the boat perpendicular to the surface of
the sphere
Finally I apply a translation to put the boat on the
surafce of the sphere
The rotations order is important

rotating a sphere a to b point on itself

Im trying to figure out how to rotate a sphere from point A on itself to point b on itself. I found some Unity3d code:
Quaternion rot = Quaternion.FromToRotation (pointA, pointB);
sphere.transform.rotation *= rot; //Multiply rotations to get the resultant rotation
via http://answers.unity3d.com/questions/21921/rotate-point-a-on-sphere-to-point-b-on-sphere.html but I can't figure out how to implement it in Three.js.
Here's my code:
var s = sphere mesh
var va = 1st start vector
var vb = 2nd end vector;
var qa = new THREE.Quaternion(va.x, va.y, va.z, 1);
var qb = new THREE.Quaternion(vb.x, vb.y, vb.z, 1);
var qc = new THREE.Quaternion();
THREE.Quaternion.slerp(qa, qb, qc, 1);
s.useQuaternion = true;
s.quaternion = qc;
Thanks!
Assume the sphere is centered at the origin and A and B are normalized (i.e. unit length). Then compute the cross product C = A×B. This is your vector representing your rotation axis. The angle for your rotation is given by θ = cos-1(A∙B) where A∙B is the dot product of the unit vectors A and B. Note that the angle in this case is typically in radians, not degrees.
If the sphere is centered at some point P not at the origin or its radius is not unit length, you will have to translate and scale A and B before derive the rotation. This looks like:
A' ← (A-P)/|A-P| // Normalized version of A
B' ← (B-P)/|B-P| // Normalized version of B
V ← A'×B' // Axis about which to rotate the system
θ ← cos-1(A'∙B') // Angle by which to rotate
You can then use V and θ as your arguments for constructing the quaternion you will use for defining your rotation. This rotation will be centered at the origin, so before applying it, translate by -P, then apply the rotation, and translate back by P.
One note: There may be a sign error in here. If it doesn't work, it's because the sign of the cross product doesn't match with the sign of the dot product. Just reverse the order of the arguments in the cross product to fix it if this is a problem.
var c = group.rotation.y;
var d = -b * (Math.PI / 180)%(2 * Math.PI);
var e = Math.PI / 2 * -1;
group.rotation.y = c % (2 * Math.PI);
group.rotation.x = a * (Math.PI / 180) % Math.PI;
group.rotation.y= d+e;
where a= latitude, b= longitude,group=Object3D(or sphere)

Categories