I am writing a mobile application for Windows Phone in HTML5. I want to detect the phone's rotation around the z-axis (I mean the rotation that is used for example in Doodle jump to make the character jump to the left or to the right).
In my application, a line that is vertical in the screen must remain vertical (absolute position in the real world) if I rotate the screen to the left or to the right. To accomplish this, I use the relative values of the angle: each time the screen is rotated a little, I rotate the points of the line by the same amount, but negative.
This works PERFECTLY if the phone is laying on a table or almost horizontal, but the behaviour is much less precise if for example I watch the phone in front of me. Anyway this is not a big problem of course because most users watch the phone from up to down, but a game like doodle jump behaves perfectly also in this situation.
Here is part of the code:
window.addEventListener("deviceorientation", handleOrientation, true);
function handleOrientation(event) {
alphaDiff = Math.floor(event.alpha) - alphaOld;
alphaOld = Math.floor(event.alpha);
//Rotation of the vertical line (x1,y1) - (x2,y2) around the center of the screen
var newPoint = rotate(225, 400, x1, y1, alphaDiff);
x1 = newPoint[0];
y1 = newPoint[1];
newPoint = rotate(225, 400, x2, y2, alphaDiff);
x2 = newPoint[0];
y2 = newPoint[1];
}
function rotate(cx, cy, x, y, angle) {
var radians = (Math.PI / 180) * angle;
cos = Math.cos(radians);
sin = Math.sin(radians);
nx = (cos * (x - cx)) - (sin * (y - cy)) + cx;
ny = (sin * (x - cx)) + (cos * (y - cy)) + cy;
return [nx, ny];
}
Have you tried beta (for the x-axis) or gamma (for the y-axis)?
e.g.
function handleOrientation(event) {
var absolute = event.absolute;
var alpha = event.alpha; // z-axis
var beta = event.beta; // x-axis
var gamma = event.gamma; // y-axis
// Do stuff with the new orientation data
}
Orientation Values Explained
The value reported for each axis indicates the amount of rotation around a given axis in reference to a standard coordinate frame.
The DeviceOrientationEvent.alpha value represents the motion of the device around the z-axis, represented in degrees with values ranging from 0 to 360.
The DeviceOrientationEvent.beta value represents the motion of the device around the x-axis, represented in degrees with values ranging from -180 to 180. This represents a front to back motion of the device.
The DeviceOrientationEvent.gamma value represents the motion of the device around the y-axis, represented in degrees with values ranging from -90 to 90. This represents a left to right motion of the device.
It also worth checking browser compatibility for this API.
As well as deviceorientation, you can check compassneedscalibration and devicemotion. For more details on deviceorientation see MDN.
A combination of beta and gamma seems to give pretty good results.
Basically, I normalize the values to a specific range and get the ratio.
window.addEventListener("deviceorientation", function(event){
var b = Math.abs(event.beta)/90;
if(b>1) b = 2-b;
var g = event.gamma/90;
if(Math.abs(event.beta)>90) g = -g;
var x = g/Math.max(0.25,b);
});
The final value is in x, positive means right, negative means left.
It works with the phone held horizontally or at any angle facing you.
Update: To make the detection work in a vertical position, the code calculates how much the phone is leaning and to which direction.
The value in x is always positive if the phone is leaning right and negative if leaning left.
This example shows a blue box in the middle of the screen and moves it to whichever direction the phone is leaning:
<!DOCTYPE html>
<html>
<head><meta name="viewport" content="width=1000,user-scalable=no"/></head>
<body style="margin:0">
<pre id="log" style="font-size:4em;"></pre>
<div id="box" style="background:#69c;width:100px;height:100px;position:relative;left:450px"></div>
<script>
var box = document.getElementById("box");
var log = document.getElementById("log");
var smoothx = 0;
window.addEventListener("deviceorientation", function(event){
var b = Math.abs(event.beta)/90;
if(b>1) b = 2-b;
var g = event.gamma/90;
if(Math.abs(event.beta)>90) g = -g;
var x = g/Math.max(0.25,b);
smoothx = smoothx*0.7+x*0.3;
box.style.left = Math.max(0, Math.min(900, smoothx*500+450))+"px";
log.innerText = x.toFixed(1)+"\n"+smoothx.toFixed(1);
});
</script>
</body>
</html>
Because the sensor seems to be very sensitive to movement, I implemented a simple smoothing that gives a steadier value in smoothx.
Use x for immediate value without smoothing.
Related
I'm writing an AR application in which the user sees a ship from the perspective of the captain of the ship like this
I want to use coordinates of other ships (like the one on the left) to draw the ships on top of the image using Canvas. The problem I'm having is mapping the latitude, longitude to Canvas points.
I've read about the Haversine and Bearing formulas but I can't figure out how to use them to do what I want. I know the latitude and longitude of the ship the user is looking from and tried using them to calculate the Canvas points of the other ship, but I can't get it to work. Any ideas?
Finding real world object in camera's view.
This is the most simplistic answer possible, as there are many unknowns that will affect the result.
The problem
The image illustrates the problem as I understand it.
We have 3 ships (the minimum required to find a solution) marked with a red A,B,C. We know the latitude and longitude of each ship. As they are close there is no need to correct for any distortion due to converging longitudes.
The green lines represent the camera's view, the image is projected via a lens onto the CCD (green line above A) The twp gray lines then are projected onto the camera's screen below the ship.
Triangulation and triangles.
From the longitudes we can find the distance from each ship to the other, giving us the lengths of the sides of a triangle.
var ships = {
A : { lat : ?, long : ? },
B : { lat : ?, long : ? },
C : { lat : ?, long : ? },
}
var AB = Math.hypot(ships.A.lat - ships.B.lat, ships.A.long - ships.B.long);
var BC = Math.hypot(ships.C.lat - ships.B.lat, ships.C.long - ships.B.long);
var CA = Math.hypot(ships.A.lat - ships.C.lat, ships.A.long - ships.C.long);
The inset triangle shows how to find the angle of a corner given the lengths of the sides.
For this we are after the angle pheta
var pheta = Math.acos(BC * BC - AB * AB + CA * CA) / (-2 * AB * CA));
The camera's field of view.
Now we need an extra bit of information. That is the field of view of the camera (in image as the green lines and red FOV). You will have to find those details for the camera you are using.
A typical phone camera has a focal length (fl) from 24-35mm equivalent and knowing the CCD size in relative terms you can find the FOV with var FOV = 2 * Math.atan((CCD.width / 2) / fl) But this is problematic as a modern phone is under 10mm thick and the CCD is tiny, what are the actual dimensions?
There is a long and complicated process you can use to determine the FOV without knowing the internal dimensions of the camera, but easy to look up the phone specs.
For now let's assume the camera has a FOV of 67deg (1.17radians). If the camera has a resolution of 1280 and we ignore lens distortions, and keep the camera vertical and at the same level as the targets , we can calculate the distance in pixels between the two ships via the angle between them.
var FOV = 1.17; // radians the Field of View of the camera
var pixels = (1.17 / 1280) * pheta; // 1280 is horizontal resolution of display
So now we have the distance in pixels between the two ships on the camera. Assuming that they fit on the camera we are missing one more important bit of information.
Bearings
We need to know which way the camera is pointing as a bearing. Only when we have that can we find the ships. So lets assume the GPS on the phone gives you the live bearing. What we need is the bearing to one of the ships.
I had to dig deep into my archives to find this, It had no source or referance so can only provide as is. All it had is Haversin so assuming that is the method used.
After some further digging I found a referance to what is likely the original source code that this was derived from Calculate distance, bearing and more between Latitude/Longitude points
function deg2rad(angle) { return angle * 0.017453292519943295 }
function rad2deg(angle) { return angle / 0.017453292519943295 }
//return bearing in radians.
function getBearing(lat1,lon1,lat2,lon2){
var earth = 6371e3;
var lat1 = lat1.toRadians();
var lat2 = lat2.toRadians();
var lon1 = lon1.toRadians();
var lon2 = lon2.toRadians();
var latD = lat2-lat1;
var lonD = lon2-lon1;
var a = Math.sin(latD / 2) * Math.sin(latD / 2) + Math.cos(lat1 ) * Math.cos(lat2) * Math.sin(lonD / 2) * Math.sin(lonD / 2);
var c = 2 * Math.atan2( Math.sqrt(a), Math.sqrt(1-a) );
return earth * c;
}
So now you can get the bearing from you to one of the ships. Use that and your bearing to find the difference in angle between you and it.
var yourBearing = ?; // in radians
var shipBBearing = getBearing(ships.A.lat, ships.A.long, ships.B.lat, ships.B.long);
Now get the differance in angle
var shipBAt = yourBearing - shipBBearing
Knowing the pixel FOV
var shipBPixelsFromCenter = (FOV / 1280) * shipBAt;
var shipBXpos = 1280 / 2 - shipBPixelsFromCenter;
// and from above the dif in pixels between ships is
var shipCXpos = shipBXpos + pixels.
And done.
I'm building an app in which I present some planes with textures. However, I would like to calculate the radius of the helix (which I use in my calculations to create a helix), dynamically based on the frustum width and the camera position.
The helix is positioned at the center of the screen x=0, y=0, z=0.
I would like this to take under consideration the screen orientation (landscape/ portrait).So far this is the code I have but it seems that I'm missing something because the planes at the left and the right are not inside the viewport.
App.prototype.calculateHelixRadius = function(){
// plane width = height = 512;
var friend = this.getFriend();
var vFOV = friend.camera.fov * Math.PI / 180;
var dist = utils.getAbsPointsDistance3D(friend.camera.position, friend.scene.position);
var aspect = friend.settings.container.clientWidth / friend.settings.container.clientHeight;
var frustumHeight = 2.0 * dist * Math.tan(0.5 * vFOV);
var frustumWidth = frustumHeight * aspect;
return utils.isLandscape() ? frustumHeight / 2 : frustumWidth / 2 ;
};
What am I doing wrong and why are the planes at the edges of the screen not inside?
Also for reference here is the code of getAbsPointsDistance3D
var utils = {
// other helpers...
getAbsPointsDistance3D: function(p1, p2) {
var xd = p2.x - p1.x;
var yd = p2.y - p1.y;
var zd = p2.z - p1.z;
return Math.sqrt(xd * xd + yd * yd + zd * zd);
}
};
update
I tried decreasing the dist parameter but the results are not consistent...
I wonder if the following explains your clipping.
You calculate your frustum characteristics, then calculate the helix radius using, say, the frustum width (width or height depending on the screen aspect...I may be getting some of the particulars wrong here because your question does not completely explain the details, but the general concepts still hold). The image below is a top view of the scenario which shows a circle representing the cylinder that encloses the helix. I believe you have calculated radius1. If so, note that there will be clipping of the cylinder (the shaded area), and thus the helix, in "front" of the cylinder centre.
Instead you need to calculate the cylinder/helix radius as shown in the second image, i.e. you need radius2. If the large angle at the image left is fov (again, vFOV? or hFOV?, etc., depending on whether your helix is going up-down or side-to-side, etc.), then its half angle is fov/2. This is the same angle shown in the centre of the cylinder. Thus, you need to decrease your helix radius as follows: radius2 = radius1 * cos(fov/2).
I am working on a "rally" game where a car is drawing on hills made of cosine curves. I know the current xspeed of the car (without hills) but the problem is that I need to know the xspeed of the car on the hills to be able to draw the wheels on right places and keep the speed steady.
At the moment my solution looks like this.
function drawWheelOnBasicHill(hillStart, xLocWheel, wheelNro) {
var cw = 400 //the width of the hill
t_max = 2*Math.PI;
var scale = 80, step = cw, inc = t_max/step;
var t1 = (xLocWheel-hillStart)*inc
var y1 = -scale*0.5 * Math.cos(t1);
if(wheelNro == 1 ){ //backwheel
drawRotatedImage(wheel, car.wheel1x, car.wheel1y-y1-45,sx);
//drawing the wheel on canvas
} else { //frontwheel
drawRotatedImage(wheel, car.wheel2x, car.wheel2y-y1-45,sx);
}
for(var i=1; i<=car.speed; i++){ //finding the next xlocation of the wheel with the
//same distance (on the curve) to the previous location as the speed of the car(=the
//distance to the new point on the flat ground)
var t2 = (xLocWheel + i -hillStart)*inc
var y2 = -scale*0.5 * Math.cos(t2);
if(Math.round(Math.sqrt(i^2+(y2-y1)^2))==car.speed){
sx = sx+i; //the new xcoordinate break;
}
}
}
The for loop is the problem. It might bee too slow (animation with fps 24). I cant understand why the if statement isnt working at the moment. It works sometimes but most of the times the value of the condition newer reaches the actual xspeed.
Are there some more efficient and easier ways to do this? Or does this code contain some errors? I really appreciate your efforts to solve this! Ive been looking at this piece of code the whole day..
So i is the variable and
x2=x1+i
t2=t1+i*inc
y1=-scale*0.5 * Math.cos(t1)
y2=-scale*0.5 * Math.cos(t2)
which somehow is strange. The landscape should be time independent, that is, y should be a function of x only. The time step is external, determined by the speed of the animation loop. So a more logical model would have dx as variable and
dt = t2-t1
x2 = x1 + dx
y1 = f(x1) = -0.5*scale*cos(x1)
y2 = f(x2) = -0.5*scale*cos(x2)
and you would be looking for the intersection of
(x2-x1)^2+(y2-y1)^2 = (speed*dt)^2
which simplifies to
(speed*dt)^2=dx^2+0.25*scale^2*(cos(x1+dx)-cos(x1))^2
For small values of dx, which would be the case if dt or speed*dt is small,
cos(x1+dx)-cos(x1) is approx. -sin(x1)*dx
leading to
dx = (speed*dt) / sqrt( 1+0.25*scale^2*sin(x1)^2 )
To get closer to the intersection of curve and circle, you can then iterate the fixed point equation
dydx = 0.5*scale*(cos(x1+dx)-cos(x1))/dx
dx = (speed*dt) / ( 1+dydx^2 )
a small number of times.
I'm kinda confused with this one.
I have an object and I know it's velocities on axis x and y. My problem is how to determine the angle at which it's moving.
function Object(){
this.velocity = {x: 5, y: 1};
}
Basically I Know that a vector's direction is x_projectioncos(deg) + y_projectionsin(deg), but I don't know how to get those projections since I only have the velocity, as I said I'm really confused.
#EDIT:
in addition to the accepted answer, here's what I did to get a full 360 degree spectrum
var addDeg = 0;
if(obj.velocity.x<0)
addDeg = obj.velocity.y>=0 ? 180 : 270;
else if(obj.velocity.y<=0) addDeg = 360;
deg = Math.abs(Math.abs(Math.atan(obj.velocity.y/obj.velocity.x)*180/Math.PI)-addDeg)
I don't know how to get those projections since I only have the
velocity
Actually, what you seem to be missing is that you already have the projections. That's what x and y are.
x is speed * cos(angle)
y is speed * sin(angle)
So y/x = sin(angle)/cos(angle) which is tan(angle) so angle=arctan(y/x).
That's the angle rotating anti-clockwise starting from the x axis (with x pointing right and y pointing up).
Find the angle between that vector and (1,0) (Right horizontal positive direction).
The math is:
A = (5,1)
B = (1,0)
A.B = |A||B|cos(angle) -> angle = arccos((|A||B|)/(A.B))
Dot product, check geometric definition
Edit:
Another option is to use the cross product formula:
|AxB| = |A||B|sin(angle) -> angle = arcsin((|A||B|)/(|AxB|))
It will give you the angle you need.
There is an easier way to get full 360 degrees. What you're looking for, is Math.atan2:
deg = Math.atan2(obj.velocity.y,obj.velocity.x)*180/Math.PI;
Over the last two days I've effectively figured out how NOT to rotate Raphael Elements.
Basically I am trying to implement a multiple pivot points on element to rotate it by mouse.
When a user enters rotation mode 5 pivots are created. One for each corner of the bounding box and one in the center of the box.
When the mouse is down and moving it is simple enough to rotate around the pivot using Raphael elements.rotate(degrees, x, y) and calculating the degrees based on the mouse positions and atan2 to the pivot point.
The problem arises after I've rotated the element, bbox, and the other pivots. There x,y position in the same only there viewport is different.
In an SVG enabled browser I can create new pivot points based on matrixTransformation and getCTM. However after creating the first set of new pivots, every rotation after the pivots get further away from the transformed bbox due to rounding errors.
The above is not even an option in IE since in is VML based and cannot account for transformation.
Is the only effective way to implement
element rotation is by using rotate
absolute or rotating around the center
of the bounding box?
Is it possible at all the create multi
pivot points for an object and update
them after mouseup to remain in the
corners and center of the transformed
bbox?
UPDATE:
I've attempted to use jQuery offset to find the pivot after it's been rotated, and to use that offset location as the pivot point.
Demo site ...
http://weather.speedfetishperformance.com/dev/raphael/rotation.html
The best cross-browser way I can think of to do what you want is to implement the rotation yourself rather than let SVG do it. Rotating x,y coordinates is fairly simple and I've been using this (tcl) code whenever I need to do 2D rotation: Canvas Rotation.
The upside to this is you have maximum control of the rotation since you're doing it manually. This solves the problems you're having trying to guess the final coordinates after rotation. Also, this should be cross browser compatible.
The downside is you have to use paths. So no rects (though it should be easy to convert them to paths) or ellipses (a little bit harder to convert to path but doable). Also, since you're doing it manually, it should be slower than letting SVG do it for you.
Here's a partial implementation of that Tcl code in javascript:
first we need a regexp to tokenize SVG paths:
var svg_path_regexp = (function(){
var number = '-?[0-9.]+';
var comma = '\s*[, \t]\s*';
var space = '\s+';
var xy = number + comma + number;
var standard_paths = '[mlcsqt]';
var horiz_vert = '[hv]\s*' + number;
var arc = 'a\s*' + xy + space + number + space + xy + space + xy;
var OR = '\s*|';
return new RegExp(
standard_paths +OR+
xy +OR+
horiz_vert +OR+
arc,
'ig'
);
})();
now we can implement the rotate function:
function rotate_SVG_path (path, Ox, Oy, angle) {
angle = angle * Math.atan(1) * 4 / 180.0; // degrees to radians
var tokens = path.match(svg_path_regexp);
for (var i=0; i<tokens.length; i++) {
var token = tokens[i].replace(/^\s+|\s+$/g,''); // trim string
if (token.match(/\d/)) { // assume it's a coordinate
var xy = token.split(/[, \t]+/);
var x = parseFloat(xy[0]);
var y = parseFloat(xy[1]);
x = x - Ox; // Shift to origin
y = y - Oy;
var xx = x * Math.cos(angle) - y * Math.sin(angle); // Rotate
var yy = x * Math.sin(angle) + y * Math.cos(angle);
x = xx + Ox; // Shift back
y = yy + Oy;
token = x + ',' + y;
}
else if (token.match(/^[hv]/)) {
// handle horizontal/vertical line here
}
else if (token.match(/^a/)) {
// handle arcs here
}
tokens[i] = token;
}
return tokens.join('');
}
The above rotate function implements everything except horizontal/vertical lines (you need to keep track of previous xy value) and arcs. Neither should be too hard to implement.