Efficiently calculate angle (and direction) between 3 points - javascript

Using this thread (Calculate the shortest way to rotate, right or left?) I created a function that returns a positive or negative rotation angle. Problem is, it seems really inefficient. Can anyone help? (for clarity purposes, i've spelled out the var names and created helper functions.)
function getRotation(center, target, heading) {
var north = new point(center.x(), center.y() + 10);
/* I tried to calculate these seperately so I dont have to calc the distance so many tiems but couldnt come up with anything...
var distCenterToHeading = getDist(center, heading);
var distCenterToTarget = getDist(center, target);
var distTargetToHeading = getDist(target, heading);
var distCenterToNorth = 10;
var distNorthToHeading = getDist(north, heading);
var distNorthToTarget = getDist(north, target);
*/
var angHeadingToTarget = getAngle(center, heading, target);
var angNorthToHeading = getAngle(center, north, heading);
var angNorthToTarget = getAngle(center, north, target);
if (((angNorthToHeading - angNorthToTarget) + 360) % 360 > 180) {
return angHeadingToTarget;
} else {
return -angHeadingToTarget;
}
}
function getAngle(Center, heading, target) {
var p12 = getDist(Center, heading);
var p13 = getDist(Center, target);
var p23 = getDist(heading, target);
return Math.acos((sq(p12) + sq(p13) - sq(p23)) / (2 * p12 * p13)) * 180 / Math.PI;
}
function point(iX, iY) {
var _X=iX;
var _Y=iY;
this.x = function () { return _X; };
this.y = function () { return _Y; };
this.setX = function (iX) {
_X = iX;
}
this.setY = function (iY) {
_Y = iY;
}
}
function getDist(p1, p2) { return Math.sqrt(Math.pow(p1.x() - p2.x(), 2) + Math.pow(p1.y() - p2.y(), 2)); }
function sq(num) { return Math.pow(num, 2); }
I will potentially need to get the rotation many times, and i've heard that the sqrt function is expensive... In the above example, it's being hit 9 times, where I can see that at least three of them are redundant (center to heading, center to target and center to north).
Is there a better way of doing this all-together?
Thanks in advance,
Dave
EDIT: #Mike Dunlavey
I've been messing around with atan2 but I cant seem to get anything that works. Maybe i'm just missing something simple, but could you explain? Thanks.

1) In your getDist function, it might be faster to just square the deltas, rather than calling Math.pow.
It's possible Math.pow treats integer powers as a special case, but in general it has to get wrapped up in log and exp, which you don't need.
2) To get the direction from one point to another, you can just use the Math.atan2 function.
No need to deal with distances.

Related

Natural Movement with Noise

Im creating an object that randomly moves in a natural way using noise like this (works as intended):
The objects encounter a collision and their trajectory is manipulated, the movement path now changes to straight line (words as intended)
thisRabbit.x = _world.width * (noise(thisRabbit.t));
thisRabbit.y = _world.height * (noise(thisRabbit.t+5));
thisRabbit.t += 0.001;
The problem is after this movement , i want the object to start moving in a random direction again as it was initially. If i use the same function, the object jumps to the last location before the trajectory was modified.
let vx = this.acquiredFood[0] - this.x;
let vy = this.acquiredFood[1] - this.y;
let f = (this.genes.speed + 10) / Math.sqrt(vx*vx+vy*vy);
vx = vx * f;
vy = vy * f;
let newX = this.x + vx;
let newY = this.y + vy;
So how do i get the object to move as before, given a starting position
edit: snippet here: https://editor.p5js.org/vince.chinner/sketches/HPFKR8eIw
Your problem is that you used a factor from 0 to 1 generated with noise and an incremented seed to generate the position by multiplying directly the world dimentions. When reaching food, you cannot increment the seed as to be in the exact position where the movement to get your food led you (I found no inverse function for noise to get the seed from the return value).
What you need to do instead is use the noise to increment or decrement the coordinates, so that no matter where the seed is, you don't loose your current position.
Here are the different corrections I applied to the code, as there were also syntax errors, I can't really paste the whole stuff here for copyright reasons (you didn't share the whole code here and the sketch belongs to you)
MAIN CORRECTION:
used a var found because returning from the forEach callback doesn't make you leave the findFood function, but the callback one. And the forEach loop doesn't stop. Using this var prevents the further forEach tests to be made and allows you to return from findFood so that no further move is made after seeing food.
noise is now applied to a value of 4 and I subtract 2, so that x and y now change with a range of -2 to 2 each. Of course, with this method, you need to check against world dimentions or else the rabbit could leave the world. The seed increment has been changed too or else it would vary too slowly (adapt values as you wish)
findFood(){
var thisRabbit = this, found = false;
_world.food.forEach(f => {
if(!found){
let d = int(dist(f[0], f[1], thisRabbit.x, thisRabbit.y));
if(d < (thisRabbit.genes.vision / 2)+3){
thisRabbit.state = "foundFood";
this.acquiredFood = f;
found = true;
}
}
});
if(found){ return; }
thisRabbit.x += (noise(thisRabbit.t) * 4) - 2;
if(thisRabbit.x < 0){ thisRabbit.x = 0; }
if(thisRabbit.x > _world.width){ thisRabbit.x = _world.width; }
thisRabbit.y += (noise(thisRabbit.t + 5) * 4) - 2;
if(thisRabbit.y < 0){ thisRabbit.y = 0; }
if(thisRabbit.y > _world.height){ thisRabbit.y = _world.height; }
thisRabbit.t += 0.01;
}
SYNTAX ERRORS:
lines 23 / 24: assignment should be with a value (null or false)
this.genes = null;
this.acquiredFood = null;
lines 129 to 133: end you instructions with a ; instead of a ,
this.width = w;
this.height = h;
this.foodDensity = foodDensity;
this.food = [];
this.rabits = [];
line 156 to 160: there should be no space between rabbit and .t. Additionnally, because the coordinates are not directly linked to t, I would prefer to use random for starting position:
let x = this.width * random();
let y = this.height * random();
let _rabbit = new rabbit(x, y);
_rabbit.genes = genes;
_rabbit.t = t;

mystery "rnorm" function terrain generation javascript - what is this doing?

Question: What in the world is this piece of code doing?
Also: Is the way 'w' is being used some sort of existing algorithm? I'm trying to figure out the intent of the function, or at least describe what sorts of numbers it produces.
Context: I'm looking at Martin O'Leary's "Fantasy Map Generation" code - full source here, which in short summary generates fantasy maps on the canvas. There is some insightful explanations of how the higher level process works in a blog post, but this is too low level to get any coverage there. There is a particular function called 'rnorm' that gets used in a couple of places, and I'm lost at how it works. I've included it below, followed by a couple of instances where it comes up for some context. Any help on what this thing is doing would be great!
var rnorm = (function() {
var z2 = null;
function rnorm() {
if (z2 != null) {
var tmp = z2;
z2 = null;
return tmp;
}
var x1 = 0;
var x2 = 0;
var w = 2.0;
while (w >= 1) {
x1 = runif(-1, 1);
x2 = runif(-1, 1);
w = x1 * x1 + x2 * x2;
}
w = Math.sqrt(-2 * Math.log(w) / w);
z2 = x2 * w;
return x1 * w;
}
return rnorm;
})();
runif(), which is called in the code above, is a short function that generates a random number between two given values
function runif(lo, hi) {
return lo + Math.random() * (hi - lo);
}
This code is used to produce random vectors (actually the only place it's used during the generation process) -
function randomVector(scale) {
return [scale * rnorm(), scale * rnorm()];
}
But I think it's doing more than that because the following, when provided a direction of 'randomVector(4),' produces a gradual slope over the entire mesh heightmap: EDIT: no, it actually is having no effect on the gradual slope. That comes from some sneakyness using the fact that one side of the map is 0,0, and the other side of the map is width,height, which creates numbers that gradually increase.
function slope(mesh, direction) {
return mesh.map(function (x) {
return x[0] * direction[0] + x[1] * direction[1];
});
}
Let me know if there's anything else I should be providing. This is my first question here, so I may be a little soft on conventions.
I think it's horrible code. It appears to create a pair of values, z1 and z2, but instead of putting them in a tuple and returning that it returns z1 and on every second call the corresponding z2 value. I have no idea why they'd do such a thing, my only guess would be to avoid allocation of objects and make usage syntactically more convenient.
It should be simplified to
function rnorm() {
var x1 = 0;
var x2 = 0;
var w = 2.0;
while (w >= 1) {
x1 = runif(-1, 1);
x2 = runif(-1, 1);
w = x1 * x1 + x2 * x2;
}
w = Math.sqrt(-2 * Math.log(w) / w);
return [x1 * w, x2 * w];
}
function randomVector(scale) {
var [z1, z2] = rnorm();
return [scale * z1, scale * z2];
}
A modern compiler should be able to avoid array allocation for the returned literal and subsequent destructuring. If it's not, you can do it manually by inlining rnorm in randomVector, especially if this is the only place where it's called anyway.

How to understand this collision detection code from a book and point me to the right math?

Please bear with me. I appreciate any pointers / patience and help in understanding the code pasted below.
All the lines marked with the '// ??' comment is where I don't have a clue what's happening.
I picked up the Building an HTML5 Game book (http://buildanhtml5game.com/) in the hopes of learning how to write a game, more especially in HTML5 and JavaScript.
To my surprise, the book does not really explain various background information or assumes the readers already know their way around.
I am not a mathematics expert and that's one big reason I am stuck with the code below. The book barely touches the surface and the explanation is in a line or two (leaving me more confused).
Please, I may ask, do provide me with links so I study the mathematics behind the formulas below and write some English explanation as well so I understand.
I don't want to skip the mathematics parts in this book because it defeats the purpose.
Much appreciated.
var BubbleShoot = window.BubbleShoot || {};
BubbleShoot.CollisionDetector = (function($){
var CollisionDetector = {
findIntersection : function(curBubble,board,angle) {
// 'angle' is the angle between the 'curBubble' starting bubble
// at the center of the screen and the mouse click's direction aimed at the bubbles
// above.
// I understand how that angle is computed in previous code snippets.
// 'board' contains an array of bubble objects that are static and laid up on the board
// waiting to be shot at with 'curbubble' and 'angle'.
// Get the bubble rows container
var rows = board.getRows();
var collision = null;
// Get the bubble's that will be shot from the center coordinates
var pos = curBubble.getSprite().position();
// The start coordinates are the bubble's center
var start = {
left : pos.left + BubbleShoot.ui.BUBBLE_DIMS/2,
top : pos.top + BubbleShoot.ui.BUBBLE_DIMS/2
};
// ?? (A) what does this give us (mathematically speaking, what are those variables called)
var dx = Math.sin(angle);
var dy = -Math.cos(angle);
for(var i=0;i<rows.length;i++){
// for each row
var row = rows[i];
for(var j=0;j<row.length;j++){
// Get bubble at i,j
var bubble = row[j];
if(bubble){
// Get bubble's coordinates. we want to see if the starting bubble
// will collidate with this static bubble if the starting bubble was fired
// with an angle 'angle'
var coords = bubble.getCoords();
// ?? (B) that's not the distance between the two points!
// ?? what do you call this mathematically?
var distToBubble = {
x : start.left - coords.left,
y : start.top - coords.top
};
// ?? (C) what is 't'? what does it give? can you please give me reference
// ?? so I study the proper math behind its purpose?
var t = dx * distToBubble.x + dy * distToBubble.y;
// ?? (D) why '-t' * dx and what does this give us?
var ex = -t * dx + start.left;
// ?? (E) same for 'ey'
var ey = -t * dy + start.top;
// distance between the two points (ex,ey) and (coords.left,coords.top)
// ?? (F) I am not sure what are 'ex' and 'ey'
var distEC = Math.sqrt((ex - coords.left) * (ex - coords.left) +
(ey - coords.top) * (ey - coords.top));
if(distEC<BubbleShoot.ui.BUBBLE_DIMS * .75){
// ?? (G) dt is what?
var dt = Math.sqrt(BubbleShoot.ui.BUBBLE_DIMS * BubbleShoot.
ui.BUBBLE_DIMS - distEC * distEC);
// ?? (H) what's this?
var offset1 = {
x : (t - dt) * dx,
y : -(t - dt) * dy
};
// ?? (I) and this?!
var offset2 = {
x : (t + dt) * dx,
y : -(t + dt) * dy
};
// ?? (J) distance between something
var distToCollision1 = Math.sqrt(offset1.x * offset1.x +
offset1.y * offset1.y);
var distToCollision2 = Math.sqrt(offset2.x * offset2.x +
offset2.y * offset2.y);
if(distToCollision1 < distToCollision2){
var distToCollision = distToCollision1;
var dest = {
x : offset1.x + start.left,
y : offset1.y + start.top
};
}else{
var distToCollision = distToCollision2;
var dest = {
x : -offset2.x + start.left,
y : offset2.y + start.top
};
}
if(!collision || collision.distToCollision>distToCollision){
collision = {
bubble : bubble,
distToCollision : distToCollision,
coords : dest
};
};
};
};
};
};
return collision;
}
};
return CollisionDetector;
})(jQuery);
You can play the game here just to see how it looks: http://buildanhtml5game.com/?page_id=20

Calculating the Right Ascension and Declination of the Sun

I've been following this guide, porting it to javascript:
http://www.saao.ac.za/public-info/sun-moon-stars/sun-index/how-to-calculate-altaz/
Everything was going swimmingly up until 9.(right ascension) and 10.(declination). I can't recreate the answers they give for these.
(9) find alpha the right ascension of the sun:
(a) for cape town:
lambda = 326.186
epsilon = 23.4396
alpha = arctan (tan(lambda) x cos(epsilon)) // in same quadrant as lambda
// THEIR RESULT
alpha = 328.428
// MY RESULT
var DEGREES = function (val) {
return val / (Math.PI / 180);
};
var alpha = Math.atan(Math.tan(lambda) * Math.sin(epsilon));
alpha = 0.495;
alpha = DEGREES(0.495) = 28.39;
I also tried:
var alpha = Math.atan2(Math.tan(lambda) * Math.sin(epsilon), lambda);
alpha = DEGREES(result) = 1.321;
Not even close!
And 10(a), the declination
delta = arcsin (sin(lambda) x sin(epsilon))
// THEIR RESULT
(a) delta = -12.789
// MY RESULT
var result = Math.asin(Math.sin(eclipticLong) * Math.sin(obliq));
result = DEGREES(result);
result = -10.966;
As you can see I'm clutching at straws as I don't really have a clue about this. Any help would be much appreciated.
Well, the biggest issue I see is here:
alpha = arctan (tan(lambda) x cos(epsilon)) // in same quadrant as lambda
...
var alpha = Math.atan(Math.tan(lambda) * Math.sin(epsilon));
You went from using cosine to sine in the second expression.
Now, this on the face of it doesn't lead to the same result, so lets dig in a bit deeper. For clarity, I'm going to use these functions and constants:
var lambda = 326.186;
var epsilon = 23.4396;
function rad (v) { return v * Math.PI / 180 }
function deg (v) { return v * 180 / Math.PI }
Javascript math functions take a radial coordinate, so lets give this a try:
var result = deg(Math.atan(Math.tan(rad(lambda)) * Math.cos(rad(epsilon))));
console.log(result); // -.31.5717
Thanks to the magic of how degrees work, this is the same answer as 360 + -31.5717 = 328.428.

Google maps api parallel path lines

I am working on a sort of itinerary mapper for packaged vacations, and I'm really happy with what I've done so far; I have the directions api implemented with a custom renderer, so I can take driving directions, and plot my own polyline complete with directional arrows that aren't google's awful ones spaced along the path. I am not exactly a math expert, and I am trying to figure out how I could make a path parallel to another path. For example, the itinerary goes from city 1 to city 2, and then back to city 1.
I want to offset the trip back to city 1's polyline, so that it mirrors the path, but travels parallel to it. Ideally, I would like to when I create the path, check for intersecting points in other paths, and if any are found, offset the path at those points only. This would be a better implementation, because you could for instance parallel the path only where it happens to intersect another one, like when it meets another path only for a short time.
I found this code for API2 from bill chadwick
The link is here: http://wtp2.appspot.com/ParallelLines.htm
Update: Somehow managed to convert this old v2 script to get it working in v3, but I'm experiencing some troubles...
It is more than doubling the original number of points, and following the path, but really throwing them in randomly. Screenshot here:
The class I converted is here:
function BDCCParallelLines(points, color, weight, opacity, opts, gapPx) {
console.log('Pllel COnstructor Initialized');
this.gapPx = gapPx;
this.points = points;
this.color = color;
this.weight = weight;
this.opacity = opacity;
this.opts = opts;
this.line1 = null;
this.line2 = null;
this.lstnZoom = null;
}
BDCCParallelLines.prototype = new google.maps.OverlayView();
BDCCParallelLines.prototype.onAdd = function() {
console.log('Pllel Initialized');
this.prj = map.getProjection();
var self = this;
this.lstnZoom = google.maps.event.addListener(map, "zoom_changed", function() {
self.recalc();
});
this.recalc();//first draw
}
BDCCParallelLines.prototype.onRemove = function() {
if(this.line2)
this.line2.setMap(null);
if(this.line1)
this.line1.setMap(null);
if(this.lstnZoom != null)
google.maps.event.removeListener(this.lstnZoom);
}
BDCCParallelLines.prototype.copy = function() {
return new BDCCParallelLines(this.points,this.color,this.weight,this.opacity,this.opts,this.gapPx);
}
BDCCParallelLines.prototype.draw = function(force) {
return; //do nothing
}
/**
* #param {google.maps.Map} map
* #param {google.maps.LatLng} latlng
* #param {int} z
* #return {google.maps.Point}
*/
BDCCParallelLines.prototype.latLngToPoint = function(latlng, z){
var normalizedPoint = map.getProjection().fromLatLngToPoint(latlng); // returns x,y normalized to 0~255
var scale = Math.pow(2, z);
var pixelCoordinate = new google.maps.Point(normalizedPoint.x * scale, normalizedPoint.y * scale);
return pixelCoordinate;
};
/**
* #param {google.maps.Map} map
* #param {google.maps.Point} point
* #param {int} z
* #return {google.maps.LatLng}
*/
BDCCParallelLines.prototype.pointToLatlng = function(point, z){
var scale = Math.pow(2, z);
var normalizedPoint = new google.maps.Point(point.x / scale, point.y / scale);
var latlng = map.getProjection().fromPointToLatLng(normalizedPoint);
return latlng;
};
BDCCParallelLines.prototype.recalc = function() {
var distallowance;
console.log('recalc called');
var zoom = map.getZoom();
distallowance = 1.6;
if(zoom > 6){
distallowance = 1.3;
if(zoom > 9){
distallowance = .7;
if( zoom > 13){
distallowance = .2;
if( zoom > 15){
distallowance = .0001;
}
}
}
}
console.log('Zoom Level: ' + zoom);
console.log('Allowance = ' + distallowance);
var pts1 = new Array();//left side of center
//shift the pts array away from the centre-line by half the gap + half the line width
var o = (this.gapPx + this.weight)/2;
var p2l,p2r;
for (var i=1; i<this.points.length; i++){
var p1lm1;
var p1rm1;
var p2lm1;
var p2rm1;
var thetam1;
var p1 = this.latLngToPoint(this.points[i-1], zoom)
var p2 = this.latLngToPoint(this.points[i], zoom)
var theta = Math.atan2(p1.x-p2.x,p1.y-p2.y);
theta = theta + (Math.PI/2);
var dl = Math.sqrt(((p1.x-p2.x)*(p1.x-p2.x))+((p1.y-p2.y)*(p1.y-p2.y)));
if(theta > Math.PI)
theta -= Math.PI*2;
var dx = Math.round(o * Math.sin(theta));
var dy = Math.round(o * Math.cos(theta));
var p1l = new google.maps.Point(p1.x+dx,p1.y+dy);
var p1r = new google.maps.Point(p1.x-dx,p1.y-dy);
p2l = new google.maps.Point(p2.x+dx,p2.y+dy);
p2r = new google.maps.Point(p2.x-dx,p2.y-dy);
if(i==1){ //first point
pts1.push(this.pointToLatlng(p1l,zoom));
}
else{ // mid this.points
if(distbetweentwo(this.points[i-1], this.points[i]) > distallowance){
if(theta == thetam1){
// adjacent segments in a straight line
pts1.push(this.pointToLatlng(p1l,zoom));
}
else{
var pli = this.intersect(p1lm1,p2lm1,p1l,p2l);
var pri = this.intersect(p1rm1,p2rm1,p1r,p2r);
var dlxi = (pli.x-p1.x);
var dlyi = (pli.y-p1.y);
var drxi = (pri.x-p1.x);
var dryi = (pri.y-p1.y);
var di = Math.sqrt((drxi*drxi)+(dryi*dryi));
var s = o / di;
var dTheta = theta - thetam1;
if(dTheta < (Math.PI*2))
dTheta += Math.PI*2;
if(dTheta > (Math.PI*2))
dTheta -= Math.PI*2;
if(dTheta < Math.PI){
//intersect point on outside bend
pts1.push(this.pointToLatlng(p2lm1,zoom));
pts1.push(this.pointToLatlng(new google.maps.Point(p1.x+(s*dlxi),p1.y+(s*dlyi)),zoom));
pts1.push(this.pointToLatlng(p1l,zoom));
}
else if (di < dl){
pts1.push(this.pointToLatlng(pli,zoom));
}
else{
pts1.push(this.pointToLatlng(p2lm1,zoom));
pts1.push(this.pointToLatlng(p1l,zoom));
}
}
}
else{
//console.log(distbetweentwo(this.points[i-1], this.points[i]));
}
}
p1lm1 = p1l;
p1rm1 = p1r;
p2lm1 = p2l;
p2rm1 = p2r;
thetam1 = theta;
//end loop
}
pts1.push(this.pointToLatlng(p2l,zoom));//final point
// console.log(pts1);
if(this.line1)
this.line1.setMap(null);
this.line1 = new google.maps.Polyline({
strokeColor: this.color,
strokeOpacity: this.opacity,
strokeWeight: this.weight,
map: map,
path: pts1 });
this.line1.setMap(map);
}
BDCCParallelLines.prototype.intersect = function(p0,p1,p2,p3)
{
// this function computes the intersection of the sent lines p0-p1 and p2-p3
// and returns the intersection point,
var a1,b1,c1, // constants of linear equations
a2,b2,c2,
det_inv, // the inverse of the determinant of the coefficient matrix
m1,m2; // the slopes of each line
var x0 = p0.x;
var y0 = p0.y;
var x1 = p1.x;
var y1 = p1.y;
var x2 = p2.x;
var y2 = p2.y;
var x3 = p3.x;
var y3 = p3.y;
// compute slopes, note the cludge for infinity, however, this will
// be close enough
if ((x1-x0)!=0)
m1 = (y1-y0)/(x1-x0);
else
m1 = 1e+10; // close enough to infinity
if ((x3-x2)!=0)
m2 = (y3-y2)/(x3-x2);
else
m2 = 1e+10; // close enough to infinity
// compute constants
a1 = m1;
a2 = m2;
b1 = -1;
b2 = -1;
c1 = (y0-m1*x0);
c2 = (y2-m2*x2);
// compute the inverse of the determinate
det_inv = 1/(a1*b2 - a2*b1);
// use Kramers rule to compute xi and yi
var xi=((b1*c2 - b2*c1)*det_inv);
var yi=((a2*c1 - a1*c2)*det_inv);
return new google.maps.Point(Math.round(xi),Math.round(yi));
}
This is working to a point... It is working as well as the original implementation. The entire path is recalculated on a zoom basis, and I kind of hacked the function to skip very short paths(weird angles) at higher zoom levels, it more closely follows the path the more you zoom in.
I would rather just have a fixed distance offset that is not recalculated, as it is pretty intensive... There are many programs which accomplish this feat, rhino3d, autocad, illustrator... I feel like it would be great for driving directions for google maps itself, an offsetting of the path so you can distinguish the return trip and the original trip.
If anybody has done anything similar to this in JS even if its not for google maps specifically, I would love to see it. Links I am investigating:
http://processingjs.nihongoresources.com/bezierinfo/
http://www.groupsrv.com/computers/about21532.html
Offsetting paths in general is a pretty tricky buisness. This paper (scientific paper alert) gives a good description of the steps taken for 'professional' offset algorithms.
http://cgcad.thss.tsinghua.edu.cn/~yongjh/papers/CiI2007V58N03P0240.pdf
You don't seem to want anything as fancy as in the demo. From what I gather you just want the same polyline, only shifted some pixels to the right and maybe some to the top so it doesn't overlap.
The code you posted has a latLngToPoint function and pointToLatLng function. I think the directions you get from Google are LatLng, so you can convert those to Points, increase the x and y property, and convert it back to a LatLng and draw your Polyline.
This should get you a line that exactly follows the original line. But, it wont look as fancy as the one in the demo. Since it wont be adding any points to smooth the line.

Categories