Calculate the mid point of latitude and longitude co-ordinates - javascript

Does anyone know the best way to go about getting the mid-point of a pair of latitude and longitude points?
I mm using d3.js to draw points on a map and need to draw a curved line between two points, so I need to create a mid point to draw a curve in the lines.
Please see image below for a better understanding of what I am trying to do:

Apologies for the long script - it just seemed fun to draw stuff :-). I've marked off sections that are not required
// your latitude / longitude
var co2 = [70, 48];
var co1 = [-70, -28];
// NOT REQUIRED
var ctx = document.getElementById("myChart").getContext("2d");
function drawPoint(color, point) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(point.x, point.y, 5, 0, 2 * Math.PI, false);
ctx.fill();
}
function drawCircle(point, r) {
ctx.strokeStyle = 'gray';
ctx.setLineDash([5, 5]);
ctx.beginPath();
ctx.arc(point.x, point.y, r, 0, 2 * Math.PI, false);
ctx.stroke();
}
// REQUIRED
// convert to cartesian
var projection = d3.geo.equirectangular()
var cot1 = projection(co1);
var cot2 = projection(co2);
var p0 = { x: cot1[0], y: cot1[1] };
var p1 = { x: cot2[0], y: cot2[1] };
// NOT REQUIRED
drawPoint('green', p0);
drawPoint('green', p1);
// REQUIRED
function dfn(p0, p1) {
return Math.pow(Math.pow(p0.x - p1.x, 2) + Math.pow(p0.y - p1.y, 2), 0.5);
}
// from http://math.stackexchange.com/a/87374
var d = dfn(p0, p1);
var m = {
x: (p0.x + p1.x) / 2,
y: (p0.y + p1.y) / 2,
}
var u = (p1.x - p0.x) / d
var v = (p1.y - p0.y) / d;
// increase 1, if you want a larger curvature
var r = d * 1;
var h = Math.pow(Math.pow(r, 2) - Math.pow(d, 2) / 4, 0.5);
// 2 possible centers
var c1 = {
x: m.x - h * v,
y: m.y + h * u
}
var c2 = {
x: m.x + h * v,
y: m.y - h * u
}
// NOT REQUIRED
drawPoint('gray', c1)
drawPoint('gray', c2)
drawCircle(c1, r)
drawCircle(c2, r)
// REQUIRED
// from http://math.stackexchange.com/a/919423
function mfn(p0, p1, c) {
// the -c1 is for moving the center to 0 and back again
var mt1 = {
x: r * (p0.x + p1.x - c.x * 2) / Math.pow(Math.pow(p0.x + p1.x - c.x * 2, 2) + Math.pow(p0.y + p1.y - c.y * 2, 2), 0.5)
};
mt1.y = (p0.y + p1.y - c.y * 2) / (p0.x + p1.x - c.x * 2) * mt1.x;
var ma = {
x: mt1.x + c.x,
y: mt1.y + c.y,
}
var mb = {
x: -mt1.x + c.x,
y: -mt1.y + c.y,
}
return (dfn(ma, p0) < dfn(mb, p0)) ? ma : mb;
}
var m1 = mfn(p0, p1, c1);
var m2 = mfn(p0, p1, c2);
var mo1 = projection.invert([m1.x, m1.y]);
var mo2 = projection.invert([m2.x, m2.y]);
// NOT REQUIRED
drawPoint('blue', m1);
drawPoint('blue', m2);
// your final output (in lat long)
console.log(mo1);
console.log(mo2);
Fiddle - https://jsfiddle.net/srjuc2gd/
And here's just the relevant portion (most of it is just copy-pasta from the beginning of this answer)
var Q31428016 = (function () {
// adjust curvature
var CURVATURE = 1;
// project to convert from lat / long to cartesian
var projection = d3.geo.equirectangular();
// distance between p0 and p1
function dfn(p0, p1) {
return Math.pow(Math.pow(p0.x - p1.x, 2) + Math.pow(p0.y - p1.y, 2), 0.5);
}
// mid point between p0 and p1
function cfn(p0, p1) {
return {
x: (p0.x + p1.x) / 2,
y: (p0.y + p1.y) / 2,
}
}
// get arc midpoint given end points, center and radius - http://math.stackexchange.com/a/919423
function mfn(p0, p1, c, r) {
var m = cfn(p0, p1);
// the -c1 is for moving the center to 0 and back again
var mt1 = {
x: r * (m.x - c.x) / Math.pow(Math.pow(m.x - c.x, 2) + Math.pow(m.y - c.y, 2), 0.5)
};
mt1.y = (m.y - c.y) / (m.x - c.x) * mt1.x;
var ma = {
x: mt1.x + c.x,
y: mt1.y + c.y,
}
var mb = {
x: -mt1.x + c.x,
y: -mt1.y + c.y,
}
return (dfn(ma, p0) < dfn(mb, p0)) ? ma : mb;
}
var Q31428016 = {};
Q31428016.convert = function (co1, co2) {
// convert to cartesian
var cot1 = projection(co1);
var cot2 = projection(co2);
var p0 = { x: cot1[0], y: cot1[1] };
var p1 = { x: cot2[0], y: cot2[1] };
// get center - http://math.stackexchange.com/a/87374
var d = dfn(p0, p1);
var m = cfn(p0, p1);
var u = (p1.x - p0.x) / d
var v = (p1.y - p0.y) / d;
var r = d * CURVATURE;
var h = Math.pow(Math.pow(r, 2) - Math.pow(d, 2) / 4, 0.5);
// 2 possible centers
var c1 = {
x: m.x - h * v,
y: m.y + h * u
}
var c2 = {
x: m.x + h * v,
y: m.y - h * u
}
// get arc midpoints
var m1 = mfn(p0, p1, c1, r);
var m2 = mfn(p0, p1, c2, r);
// convert back to lat / long
var mo1 = projection.invert([m1.x, m1.y]);
var mo2 = projection.invert([m2.x, m2.y]);
return [mo1, mo2]
}
return Q31428016;
})();
// your latitude / longitude
var co1 = [-70, -28];
var co2 = [70, 48];
var mo = Q31428016.convert(co1, co2)
// your output
console.log(mo[0]);
console.log(mo[1]);

For the accurate side:
You may use the Esri web API. Nothing beats decades of experience implementing the hard side of projection systems and datums... Athough the ArcGIS for Server line is a commercial product, the JS API is free, and here there's a pure JS function that does just what you want : geometryEngine.densify ; that function requires an interval parameter, that you can, in your case, get by dividing by two the results of geometryEngine.geodesicLength
That'll need you to get acquainted with the Polyline class in a very basic way, such as var mySegment = new Polyline([[50,3], [55,8]]); and probably nothing further.
For the visual side :
Your segment has two middles ? You may also be interested in geometryEngine.offset ; first offset the original segment once in each direction, then get the center point for each of the resulting segments.
For the practical side :
Given the short distances involved, provided you're not dealing with a weird place that'd be too close to the poles, I'd simply go with an arithmetic average of X and Y, and then add/subtract a rotated vector to offset your two "middles". Doing it this way would be both lighter on the machine (no libs to load from a CDN), easier on you, and as long as the purpose of it is a nice display, the result will be more than good enough.
(added as per comment : a sample)
// Your known starting points, and a k factor of your choice.
var a = {x:3, y:50};
var b = {x:8, y:55};
var k = 0.2;
// Intermediate values
var vab = {x:b.x-a.x, y:b.y-a.y};
var v_rotated = {x:-k*vab.y, y:k*vab.x};
var middle = {x:a.x+vab.x/2, y:a.y+vab.y/2};
// Your two resulting points
var result_i = {x: middle.x + v_rotated.x, y: middle.y + v_rotated.y};
var result_j = {x: middle.x - v_rotated.x, y: middle.y - v_rotated.y};

Check this question, you can use it to find the middle of your coordination on google map. I customized it to use with d3js.
Hope this help you.
In D3
function midpoint (lat1, lng1, lat2, lng2) {
lat1= deg2rad(lat1);
lng1= deg2rad(lng1);
lat2= deg2rad(lat2);
lng2= deg2rad(lng2);
dlng = lng2 - lng1;
Bx = Math.cos(lat2) * Math.cos(dlng);
By = Math.cos(lat2) * Math.sin(dlng);
lat3 = Math.atan2( Math.sin(lat1)+Math.sin(lat2),
Math.sqrt((Math.cos(lat1)+Bx)*(Math.cos(lat1)+Bx) + By*By ));
lng3 = lng1 + Math.atan2(By, (Math.cos(lat1) + Bx));
return (lat3*180)/Math.PI .' '. (lng3*180)/Math.PI;
}
function deg2rad (degrees) {
return degrees * Math.PI / 180;
};
Update 1
If you try to draw curve line, you should create a path with coordination such as :
var lat1=53.507651,lng1=10.046997,lat2=52.234528,lng2=10.695190;
var svg=d3.select("body").append("svg").attr({width:700 , height:600});
svg.append("path").attr("d", function (d) {
var dC="M "+lat1+","+lng1+ " A " + midpoint (lat1, lng1, lat2, lng2)
+ " 0 1,0 " +lat2 +" , "+lng2 +"Z";
return dC;
})
.style("fill","none").style("stroke" ,"steelblue");
You need to create your curve line as you want in d3.
JsFiddle here.

I always use geo-lib
and works

Related

JS move point by an angle on circle

I have program where I click three times, each click creating a point on canvas. I then calculate angle between those three points like this:
function find_angle(A, B, C) {
var AB = Math.sqrt(Math.pow(B.x - A.x, 2) + Math.pow(B.y - A.y, 2));
var BC = Math.sqrt(Math.pow(B.x - C.x, 2) + Math.pow(B.y - C.y, 2));
var AC = Math.sqrt(Math.pow(C.x - A.x, 2) + Math.pow(C.y - A.y, 2));
return Math.acos((BC * BC + AB * AB - AC * AC) / (2 * BC * AB));
}
In example picture above, the calculated angle is 93°. I need to move point 3 by -3° so the points make exactly 90°. I have this function for it:
var angleToCorrect = alpha * (Math.PI / 180) - 90 * (Math.PI / 180);
correct_angle(point2, point3, angleToCorrect)
...
function correct_angle(p2, p3, angle) {
var x = p2.x - p3.x;
var y = p2.y - p3.y;
var r = Math.sqrt(x * x + y * y); //circle radius. origin of the circle is point 2
return {
X: p2.x + (Math.cos(angle) * r),
Y: p2.y + (Math.sin(angle) * r)
};
}
Now, this function should return new x and y for point 3 with corrected angle to 90°. Yet the coordinates don't agree with what I expect. Can someone point out what I'm doing wrong?
To calculate the new position it isn't enough to provide just two of the points since the angle is measured between the three.
So inside this function you have to figure out what the current angle of the vector between point 1 and point 2 is. Javascript offers a nifty built-in function for this Math.atan2()
Now that we know the angle (in radians) we need to add the new angle to it. This makes sure we can place point 3 correctly.
function correct_angle(p1, p2, p3, angle)
{
var currentAngle=Math.atan2(p1.y-p2.y, p1.x-p2.x);
currentAngle+=angle;
var x = p2.x - p3.x;
var y = p2.y - p3.y;
var r = Math.sqrt(x * x + y * y);
return {
X: p2.x + (Math.cos(currentAngle) * r),
Y: p2.y + (Math.sin(currentAngle) * r)
};
}
The angle parameter of the function should be the target angle in radians (90 or 1.5707963267949 in your case)
Here's an interactive example:
Point = function(x, y) {
this.x = x;
this.y = y;
}
var pointA = new Point(162, 39);
var pointB = new Point(105, 161);
var pointC = new Point(211, 242);
var context = document.getElementById("canvas").getContext("2d");
function correct() {
var newPoint = correct_angle(pointA, pointB, pointC, 1.5707963267949);
pointC.x = newPoint.X;
pointC.y = newPoint.Y;
draw();
}
function correct_angle(p1, p2, p3, angle) {
var currentAngle = Math.atan2(p1.y - p2.y, p1.x - p2.x);
currentAngle += angle;
var x = p2.x - p3.x;
var y = p2.y - p3.y;
var r = Math.sqrt(x * x + y * y);
return {
X: p2.x + (Math.cos(currentAngle) * r),
Y: p2.y + (Math.sin(currentAngle) * r)
};
}
function draw() {
context.clearRect(0, 0, 400, 300);
context.fillStyle = "red";
context.beginPath();
context.arc(pointA.x, pointA.y, 10, 0, 2 * Math.PI);
context.fill();
context.beginPath();
context.arc(pointB.x, pointB.y, 10, 0, 2 * Math.PI);
context.fill();
context.beginPath();
context.arc(pointC.x, pointC.y, 10, 0, 2 * Math.PI);
context.fill();
}
draw();
<canvas id="canvas" width="400" height="300" style="background-color:#dddddd;"></canvas>
<button onclick="correct()" style="float:left">
correct me
</button>

Converting X/ Y Circle Coordinates to functional Lat/ Lng

I wanted a random x/ y coordinate on the area of an intersection of two circles
My problem now is that i got the heavy part done, and it does work in the browser with Canvas for demonstration. But only on 2D Coordinates. Im using Google Maps, and i wanted to input two Circles with the Lat Lng positions and a radius. The result should have been the Lat/ Lang which i calculated on 2D.
But my mathematics end on real world coordinates..
so circle1.x and .y would be .lat and .lng for example.
/******************************************************************
Generate New Coord From Intersect
Generate random position point on cntersect area of two circles.
*******************************************************************/
var circle1 = {x : 0, y : 0, radius : 10};
var circle2 = {x : 0, y : 0, radius : 10};
var ctx;
var p2 = {x : 0, y : 0};
var p3 = {x : 0, y : 0};
var t3 = {x : 0, y : 0};
var t6 = {x : 0, y : 0};
var t7 = {x : 0, y : 0};
function GenerateNewCoordFromIntersect(circle1, circle2) {
var c = document.getElementById('canvasID');
c.width = 500;
c.height = 500;
ctx = c.getContext('2d');
drawCircle(circle1.x,circle1.y,circle1.radius,"red");
drawCircle(circle2.x,circle2.y,circle2.radius,"blue");
var distance = Math.sqrt(Math.pow(circle2.x-circle1.x,2) + Math.pow(circle2.y-circle1.y,2));
// then there are no solutions, the circles are separate.
if (distance > circle1.radius + circle2.radius ) {
return;
}
// then there are no solutions because one circle is contained within the other.
if (distance < circle1.radius - circle2.radius ) {
return;
}
// then the circles are coincident and there are an infinite number of solutions.
if (distance == 0 && circle1.radius == circle2.radius) {
return;
}
// random if take sector of circle 1 or 2
if (Math.random() > 0.5) {
var newcircle1 = JSON.parse(JSON.stringify(circle1));
var newcircle2 = JSON.parse(JSON.stringify(circle2));
circle1 = newcircle2;
circle2 = newcircle1;
}
// calc a
a = ((Math.pow(circle1.radius,2) - Math.pow(circle2.radius,2) + Math.pow(distance,2))) / (2 * distance);
// calc height
h = Math.sqrt(Math.pow(circle1.radius,2) - Math.pow(a,2));
// calc middle point of intersect
p2.x = circle1.x + a * (circle2.x - circle1.x) / distance;
p2.y = circle1.y + a * (circle2.y - circle1.y) / distance;
// calc upper radius intersect point
p3.x = p2.x + h * (circle2.y - circle1.y) / distance;
p3.y = p2.y - h * (circle2.x - circle1.x) / distance;
// random height for random point position
var randNumber = Math.random() / 2 + Math.random() * 0.5;
var radiusOfH = (h*2);
var randh = (randNumber) * radiusOfH - h;
// calc random Hypotenuse
var hypDistance = Math.abs(Math.sqrt(Math.pow(a,2) + Math.pow(randh,2)));
var randomHyp = (circle1.radius - hypDistance) + hypDistance;
// random point on line of middlepoint
t3.x = p2.x + ((randh) * (circle2.y - circle1.y)) / distance;
t3.y = p2.y - (randh) * (circle2.x - circle1.x) / distance;
// angle calc
var winkel = Math.atan(randh / a);
var newA = Math.cos(winkel) * randomHyp; //(randomHyp);
var newH = Math.sin(winkel) * randomHyp;//newA ;
t6.x = circle1.x + newA * (circle2.x - circle1.x) / distance;
t6.y = circle1.y + newA * (circle2.y - circle1.y) / distance;
t7.x = t6.x + newH * (circle2.y - circle1.y) / distance;
t7.y = t6.y - newH * (circle2.x - circle1.x) / distance;
randNumber = Math.random();
var xDist = (t7.x - t3.x) * (randNumber);
var yDist = (t7.y - t3.y) * (randNumber);
var rx = t3.x + xDist;
var ry = t3.y + yDist;
drawCircle(rx,ry,2,"blue");
}
function drawCircle(x, y, r, fill) {
ctx.beginPath();
ctx.arc(x,y,r,0,2*Math.PI);
ctx.strokeStyle = fill;
ctx.stroke();
}
You need to know the map projection that it's used to convert between lat/long coordinates to a x/y coordinates of a 2d plane.
You can find specific information for the Google Maps API here.

How can I create an array of X,Y coordinates in a square shape given three inputs?

I am trying to create a grid of x/y coordinates in a square pattern, given three points on an x/y plane. A grid like this
This is to be used to drive a gcode generator for moving a tool-head to desired x,y positions on a 3d printer. I need to account for skew and off-alignment of the square, so the grid of x / y points inside the square needs to account for the alignment.
function genGrid (topLeft, btmRight, btmLeft, rows, cols) {
// Generate Grid
// Return array of coordinates like the red dots in the picture I made.
}
[This picture helps explain it better!]
This code did the trick!
<script>
function grid(p1, p2, count) {
pointPerRow = Math.sqrt(count);
p3 = {
x: (p1.x + p2.x + p2.y - p1.y) / 2,
y: (p1.y + p2.y + p1.x - p2.x) / 2
};
p4 = {
x: (p1.x + p2.x + p1.y - p2.y) / 2,
y: (p1.y + p2.y + p2.x - p1.x) / 2
};
edgeLenght = Math.sqrt( (p3.x - p1.x)**2 + (p3.y - p1.y)**2);
vectorH = {
x: (p3.x - p1.x) / edgeLenght,
y: (p3.y - p1.y) / edgeLenght
};
vectorV = {
x: (p4.x - p1.x) / edgeLenght,
y: (p4.y - p1.y) / edgeLenght
};
movingStep = edgeLenght / (pointPerRow -1);
result = {};
for (var i = 0; i < pointPerRow; i++) {
row = {};
point = {
x: p1.x + vectorH.x * movingStep * (i),
y: p1.y + vectorH.y * movingStep * (i),
}
for (var j = 0; j < pointPerRow; j++) {
row[j] = {
x: point.x + vectorV.x * movingStep * (j),
y: point.y + vectorV.y * movingStep * (j),
};
}
result[i] = row;
}
// Debugging
for (var x=0;x < pointPerRow; x++) {
for (var y=0; y < pointPerRow; y++) {
ctx.fillStyle="#000000";
ctx.fillRect(result[x][y].x,result[x][y].y,10,10);
}
}
ctx.fillStyle="#FF0000";
ctx.fillRect(p1.x,p1.y,5,5);
ctx.fillRect(p2.x,p2.y,5,5);
ctx.fillRect(p3.x,p3.y,5,5);
ctx.fillRect(p4.x,p4.y,5,5);
return result;
}
// Create a canvas that extends the entire screen
// and it will draw right over the other html elements, like buttons, etc
var canvas = document.createElement("canvas");
canvas.setAttribute("width", window.innerWidth);
canvas.setAttribute("height", window.innerHeight);
canvas.setAttribute("style", "position: absolute; x:0; y:0;");
document.body.appendChild(canvas);
//Then you can draw a point at (10,10) like this:
var ctx = canvas.getContext("2d");
var grid = grid({x:100, y:50}, {x:200, y:350}, 16);
</script>

Find angle to rotate point so it faces another point in 3d space

Say I have two vectors:
V1 = { x: 3.296372727813439, y: -14.497928014719344, z: 12.004105246875968 }
V2 = { x: 2.3652551657790695, y: -16.732085083053185, z: 8.945905454164146 }
How can I figure out what angle v1 needs to be rotated to look directly at v2?
Put into English: say I knew exactly where I was in space, and exactly where another person was somewhere else in space.... Mathematically, how could I figure out what angles to put my finger at to point at them?
Here's what my axis looks like
My current (incorrect) formula
v2.x -= v1.x; // move v2 (vector 2) relative to the new origin
v2.y -= v1.y;
v2.z -= v1.z;
v1.x = 0; // set v1 (vector 1) as the origin
v1.y = 0;
v1.z = 0;
var r = Math.sqrt(Math.pow(v2.x,2) + Math.pow(v2.y,2) + Math.pow(v2.z,2));
var θ = Math.acos((Math.pow(v2.x,2) + Math.pow(v2.z,2))/(r*Math.sqrt(Math.pow(v2.x,2) + Math.pow(v2.z,2))));
var ϕ = Math.acos(v2.x/Math.sqrt(Math.pow(v2.x,2) + Math.pow(v2.z,2)));
Then I rotate v1 with the theta and phi.
v1.rotation.y = θ;
v2.rotation.x = ϕ;
But this is giving me the wrong rotation.
θ = 0.6099683401012933
ϕ = 1.8663452274936656
But if I use THREE.js and use the lookAt function, it spits out these rotations instead, that work beautifully:
y/θ: -0.24106818240525682
x/ϕ: 2.5106584861123644
Thanks for all the help in advance! I cannot use THREE.js in my final result, I'm trying to go pure vanilla JS so I can port it to another language.
How about using matrix? I think v1 is your view point, and looking towards v2. matrix is a good way to show orientation. Euler Angle is another interpretation of orientation.
My idea is building a transformation matrix from object space to world space, what you want to do can be translated in three steps:
at the beginning, the camera is at the world space origin, camera rotation is (0,0,0) world space is same as object space. v1'(0,0,0).
we translate camera to v1(3.296372727813439,-14.497928014719344,12.004105246875968), object space has an offset with world space, but object space axises are parallel with world space axises, camera rotation is still (0,0,0).
we make camera look at v2, as you see, camera rotation would change.
If I can build a transformation matrix represent all action above, I can get the orientation.
First, calculating translation matrix: Because translation is an affine transformation, we need to use a 4x4 matrix to represent translation. we can easily get the matrix:
we use basis axis to get rotation matrix.
you may need to set the camera up vector. the default is (0,1,0). in object space, the basis z axis can be calculated by v1-v2.
z = (v1.x-v2.x,v1.y-v2.y,v1.z-v2.z).normalize()
basis x vector: we know basis vector is a vector perpendicular to z-up plane, we get the x vector by cross product up and z.
x = up.crossproduct(z)
basis y vector, y is prependicular to z-x plane.
y = z.product(x)
we can build the rotation matrix as a 3 x 3 matrix:
then, we finally get the transformation matrix:
we can use the matrix represent the camera orientation. if you need Euler Angle or Quaternion. there some ways convert between them. you can find in this book: 3D Math Primer for Graphics and Game Developmen
Three.js implements LookAt() function same as my way.
Here is three.js source code, and I add some comments:
function lookAt( eye, target, up ) //eye : your camera position; target : which point you want to look at; up : camera up vector
{
if ( x === undefined ) {
x = new Vector3();
y = new Vector3();
z = new Vector3();
}
var te = this.elements; //this.elements is a 4 x 4 matrix stored in a list.
z.subVectors( eye, target ).normalize(); // set z vector with the direction from your camera to target point.
if ( z.lengthSq() === 0 ) {
z.z = 1;
}
x.crossVectors( up, z ).normalize(); // set the x vector by cross product up and z vector, you know cross product would get a //vector which perpendicular with these two vectors.
if ( x.lengthSq() === 0 ) {
z.z += 0.0001; // if z is ZERO vector, then, make a little addition to z.z
x.crossVectors( up, z ).normalize();
}
y.crossVectors( z, x ); // set y by cross product z and x.
// using basic axises to set the matrix.
te[ 0 ] = x.x; te[ 4 ] = y.x; te[ 8 ] = z.x;
te[ 1 ] = x.y; te[ 5 ] = y.y; te[ 9 ] = z.y;
te[ 2 ] = x.z; te[ 6 ] = y.z; te[ 10 ] = z.z;
return this;
};
// now you get the transformation matrix, you can set the rotation or orientation with this matrix.
You can implement Matrix with list like three.js does.
I also have another idea--Spherical Polar Coordinates System. Spherical coordinates always be noted like (r,Θ,Φ), Θ is heading angle, and Φ is pitch angle. what you need to do is convert v1 and v2 Cartesian coordinates to spherical coordinates. because the second and third element in spherical is angle, we can calculate angular displacement between v1 and v2. then, make this displacement as an addition to you camera rotation.
Here is my code(suppose you camera is at the world origin(0,0,0)):
//convert v1 and v2 Cartesian coordinates to Spherical coordinates;
var radiusV1 = Math.sqrt( Math.pow(v1.x) + Math.pow(v1.y) + Math.pow(v1.z));
var headingV1 = Math.atan2(v1.x , v1.z);
var pitchV1 = Math.asin(-(v1.y) / radiusV1);
var radiusV2 = Math.sqrt( Math.pow(v2.x) + Math.pow(v2.y) + Math.pow(v2.z));
var headingV2 = Math.atan2(v2.x , v2.z);
var pitchV2 = Math.asin(-(v2.y) / radiusV2);
//calculate angular displacement.
var displacementHeading = headingV2 - headingV1;
var displacementPitch = pitchV2 - pitchV1;
//make this displacement as an addition to camera rotation.
camera.rotation.x += displacementPitch;
camera.rotation.y += displacementHeading;
By the way, 3D math is very helpful and worth to learn, all the formula or concept I reference can be found in the book.
Wish it can help you.
The correct equations are:
var dx = v2.x-v1.x; //-0.93
var dy = v2.y-v1.y; //-31.22
var dz = v2.z-v1.z;
var rxy = Math.sqrt( Math.pow(dx,2) + Math.pow(dy,2) );
var lambda = Math.atan(dy/dx);
var phi = Math.atan(dz/rxy)
The above formula for phi and lambda needs to be adjusted based on which quarter your vector lies in. I made it easy for you:
//if you do the calculations in degrees, you need to add 180 instead of PI
if (dx < 0) phi = phi + Math.PI;
if (dz < 0) lambda = -1 * lambda;
x/ϕ is rotation around the x Axis so its equal to the angle between y,z and for y/θ we have to find angle between x,z.
V1 = { x: 3.296372727813439, y: -14.497928014719344, z: 12.004105246875968 }
V2 = { x: 2.3652551657790695, y: -16.732085083053185, z: 8.945905454164146 }
var v={dx:V2.x-V1.x, dy:V2.y-V1.y, dz:V2.z-V1.z}
testVector(v);
function testVector(vec){
console.log();
var angles=calcAngles(vec);
console.log("phi:"+angles.phi+" theta:"+angles.theta);
}
function calcAngles(vec){
return {
theta:(Math.PI/2)+Math.atan2(vec.dz, vec.dx),
phi:(3*Math.PI/2)+Math.atan2(vec.dz, vec.dy)
};
}
I've extracted the relevant code from the latest version of THREE.js (r84).
I think this is the best way of getting the result you're after.
// Unless otherwise noted by comments, all functions originate from the latest version of THREE.js (r84)
// https://github.com/mrdoob/three.js/tree/master
// THREE.js is licensed under MIT (Copyright © 2010-2017 three.js authors)
//
// Some functions have been changed by K Scandrett to work within this setting,
// but not the calculations.
// Any mistakes are considered mine and not the authors of THREE.js.
// I provide no guarantees that I haven't created any bugs in reworking the original code
// so use at your own risk. Enjoy the pizza.
var v1 = {x: 3.296372727813439, y: -14.497928014719344, z: 12.004105246875968};
var v2 = {x: 2.3652551657790695, y: -16.732085083053185,z: 8.945905454164146};
var startVec = {x: v1.x, y: v1.y, z: v1.z, w: 0};
var endVec = {x: v2.x, y: v2.y, z: v2.z, w: 0};
var upVec = {x: 0, y: 1, z: 0}; // y up
var quat = lookAt(startVec, endVec, upVec);
var angles = eulerSetFromQuaternion(quat);
console.log(angles.x + " " + angles.y + " " + angles.z);
/* KS function */
function magnitude(v) {
return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
}
/* KS function */
function normalize(v) {
var mag = magnitude(v);
return {
x: v.x / mag,
y: v.y / mag,
z: v.z / mag
};
}
function subVectors(a, b) {
return {
x: a.x - b.x,
y: a.y - b.y,
z: a.z - b.z
};
}
function crossVectors(a, b) {
var ax = a.x,
ay = a.y,
az = a.z;
var bx = b.x,
by = b.y,
bz = b.z;
return {
x: ay * bz - az * by,
y: az * bx - ax * bz,
z: ax * by - ay * bx
};
}
function lengthSq(v) {
return v.x * v.x + v.y * v.y + v.z * v.z;
}
function makeRotationFromQuaternion(q) {
var matrix = new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
var te = matrix;
var x = q.x,
y = q.y,
z = q.z,
w = q.w;
var x2 = x + x,
y2 = y + y,
z2 = z + z;
var xx = x * x2,
xy = x * y2,
xz = x * z2;
var yy = y * y2,
yz = y * z2,
zz = z * z2;
var wx = w * x2,
wy = w * y2,
wz = w * z2;
te[0] = 1 - (yy + zz);
te[4] = xy - wz;
te[8] = xz + wy;
te[1] = xy + wz;
te[5] = 1 - (xx + zz);
te[9] = yz - wx;
te[2] = xz - wy;
te[6] = yz + wx;
te[10] = 1 - (xx + yy);
// last column
te[3] = 0;
te[7] = 0;
te[11] = 0;
// bottom row
te[12] = 0;
te[13] = 0;
te[14] = 0;
te[15] = 1;
return te;
}
function RotationMatrix(m) {
// http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
// assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
var _w, _x, _y, _z;
var te = m,
m11 = te[0],
m12 = te[4],
m13 = te[8],
m21 = te[1],
m22 = te[5],
m23 = te[9],
m31 = te[2],
m32 = te[6],
m33 = te[10],
trace = m11 + m22 + m33,
s;
if (trace > 0) {
s = 0.5 / Math.sqrt(trace + 1.0);
_w = 0.25 / s;
_x = (m32 - m23) * s;
_y = (m13 - m31) * s;
_z = (m21 - m12) * s;
} else if (m11 > m22 && m11 > m33) {
s = 2.0 * Math.sqrt(1.0 + m11 - m22 - m33);
_w = (m32 - m23) / s;
_x = 0.25 * s;
_y = (m12 + m21) / s;
_z = (m13 + m31) / s;
} else if (m22 > m33) {
s = 2.0 * Math.sqrt(1.0 + m22 - m11 - m33);
_w = (m13 - m31) / s;
_x = (m12 + m21) / s;
_y = 0.25 * s;
_z = (m23 + m32) / s;
} else {
s = 2.0 * Math.sqrt(1.0 + m33 - m11 - m22);
_w = (m21 - m12) / s;
_x = (m13 + m31) / s;
_y = (m23 + m32) / s;
_z = 0.25 * s;
}
return {
w: _w,
x: _x,
y: _y,
z: _z
};
}
function eulerSetFromQuaternion(q, order, update) {
var matrix;
matrix = makeRotationFromQuaternion(q);
return eulerSetFromRotationMatrix(matrix, order);
}
function eulerSetFromRotationMatrix(m, order, update) {
var _x, _y, _z;
var clamp = function(value, min, max) {
return Math.max(min, Math.min(max, value));
};
// assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
var te = m;
var m11 = te[0],
m12 = te[4],
m13 = te[8];
var m21 = te[1],
m22 = te[5],
m23 = te[9];
var m31 = te[2],
m32 = te[6],
m33 = te[10];
//order = order || this._order;
order = order || 'XYZ'; // KS added. Other code sets the rotation order default
if (order === 'XYZ') {
_y = Math.asin(clamp(m13, -1, 1));
if (Math.abs(m13) < 0.99999) {
_x = Math.atan2(-m23, m33);
_z = Math.atan2(-m12, m11);
} else {
_x = Math.atan2(m32, m22);
_z = 0;
}
} else if (order === 'YXZ') {
_x = Math.asin(-clamp(m23, -1, 1));
if (Math.abs(m23) < 0.99999) {
_y = Math.atan2(m13, m33);
_z = Math.atan2(m21, m22);
} else {
_y = Math.atan2(-m31, m11);
_z = 0;
}
} else if (order === 'ZXY') {
_x = Math.asin(clamp(m32, -1, 1));
if (Math.abs(m32) < 0.99999) {
_y = Math.atan2(-m31, m33);
_z = Math.atan2(-m12, m22);
} else {
_y = 0;
_z = Math.atan2(m21, m11);
}
} else if (order === 'ZYX') {
_y = Math.asin(-clamp(m31, -1, 1));
if (Math.abs(m31) < 0.99999) {
_x = Math.atan2(m32, m33);
_z = Math.atan2(m21, m11);
} else {
_x = 0;
_z = Math.atan2(-m12, m22);
}
} else if (order === 'YZX') {
_z = Math.asin(clamp(m21, -1, 1));
if (Math.abs(m21) < 0.99999) {
_x = Math.atan2(-m23, m22);
_y = Math.atan2(-m31, m11);
} else {
_x = 0;
_y = Math.atan2(m13, m33);
}
} else if (order === 'XZY') {
_z = Math.asin(-clamp(m12, -1, 1));
if (Math.abs(m12) < 0.99999) {
_x = Math.atan2(m32, m22);
_y = Math.atan2(m13, m11);
} else {
_x = Math.atan2(-m23, m33);
_y = 0;
}
} else {
console.warn('THREE.Euler: .setFromRotationMatrix() given unsupported order: ' + order);
}
//_order = order;
//if ( update !== false ) this.onChangeCallback();
return {
x: _x,
y: _y,
z: _z
};
}
function setFromQuaternion(q, order, update) {
var matrix = makeRotationFromQuaternion(q);
return setFromRotationMatrix(matrix, order, update);
}
function setFromRotationMatrix(m) {
// http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
// assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
var _w, _x, _y, _z;
var te = m,
m11 = te[0],
m12 = te[4],
m13 = te[8],
m21 = te[1],
m22 = te[5],
m23 = te[9],
m31 = te[2],
m32 = te[6],
m33 = te[10],
trace = m11 + m22 + m33,
s;
if (trace > 0) {
s = 0.5 / Math.sqrt(trace + 1.0);
_w = 0.25 / s;
_x = (m32 - m23) * s;
_y = (m13 - m31) * s;
_z = (m21 - m12) * s;
} else if (m11 > m22 && m11 > m33) {
s = 2.0 * Math.sqrt(1.0 + m11 - m22 - m33);
_w = (m32 - m23) / s;
_x = 0.25 * s;
_y = (m12 + m21) / s;
_z = (m13 + m31) / s;
} else if (m22 > m33) {
s = 2.0 * Math.sqrt(1.0 + m22 - m11 - m33);
_w = (m13 - m31) / s;
_x = (m12 + m21) / s;
_y = 0.25 * s;
_z = (m23 + m32) / s;
} else {
s = 2.0 * Math.sqrt(1.0 + m33 - m11 - m22);
_w = (m21 - m12) / s;
_x = (m13 + m31) / s;
_y = (m23 + m32) / s;
_z = 0.25 * s;
}
return {
w: _w,
x: _x,
y: _y,
z: _z
};
}
function lookAt(eye, target, up) {
// This routine does not support objects with rotated and/or translated parent(s)
var m1 = lookAt2(target, eye, up);
return setFromRotationMatrix(m1);
}
function lookAt2(eye, target, up) {
var elements = new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
var x = {
x: 0,
y: 0,
z: 0
};
var y = {
x: 0,
y: 0,
z: 0
};
var z = {
x: 0,
y: 0,
z: 0
};
var te = elements;
z = subVectors(eye, target);
z = normalize(z);
if (lengthSq(z) === 0) {
z.z = 1;
}
x = crossVectors(up, z);
x = normalize(x);
if (lengthSq(x) === 0) {
z.z += 0.0001;
x = crossVectors(up, z);
x = normalize(x);
}
y = crossVectors(z, x);
te[0] = x.x;
te[4] = y.x;
te[8] = z.x;
te[1] = x.y;
te[5] = y.y;
te[9] = z.y;
te[2] = x.z;
te[6] = y.z;
te[10] = z.z;
return te;
}
function lookatOld(vecstart, vecEnd, vecUp) {
var temp = new THREE.Matrix4();
temp.lookAt(vecEnd, vecstart, vecUp);
var m00 = temp.elements[0],
m10 = temp.elements[1],
m20 = temp.elements[2],
m01 = temp.elements[4],
m11 = temp.elements[5],
m21 = temp.elements[6],
m02 = temp.elements[8],
m12 = temp.elements[9],
m22 = temp.elements[10];
var t = m00 + m11 + m22,
s, x, y, z, w;
if (t > 0) {
s = Math.sqrt(t + 1) * 2;
w = 0.25 * s;
x = (m21 - m12) / s;
y = (m02 - m20) / s;
z = (m10 - m01) / s;
} else if ((m00 > m11) && (m00 > m22)) {
s = Math.sqrt(1.0 + m00 - m11 - m22) * 2;
x = s * 0.25;
y = (m10 + m01) / s;
z = (m02 + m20) / s;
w = (m21 - m12) / s;
} else if (m11 > m22) {
s = Math.sqrt(1.0 + m11 - m00 - m22) * 2;
y = s * 0.25;
x = (m10 + m01) / s;
z = (m21 + m12) / s;
w = (m02 - m20) / s;
} else {
s = Math.sqrt(1.0 + m22 - m00 - m11) * 2;
z = s * 0.25;
x = (m02 + m20) / s;
y = (m21 + m12) / s;
w = (m10 - m01) / s;
}
var rotation = new THREE.Quaternion(x, y, z, w);
rotation.normalize();
return rotation;
}
Here's the same code in Plunker: http://plnkr.co/edit/vgNko1fJu9eYYCnJbYVo?p=preview
The exact/literal answer for your question would be a bad/unethical answer. Don't try to use Euler angles. The Euler coordinate system is for coordination. It's not a good system to do orientation/rotation. While being easy to read for human, it is prone to Gimbal lock-ing that will produce incorrect result.
There are 2 common systems for orientation: Transform matrix and Quaternions. three.js lookAt() uses quaternions, Crag.Li's answer uses Transform Matrix.
I feel obliged to emphasize this because I once underestimated 3D transformation and tried to solve it "the simple way" too, wasting nearly a month doing fool's work. 3D transformation is hard. There's no quick, dirty way to it, you can only do it the proper way. Grab a book (3D math primer is a good one) and spend time to learn the math if you truly want to do it.

Draw a nice neat arc without the massive right angle and have nice corners instead

I've made an arc in Raphael what I was aiming for was just one arc with out the
big right angle in it.
So just one smooth curved line without the right angle.
It's pretty basic and uses the Raphael elliptical arc.
You can see it at http://jsfiddle.net/mailrox/uuAjV/1/
Here's the code:
var raph = Raphael(0, 0, 1000, 1000);
var x = 150;
var y = 150;
var r = 100; //radius
var value = 100;
var maxValue = 360;
var pi = Math.PI;
var cos = Math.cos;
var sin = Math.sin;
var t = (pi/2) * 3; //translate
var rad = (pi*2 * (maxValue-value)) / maxValue + t;
var p = [
"M", x, y,
"l", r * cos(t), r * sin(t),
"A", r, r, 0, +(rad > pi + t), 1, x + r * cos(rad), y + r * sin(rad),
"z"
];
var param = {"stroke-width": 30}
var d = raph.path(p).attr(param);
One way I've done is I could mask the right-angle sections of the lines out however I'd rather not have this and just have once nice curve opposed to managing both that current path and a mask over the top.
Really appreciate some help with this thanks!
Try this. Just take the close path (the 'z') off your SVG path definition (note I didn't test this solution):
var raph = Raphael(0, 0, 1000, 1000);
var x = 150;
var y = 150;
var r = 100; //radius
var value = 100;
var maxValue = 360;
var pi = Math.PI;
var cos = Math.cos;
var sin = Math.sin;
var t = (pi/2) * 3; //translate
var rad = (pi*2 * (maxValue-value)) / maxValue + t;
var p = [
"M", x, y,
"l", r * cos(t), r * sin(t),
"A", r, r, 0, +(rad > pi + t), 1, x + r * cos(rad), y + r * sin(rad)
];
var param = {"stroke-width": 30}
var d = raph.path(p).attr(param);
jsFiddle
You can extend the Raphael object to include an arc function.
The arc calculation has been modfied from Raphael's 'Polar clock' demo: http://raphaeljs.com/polar-clock.html
Demo: http://jsfiddle.net/TmVHq/
Raphael.fn.arc = function(cx, cy, value, total, radius) {
var alpha = 360 / total * value,
a = (90 - alpha) * Math.PI / 180,
x = cx + radius * Math.cos(a),
y = cy - radius * Math.sin(a),
path;
if (total === value) {
path = [['M', cx, cy - radius], ['A', radius, radius, 0, 1, 1, (cx - 0.01), cy - radius]];
} else {
path = [['M', cx, cy - radius], ['A', radius, radius, 0, +(alpha > 180), 1, x, y]];
}
return this.path(path);
}
var Paper = new Raphael('canvas', 300, 300);
var arc = Paper.arc(150, 150, 270, 360, 100);
arc.attr('stroke-width', 15);​

Categories