Calculate roll angle for Google Maps Street View - javascript

Preamble: there's an issue logged with the Google Maps API, requesting the ability to correct the roll angle of street view tiles to compensate for hills. I've come up with a client-side workaround involving some css sorcery on the tile container. Here's my rotate function:
rotate: function() {
var tilesLoaded = setInterval(function() {
var tiles = $('map-canvas').getElementsByTagName('img');
for (var i=0; i<tiles.length; i++) {
if (tiles[i].src.indexOf(maps.panorama.getPano()) > -1) {
if (typeof maps.panorama.getPhotographerPov != 'undefined') {
var pov = maps.panorama.getPhotographerPov(),
pitch = pov.pitch,
cameraHeading = pov.heading;
/**************************
// I need help with my logic here.
**************************/
var yaw = pov.heading - 90;
if (yaw < 0) yaw += 360;
var scale = ((Math.abs(maps.heading - yaw) / 90) - 1) * -1;
pitch = pov.pitch * scale;
tiles[i].parentNode.parentNode.style.transform = 'rotate(' + pitch + 'deg)';
clearInterval(tilesLoaded);
return;
}
}
}
}, 20);
}
A full (and more thoroughly commented) proof-of-concept is at this JSFiddle. Oddly, the horizon is just about perfectly level if I do no calculation at all on the example in the JSFiddle, but that result isn't consistent for every Lat/Lng. That's just a coincidence.
So, I need to calculate the roll at the client's heading, given the client heading, photographer's heading, and photographer's pitch. Assume the photographer is either facing uphill or downhill, and pov.pitch is superlative (at the min or max limit). How can I calculate the desired pitch facing the side at a certain degree?
Edit: I found an equation that seems to work pretty well. I updated the code and the fiddle. While it seems to be pretty close to the answer, my algorithm is linear. I believe the correct equation should be logarithmic, resulting in subtler adjustments closer to the camera heading and opposite, while to the camera's left and right adjustments are larger.

I found the answer I was looking for. The calculation involves spherical trigonometry, which I didn't even know existed before researching this issue. If anyone notices any problems, please comment. Or if you have a better solution than the one I found, feel free to add your answer and I'll probably accept it if it's more reliable or significantly more efficient than my own.
Anyway, if the tile canvas is a sphere, 0 pitch (horizon) is a plane, and camera pitch is another plane intersecting at the photographer, the two planes project a spherical lune onto the canvas. This lune can be used to calculate a spherical triangle where:
polar angle = Math.abs(camera pitch)
base = camera heading - client heading
one angle = 90° (for flat horizon)
With two angles and a side available, other properties of a spherical triangle can be calculated using the spherical law of sines. The entire triangle isn't needed -- only the side opposite the polar angle. Because this is math beyond my skills, I had to borrow the logic from this spherical triangle calculator. Special thanks to emfril!
The jsfiddle has been updated. My production roll getter has been updated as follows:
function $(what) { return document.getElementById(what); }
var maps = {
get roll() {
function acos(what) {
return (Math.abs(Math.abs(what) - 1) < 0.0000000001)
? Math.round(Math.acos(what)) : Math.acos(what);
}
function sin(what) { return Math.sin(what); }
function cos(what) { return Math.cos(what); }
function abs(what) { return Math.abs(what); }
function deg2rad(what) { return what * Math.PI / 180; }
function rad2deg(what) { return what * 180 / Math.PI; }
var roll=0;
if (typeof maps.panorama.getPhotographerPov() != 'undefined') {
var pov = maps.panorama.getPhotographerPov(),
clientHeading = maps.panorama.getPov().heading;
while (clientHeading < 0) clientHeading += 360;
while (clientHeading > 360) clientHeading -= 360;
// Spherical trigonometry method
a1 = deg2rad(abs(pov.pitch));
a2 = deg2rad(90);
yaw = deg2rad((pov.heading < 0 ? pov.heading + 360 : pov.heading) - clientHeading);
b1 = acos((cos(a1) * cos(a2)) + (sin(a1) * sin(a2) * cos(yaw)));
if (sin(a1) * sin(a2) * sin(b1) !== 0) {
roll = acos((cos(a1) - (cos(a2) * cos(b1))) / (sin(a2) * sin(b1)));
direction = pov.heading - clientHeading;
if (direction < 0) direction += 360;
if (pov.pitch < 0)
roll = (direction < 180) ? rad2deg(roll) * -1 : rad2deg(roll);
else
roll = (direction > 180) ? rad2deg(roll) * -1 : rad2deg(roll);
} else {
// Fall back to algebraic estimate to avoid divide-by-zero
var yaw = pov.heading - 90;
if (yaw < 0) yaw += 360;
var scale = ((abs(clientHeading - yaw) / 90) - 1) * -1;
roll = pov.pitch * scale;
if (abs(roll) > abs(pov.pitch)) {
var diff = (abs(roll) - abs(pov.pitch)) * 2;
roll = (roll < 0) ? roll + diff : roll - diff;
}
}
}
return roll;
}, // end maps.roll getter
// ... rest of maps object...
} // end maps{}
After rotating the panorama tile container, the container also needs to be expanded to hide the blank corners. I was originally using the 2D law of sines for this, but I found a more efficient shortcut. Thanks Mr. Tan!
function deg2rad(what) { return what * Math.PI / 180; }
function cos(what) { return Math.cos(deg2rad(what)); }
function sin(what) { return Math.sin(deg2rad(what)); }
var W = $('map-canvas').clientWidth,
H = $('map-canvas').clientHeight,
Rot = Math.abs(maps.originPitch);
// pixels per side
maps.growX = Math.round(((W * cos(Rot) + H * cos(90 - Rot)) - W) / 2);
maps.growY = Math.round(((W * sin(Rot) + H * sin(90 - Rot)) - H) / 2);
There will be no more edits to this answer, as I don't wish to have it converted to a community wiki answer yet. As updates occur to me, they will be applied to the fiddle.

Related

Calculating the angle between a velocity (particles motion) and a line

So I am creating a simulation of a bouncing ball, and the user can place lines the ball can collide with on the canvas by dragging from one point to another. There are essentially four lines that can be created:
So the object that stores a line is defined as such:
export interface pathSection {
xfrom: number;
yfrom: number;
xto: number;
yto: number;
length: number;
}
The first and third lines in the image for example dont give the same value from
Math.atan2(yto - yfrom, xto - from);
So given the (relative) complexity of the surfaces, I need to find the angle between a moving object and that surface at the point of collision:
The ball strikes the surface at an angle a, which is what I want!
However I am having trouble finding the angle between the two vectors. This is what I understood would work:
var dx = this.path[index_for_path_section].xfrom - this.path[index_for_path_section].xto;
var dy = this.path[index_for_path_section].yfrom - this.path[index_for_path_section].yto;
var posX = this.particle.pos.x;
var posY = this.particle.pos.y;
var posNextX = posX + this.particle.v.x;
var posNextY = posY + this.particle.v.y;
var angleOfRamp = Math.atan2(dy, dx);
var angleOfvelocity = Math.atan2(posNextY - posY, posNextX - posX);
var angleBetween = angleOfRamp - angleOfvelocity;
This is then used to calculate the speed of the object after the collision:
var spd = Math.sqrt(this.particle.v.x * this.particle.v.x + this.particle.v.y * this.particle.v.y);
var restitution = this.elasticity / 100;
this.particle.v.x = restitution * spd * Math.cos(angleBetween);
this.particle.v.y = restitution * spd * Math.sin(angleBetween);
However the angle calculated is around -4.5 Pi, about -90 degrees for the object directly down and the surface at what looks to be around 45-60 degrees…
The red arrow shows the path of the object moving through the surface - the white dots show where a collision has been detected between the surface and the object.
Any help on how to get the correct and usable angle between the two velocity and the line would be appreciated!
Note I have tried utilizing this solution, but have struggled to adapt it to my own work.
So it took me some time, and I am not 100% sure still of why it works because I think im finding the JavaScript angles system a bit tricky, but:
var dx = this.path[collided].xfrom - this.path[collided].xto;
var dy = this.path[collided].yfrom - this.path[collided].yto;
var spd = Math.sqrt(this.particle.v.x * this.particle.v.x + this.particle.v.y * this.particle.v.y);
var angleOfRamp = Math.atan2(dy, dx);
var angleOfvelocity = Math.atan2(this.particle.v.y, this.particle.v.x);
var angleBetween = angleOfRamp * 2 - angleOfvelocity; // not sure why :)
if (angleBetween < 0) { angleBetween += 2*Math.PI; } // not sure why :)
const restitution = this.elasticity / 100;
this.particle.v.x = restitution * spd * Math.cos(angleBetween);
this.particle.v.y = restitution * spd * Math.sin(angleBetween);
Thanks to all who looked :)

Canvas jitters half my rendering

I was working on a fun project that implicates creating "imperfect" circles by drawing them with lines and animate their points to generate a pleasing effect.
The points should alternate between moving away and closer to the center of the circle, to illustrate:
I think I was able to accomplish that, the problem is when I try to render it in a canvas half the render jitters like crazy, you can see it in this demo.
You can see how it renders for me in this video. If you pay close attention the bottom right half of the render runs smoothly while the top left just..doesn't.
This is how I create the points:
for (var i = 0; i < q; i++) {
var a = toRad(aDiv * i);
var e = rand(this.e, 1);
var x = Math.cos(a) * (this.r * e) + this.x;
var y = Math.sin(a) * (this.r * e) + this.y;
this.points.push({
x: x,
y: y,
initX: x,
initY: y,
reverseX: false,
reverseY: false,
finalX: x + 5 * Math.cos(a),
finalY: y + 5 * Math.sin(a)
});
}
Each point in the imperfect circle is calculated using an angle and a random distance that it's not particularly relevant (it relies on a few parameters).
I think it's starts to mess up when I assign the final values (finalX,finalY), the animation is supposed to alternate between those and their initial values, but only half of the render accomplishes it.
Is the math wrong? Is the code wrong? Or is it just that my computer can't handle the rendering?
I can't figure it out, thanks in advance!
Is the math wrong? Is the code wrong? Or is it just that my computer can't handle the rendering?
I Think that your animation function has not care about the elapsed time. Simply the animation occurs very fast. The number of requestAnimationFrame callbacks is usually 60 times per second, So Happens just what is expected to happen.
I made some fixes in this fiddle. This animate function take care about timestamp. Also I made a gradient in the animation to alternate between their final and initial positions smoothly.
ImperfectCircle.prototype.animate = function (timestamp) {
var factor = 4;
var stepTime = 400;
for (var i = 0, l = this.points.length; i < l; i++) {
var point = this.points[i];
var direction = Math.floor(timestamp/stepTime)%2;
var stepProgress = timestamp % stepTime * 100 / stepTime;
stepProgress = (direction == 0 ? stepProgress: 100 -stepProgress);
point.x = point.initX + (Math.cos(point.angle) * stepProgress/100 * factor);
point.y = point.initY + (Math.sin(point.angle) * stepProgress/100 * factor);
}
}
Step by Step:
based on comments
// 1. Calculates the steps as int: Math.floor(timestamp/stepTime)
// 2. Modulo to know if even step or odd step: %2
var direction = Math.floor(timestamp/stepTime)%2;
// 1. Calculates the step progress: timestamp % stepTime
// 2. Convert it to a percentage: * 100 / stepTime
var stepProgress = timestamp % stepTime * 100 / stepTime;
// if odd invert the percentage.
stepProgress = (direction == 0 ? stepProgress: 100 -stepProgress);
// recompute position based on step percentage
// factor is for fine adjustment.
point.x = point.initX + (Math.cos(point.angle) * stepProgress/100 * factor);
point.y = point.initY + (Math.sin(point.angle) * stepProgress/100 * factor);

How to prevent object from rotating 360deg when reaching full circle?

I have a image which always points in the direction of the mouse.
At some point the angle goes from 180deg to -180deg, how do I get the image to take the short angle instead of doing full circle?
// Find ship angle (Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI;).
var mouseAngle = getAngle(FIREFLY.CENTER.X, FIREFLY.CENTER.Y, currentMousePos[0], currentMousePos[1]);
var turnDegrees = mouseAngle - FIREFLY.ANGLE;
var maxDegrees = 5;
console.log(mouseAngle + " " + FIREFLY.ANGLE);
if (turnDegrees > -5 && turnDegrees < 5) {
// Do nothing.
} else if (turnDegrees < 0) {
FIREFLY.ANGLE -= 5;
} else {
FIREFLY.ANGLE += 5;
}
// Set ship direction.
FIREFLY.style.transform = 'rotate(' + (FIREFLY.ANGLE + 90) + 'deg)';
Fiddle
Angles are the same modulo 360 degrees, so to avoid big rotations like 359° you choose -1° and to avoid -359° you choose 1°
You could do something like
var turnDegrees = (mouseAngle - FIREFLY.ANGLE) ;
if(-540<turnDegrees&&turnDegrees <=-180) turnDegrees+=360;
else if(180<turnDegrees&&turnDegrees<=540) turnDegrees-=360;
but it turns out FIREFLY.ANGLE can take large values, so to avoid plenty of other if clauses, a general formula is better. Here is one using modulo
var turnDegrees = mod(mouseAngle - FIREFLY.ANGLE +180, 360) -180;
function mod(x, value){ // Euclidean modulo
return x>=0 ? x%value : value+ x%value;
}
plot test
fiddle updated: http://jsfiddle.net/crl/2rz296tf/31
You could turn off CSS transforms, and do the interpolation yourself.
Try calculating the difference between the angles when the mouse moves and add that to a total angle, that way the angle wont be bound to (-180, 180)

Apply gravity between two or more objects in HTML5 Canvas

I was creating something like a 2d gravity simulator, just for fun, and noticed that I'm a complete idiot in terms of math. I just can't get the gravity to work.
I've tried following the instructions found here but it looks weird and when the distance reaches zero, it goes completely buggy. If I add 1 to the distance as recommended in the question, all objects go upper left. I've even tried not modifying gravity when distances reach zero, but this doesn't change the behavior.
Here's the algorithm I'm using to apply gravity:
var distX = obj1.x - obj2.x,
distY = obj1.y - obj2.y;
if (obj1 != obj2) {
if (distY != 0) {
obj1.vy += -(1 / (distY));
}
if (distX != 0) {
obj1.vx += -(1 / (distX));
}
}
I've tried using other algorithms too, but most of them don't care for the distance between objects.
Note that I want the gravity to affect distant objects less than closer objects.
Instead of solving any equations we could use an approximation. dv/dt = G*M*m/r^2, but for small t we could use the approximation Δv = (G*M*m/r^2)*Δt.
When the objects collide I have implemented perfectly inelastic collision (see Wikipedia). This prevents the distance between two objects from being to small and therefore the maximum force is limited.
I also moved the part of the code where the object's position is changed to a separate loop, so the forces calculated for obj1 and obj2 are equal in size.
Demo
function tick() {
allObjs.forEach(function (obj1) {
allObjs.forEach(function (obj2) {
var diffX = obj2.x - obj1.x,
var diffY = obj2.y - obj1.y;
var distSquare = diffX*diffX + diffY*diffY
var dist = Math.sqrt(distSquare);
if (obj1 != obj2) {
if (dist > obj1.w/2 + obj2.w/2) {
//If you add mass to the objects change to obj2.mass
//instead of 50
var totalForce = 50/distSquare;
obj1.vx += totalForce * diffX / dist;
obj1.vy += totalForce * diffY / dist;
} else {
//Collision has occurred
//If you add mass to the objects change to
//tempX = (obj1.mass*obj1.vx + obj2.mass*obj2.vx)/(obj1.mass+
//obj2.mass);
//tempY = (obj1.mass*obj1.vy + obj2.mass*obj2.vy)/(obj1.mass+
//obj2.mass);
var tempX = (obj1.vx + obj2.vx)/2;
var tempY = (obj1.vy + obj2.vy)/2;
obj1.vx = tempX; obj2.vx = tempX;
obj1.vy = tempY; obj2.vy = tempY;
}
}
});
});
allObjs.forEach(function (obj1) {
obj1.x += obj1.vx / 25;
obj1.y += obj1.vy / 25;
});
stage.update();
}
Try
var distX = obj1.x - obj2.x,
distY = obj1.y - obj2.y;
var rsq = distX *distX + distY * distY;
var r = Math.sqrt(rsq);
var F = 50 / rsq; // constant chosen to be pleasing
var rhat_x = distX / r;
var rhat_y = distY / r;
var Fx = F * rhat_x;
var Fy = F * rhat_y;
obj1.vx += -Fx;
obj1.vy += -Fy;
obj2.vx += Fx;
obj2.vy += Fy;
This is very basic, its not taking mass into account its using the simplest possible way of solving the equations you should really use something like 5th order Runga-Kutta w/ error correction. But it does use the formula for gravitational
F = - G m1 m2 / r^2
where G is the universal gravitational constant, m1 m2 are the two masses (I've all of these to 1!) r^2 is the square of the distance between the objects. The force is in the direction to the other object, let this be a unit vector rhat so the vector version of the force, using 1 for the constants
F = - ( 1 / r^2 ) rhat
The above gives reasonable results it you start out with
createPlanet(50, 200, 2, 0, 1);
createPlanet(400, 200, 2, 0, -1);
you have to take care that the two planets don't get too close or the acceleration goes off to infinity and the velocities get too big.
While playing around I tried
var distX = obj1.x - obj2.x,
distY = obj1.y - obj2.y;
var rsq = distX *distX + distY * distY; // square of the distance
var r = Math.sqrt(rsq);
var Fx = distX / r;
var Fy = distY / r;
obj1.vx += -Fx;
obj1.vy += -Fy;
obj2.vx += Fx;
obj2.vy += Fy;
which gives pleasing but physically incorrect results.
Newton's equations of motion F = ma need to be solved here. You are not doing anything like that in your code. No wonder it isn't matching your intuition.
It would help to understand the physics.
This is a vector equation. The force is gravity, which follows an inverse distance squared law.
You also know how acceleration, velocity, and displacement are related. You have to know calculus.
For your 2D world, that means six equations for each body in the problem. Two bodies means 12 coupled equations.
Solving these equations means integrating all those coupled ordinary differential equations in time. You'll need to know something about numerical methods (e.g. Runga-Kutta 5th order integration w/ error correction).
You'd have a lot to learn to write such a thing yourself. I'd recommend looking into a JavaScript physics library like Box2D or something else that Google might find.

Javascript - normalized vector only rotating in a range of 180 degrees

I am attempting to create a small asteroids game in Javascript, and I've gotten it to the point where the ship draws on the screen and can fly around. However, whenever I try to rotate it by a certain amount, it can only rotate between +/- PI/2. I need it to cover more than just that range of 180 degrees, or else the ship can never turn around. I'm trying to do this from scratch using a custom 2D Vector class, and I've had a couple people look at it with no luck as to what to do.
Here's my vector code, or at least the constructor and rotation functions.
function Vec2D(x, y) {
var self = this;
var sqrt = Math.sqrt;
this.x = x !== null ? Number(x) : 0;
this.y = y !== null ? Number(y) : 0;
}
Vec2D.prototype.rotate = function (deg) {
var theta = deg * (Math.PI / 180),
xTemp = this.x;
this.x = this.x * Math.cos(theta) - this.y * Math.sin(theta);
this.y = xTemp * Math.sin(theta) + this.y * Math.cos(theta);
return this;
}
And here's the code for where my ship is trying to rotate.
function Ship(x_, y_, size_) {
this.position = new Vec2D(x_, y_);
this.velocity = new Vec2D(0, 0);
this.forward = new Vec2D(0, 0);
//some other things
this.turningRight = false;
this.turningLeft = false;
this.turnAmt = 5;
//some more things
}
Ship.prototype.update = function () {
//other update code
if (this.turningRight) {
this.forward.rotate(this.turnAmt);
console.log("right");
}
if (this.turningLeft) {
this.forward.rotate(-1.0 * this.turnAmt);
console.log("left");
}
//end of rotation code in update
}
I can reproduce more code if necessary, but this is all the relevant code as far as I can tell. I've tried console printing, I've tried messing with the rotation matrix, and I've even tried using only radians as opposed to converting it from degrees every time (which in all honesty I really should be doing anyway).
Any thoughts on my gross novice JavaScript?

Categories

Resources