How to map deviceorientation to MozOrientation values - javascript

I have previously created a labyrinth game which works with Firefox's MozOrientation. I am now looking into getting it working with WebKit also...
But Webkit uses the w3c's DeviceOrientation. The values appear to be totally different, but someone must have some algorithm to get it from one to the other? Or am I missing something simple?
The labyrinth game with github link
http://playground.marmaladeontoast.co.uk/labyrinth/
MozOrientation
https://developer.mozilla.org/en/Detecting_device_orientation
DeviceOrientation
http://dev.w3.org/geo/api/spec-source-orientation.html
Some sample values I have obtained:
Chrome
alpha = null
beta = -178
gamma = 4.57
Firefox
x = 0.035999998450279236
y = -0.02800000086426735
z = 1
Any help would be greatly appreciated :)

I've been playing around with this a bit. The source below 'maps' both alternatives to an understandable degrees range for x/gamma and y/beta.
The MozOrientation.z value is completely different from the deviceorientation.alpha value. The first returns the vertical orientation, the latter returns a sort of compass value. These values are therefor not interchangable / convertible.
function displayOrientation(orientData){
var x = Math.round(orientData.x);
var y = Math.round(orientData.y);
var z = orientData.z;
document.getElementById('x').value = x;
document.getElementById('y').value = y;
document.getElementById('z').value = z;
}
window.addEventListener("MozOrientation", function(orientData){
var obj = {};
obj.x = orientData.x * 90;
obj.y = orientData.y * 90;
obj.z = orientData.z;
displayOrientation(obj);
}, true);
window.addEventListener("deviceorientation", function(orientData) {
var obj = {};
obj.x = orientData.gamma;
obj.y = orientData.beta;
obj.z = orientData.alpha;
displayOrientation(obj);
}, true);
It seems the current desktop Safari (5.0.3) doesn't support this event at all. The beta value in desktop Chrome 9 is exactly 180 less than it is in mobile Safari.
The x & y values in Firefox and mobile Safari should be approximately the same.

I hope that this codes clarify everything. Although the values this code returns can't be matched directly with the values of beta and gamma:
Mozilla returns the sin of the angle so, to get the angle...
X: Math.asin(eventInfo.x)*180/Math.PI
Y: Math.asin(eventInfo.y)*180/Math.PI
Z: Math.asin(eventInfo.z)*180/Math.PI
Webkit returns the acceleration in each axe. Then, to get the angle... (The sing changes is just to unify returned values)
X: Math.asin(clean(eventInfo.accelerationIncludingGravity.x/GRAVITY))*180/Math.PI
Y: Math.asin(clean(-eventInfo.accelerationIncludingGravity.y/GRAVITY))*180/Math.PI
Z: Math.asin(clean(-eventInfo.accelerationIncludingGravity.z/GRAVITY))*180/Math.PI
Being clean: (Because sometimes, the data returned, even without accelerating the phone, it's more than 9.8)
function clean(data)
{
if(data<-1.0)
{
return -1.0;
}
else
{if(data>1.0)
{
return 1.0;
}else
{
return data;
}}
}
The meaning of each axe is:
X-> inclination of the mobile to its right or left. Will be + if you tilt the mobile to its right (clockwise), keeping the buttons of volume up.
Y-> Tells if the mobile is up -if the lock/on-off button is up- (angle +) or down (angle -). It tells the inclination of the mobile relative to the floor. (angle of Y vector with plane X-Z)
Z-> Tells if the mobile screen is facing up (angle +) or upside down (angle -). It's the angle of the Z vector on the plane Y-X
Hope this is worthy for you!
Anyway, I'm working on some GWT classes to make working with this easier :) It's not difficult but I do not have so much time.
I'll tell you

You should be able to calculate one according to the other.
The problem I see is that Mozilla doesn't tell what means the data it returns.
I'm working now with three mobile devices: ipad, iphone4 and nokia n900.
When I work with iOS and safari and I get the accelerationIncludingGravity, I get values from -9.8 to 9.8. Those are values of the gravity that affect each axe. Then using trigonometry, I can calculate the angle in each axe.
If I get orientation in the iphone4, I can get directly the angle in each axe.
The problem comes with nokia n900, whose browser is based in Mozilla. That browser only returns data from -1 to 1. Documentation says that it's the tilt of each axe. In what unities? Cosine? Or just angle/180?
I guess that it's the cosine as when I put it in horizontal, it returns 0, and when I put it in vertical, it gets 1. And upside down, returns -1. Moreover, If you tilt it in aprox 60degrees, it returns 1/2.
As far I've seen, when using a Macbook laptop with Firefox, the data x,y and z is get as the cosine.

Mozilla Firefox now fully supports the W3C DeviceOrientation Events API. You should not use MozOrientation in your web applications any more.

Related

HTML5 orientation axes jump

I'm making a web app for which I need to know the orientation of the phone around the depth axis: like where the phone would be if it were rotated like the arms on the face of clock. The beta axis tells me this.
However, when I hold the phone in portrait mode facing me, when I tilt the phone back and forth (the way the top card on a Rolodex would be tilted), all the values jump. See video:
https://drive.google.com/file/d/0B5C3MHv2Hmc6VTI2RDlOSkJPVHc/view?usp=sharing
I've tried it on two phones and they are consistent. How can I get that beta axis value without the jumping? I'm sure there is a mathematical way to cancel out the jumps, but I'm not sure where to start.
I was about to give up and I thought, "Gee, I never have that kind of problem with Unity. But, of course, Unity has quaternions." This thought led me to think that there must be a Quaternion library for JavaScript and indeed there is.
This led me to this code: I just rotate "up" to the phone orientation converted to a quaternion and grab the z axis:
let quaternion = new Quaternion();
let radian = Math.PI / 180;
$(window).on('deviceorientation', function(event) {
event = event.originalEvent;
quaternion.setFromEuler(
event.alpha * radian,
event.beta * radian,
event.gamma * radian);
let vector3 = quaternion.rotateVector([0, 1, 0]);
let result = vector3[2];
});
This gives exactly the result I need!

JavaScript deviceorientation on Microsoft Surface Pro 2: alpha and gamma values switched

I am using the deviceorientation event in JavaScript to retrieve Gyroscope data. So far, I have tested this on iOS devices which worked perfectly. Today I tried the same code on a Microsoft Surface Pro 2 tablet running Chrome 39 and noticed I get pretty strange values.
After exploring this a bit, it seems that the alpha and gamma values are switched on the Surface Pro, as it can be seen when testing on this page.
When lying flat on a table and rotating the device, my iPad Air changes its alpha value while the Surface Pro changes the gamma value. When picking up the device (so it stands at a 90 degree angle to the table), I get a gamma value of 90 on the iPad and an alpha value of 90 on the Surface Pro.
I suppose, according to the description given at HTML5Rocks about how those events should be working, the iPad is doing the correct thing but the Surface Pro is not.
So I guess my question is: Is this a known issue? Is there any way to detect or work around this?
WORKAROUND:
I am currently using the following workaround for this:
var alphaGammaFlipped = false;
window.addEventListener("deviceorientation", function(e) {
if (e.alpha < 0 || e.gamma > 180) alphaGammaFlipped = true;
var alpha = e.alpha;
var beta = e.beta;
var gamma = e.gamma;
if (alphaGammaFlipped) {
var temp = alpha;
alpha = gamma;
gamma = temp;
}
// Do something with alpha, beta, gamma...
});
This code uses the assumption that alpha should be between 0 and 360 and gamma should be between -180 and 180. If one of them is out of bounds, this means that they are probably flipped. Obviously, this is just a workaround als also the values will be wrong as long as both stay between 0 and 180. If anyone has something better, I'd be glad.
In my case, with the same config (Surface Pro 2 + javascript deviceorientation), the three angles are reversed.
For fix this I do that :
false_alpha = gamma
false_beta = alpha
false_gamma = beta
(I noticed that there was no inversion with Internet Explorer 11 ...)

Preloaded images start off smooth, become choppy after ~15 seconds regardless of activity in Google Chrome

Here's my cobbled-together fiddle in which I preload a number of face images, and the user can morph the central face by moving a marker around the circle surrounding it. At first the face morphs smoothly, but something goes wrong after about 15 seconds and the image transitions become very choppy. Note that this occurs for me regardless of user input, on multiple computers of various operating systems. What can I do to keep my image transitions smooth indefinitely?
This is the function that is updating the image and the marker:
function rotateAnnotationCropper(offsetSelector, xCoordinate, yCoordinate, cropper){
var x = xCoordinate - offsetSelector.offset().left - offsetSelector.width()/2;
var y = -1*(yCoordinate - offsetSelector.offset().top - offsetSelector.height()/2);
var theta = Math.atan2(y,x)*(180/Math.PI);
var cssDegs = convertThetaToCssDegs(theta);
var rotate = 'rotate(' +cssDegs + 'deg)';
cropper.css({'-moz-transform': rotate, 'transform' : rotate, '-webkit-transform': rotate, '-ms-transform': rotate});
$('body').on('mouseup', function(event){ $('body').unbind('mousemove')});
output = cssDegs + randomOffset;
output = (output % 360) + 1;
if (output < 0) {
output = 360+output;// since angles go from 0 to 270 and then -90 back to 0
}
faceNum = Math.round(output/degreesPerFace);
if (faceNum < 1) {
faceNum = 1;
}
element.src = images[faceNum-1].src;
return [faceNum, cssDegs, output, xCoordinate, yCoordinate];
}
EDIT: It seems as though jQuery is canceling requests to obtain an image at some point for reasons unbeknownst to me. From Chrome's Network tab, I can see the following errors start cropping up pretty often:
If I am cueing the transforms to occur too quickly I don't think that's what is causing the problem, since this starts happening even if I load up the fiddle, do nothing and then start interacting with it after 15 seconds.
EDIT 2: On further inspection, this problem appears unique to Google Chrome, and my problems on other browsers resulted from using older versions of my script. This thread provides some background on the Chrome issue.
(My thanks to those contributors on this thread in particular, who made this whole thing a lot easier for a novice like me to do.)

Check if two objects are facing eachother

I've searched all over and couldn't find an answer to this seemingly common question, surprisingly. The problem I'm currently facing is checking if the player is facing an enemy, then if so within what range of the players' view (adjustable) and if it's within that range then move away in the nearest safe direction.
Here's a picture :D
So, how would I accomplish this? I have the x, y, and direction, of every ship object. This is my last failed attempt, attempting to consider that the player's direction will be exactly 180 degrees away from the enemy's direction relative to the player.
var direction=Math.direction(this.x,this.y,player.x,player.y,1),
playerview=Math.abs(direction)-Math.abs(player.direction-180)
if(Math.abs(playerview)<10) {
console.log('in view')
this.xVelocity+=AI.speed*Math.sin(playerview*Math.PI/180)
this.xVelocity+=AI.speed*Math.cos(playerview*Math.PI/180)
}
In this example, 10 would be the range. Of course, I've only managed to make ships rotate to the right, so aside from the detection only working on half a circle I can't make the enemy go to the right side either. Any ideas?
In Your code, You are modifying this.xVelocity twice instead of modifying this.yVelocity.
I guess, Math.direction is not in the JavaScript/ECMA standard. Are You sure, You are using it correctly? Consider Math.atan2.
Moreover, You should provide a definition of "facing each other".
If it's "seeing each other", then Your comment "in view" is misleading.
But the main issue is:
Math.abs(angleInDegrees) modifies the angle!
The correct way to build an absolute value of an angle is:
while (angleInDegrees < 0)
{
angleInDegrees += 360;
}
// If necessary, add this too:
while (angleInDegrees >= 360)
{
angleInDegrees -= 360;
}
-10 and 350 are identical, -10 and 10 are 20 degrees apart.
For further explanation, let's call the code above normalizeAngle
(Note: For huge values, the loop may run very long or even forever. If such values may occur, this should be checked.)
The following should do the trick:
playerview = normalizeAngle(direction - player.direction - 180)
if (playerview < range || playerview > 360-range) {
By the way: "playerview" should be the mininum from the player's field of view and the enemy's field of view.

Detecting the system DPI/PPI from JS/CSS?

I'm working on a kind of unique app which needs to generate images at specific resolutions according to the device they are displayed on. So the output is different on a regular Windows browser (96ppi), iPhone (163ppi), Android G1 (180ppi), and other devices. I'm wondering if there's a way to detect this automatically.
My initial research seems to say no. The only suggestion I've seen is to make an element whose width is specified as "1in" in CSS, then check its offsetWidth (see also How to access screen display’s DPI settings via javascript?). Makes sense, but iPhone is lying to me with that technique, saying it's 96ppi.
Another approach might be to get the dimensions of the display in inches and then divide by the width in pixels, but I'm not sure how to do that either.
<div id='testdiv' style='height: 1in; left: -100%; position: absolute; top: -100%; width: 1in;'></div>
<script type='text/javascript'>
var devicePixelRatio = window.devicePixelRatio || 1;
dpi_x = document.getElementById('testdiv').offsetWidth * devicePixelRatio;
dpi_y = document.getElementById('testdiv').offsetHeight * devicePixelRatio;
console.log(dpi_x, dpi_y);
</script>
grabbed from here http://www.infobyip.com/detectmonitordpi.php. Works on mobile devices! (android 4.2.2 tested)
I came up with a way that doesn't require the DOM... at all
The DOM can be messy, requiring you to append stuff to the body without knowing what stuff is going on with width: x !important in your stylesheet. You would also have to wait for the DOM to be ready to use...
/**
* Binary search for a max value without knowing the exact value, only that it can be under or over
* It dose not test every number but instead looks for 1,2,4,8,16,32,64,128,96,95 to figure out that
* you thought about #96 from 0-infinity
*
* #example findFirstPositive(x => matchMedia(`(max-resolution: ${x}dpi)`).matches)
* #author Jimmy Wärting
* #see {#link https://stackoverflow.com/a/35941703/1008999}
* #param {function} fn The function to run the test on (should return truthy or falsy values)
* #param {number} start=1 Where to start looking from
* #param {function} _ (private)
* #returns {number} Intenger
*/
function findFirstPositive (f,b=1,d=(e,g,c)=>g<e?-1:0<f(c=e+g>>>1)?c==e||0>=f(c-1)?c:d(e,c-1):d(c+1,g)) {
for (;0>=f(b);b<<=1);return d(b>>>1,b)|0
}
var dpi = findFirstPositive(x => matchMedia(`(max-resolution: ${x}dpi)`).matches)
console.log(dpi)
There is the resolution CSS media query — it allows you to limit CSS styles to specific resolutions:
http://www.w3.org/TR/css3-mediaqueries/#resolution
However, it’s only supported by Firefox 3.5 and above, Opera 9 and above, and IE 9. Other browsers won’t apply your resolution-specific styles at all (although I haven’t checked non-desktop browsers).
Here is what works for me (but didn't test it on mobile phones):
<body><div id="ppitest" style="width:1in;visible:hidden;padding:0px"></div></body>
Then I put in the .js: screenPPI = document.getElementById('ppitest').offsetWidth;
This got me 96, which corresponds to my system's ppi.
DPI is by definition tied to the physical size of the display. So you won't be able to have the real DPI without knowing exactly the hardware behind.
Modern OSes agreed on a common value in order to have compatible displays: 96 dpi. That's a shame but that's a fact.
You will have to rely on sniffing in order to be able to guess the real screen size needed to compute the resolution (DPI = PixelSize / ScreenSize).
I also needed to display the same image at the same size at different screen dpi but only for Windows IE. I used:
<img src="image.jpg" style="
height:expression(scale(438, 192));
width:expression(scale(270, 192))" />
function scale(x, dpi) {
// dpi is for orignal dimensions of the image
return x * screen.deviceXDPI/dpi;
}
In this case the original image width/height are 270 and 438 and the image was developed on 192dpi screen. screen.deviceXDPI is not defined in Chrome and the scale function would need to be updated to support browsers other than IE
The reply from #Endless is pretty good, but not readable at all,
this is a similar approche with fixed min/max (it should be good ones)
var dpi = (function () {
for (var i = 56; i < 2000; i++) {
if (matchMedia("(max-resolution: " + i + "dpi)").matches === true) {
return i;
}
}
return i;
})();
matchMedia is now well supported and should give good result, see http://caniuse.com/#feat=matchmedia
Be careful the browser won't give you the exact screen dpi but only an approximation
function getPPI(){
// create an empty element
var div = document.createElement("div");
// give it an absolute size of one inch
div.style.width="1in";
// append it to the body
var body = document.getElementsByTagName("body")[0];
body.appendChild(div);
// read the computed width
var ppi = document.defaultView.getComputedStyle(div, null).getPropertyValue('width');
// remove it again
body.removeChild(div);
// and return the value
return parseFloat(ppi);
}
(From VodaFone)
Reading through all these responses was quite frustrating, when the only correct answer is: No, it is not possible to detect the DPI from JavaScript/CSS. Often, the operating system itself does not even know the DPI of the connected screens (and reports it as 96 dpi, which I suspect might be the reason why many people seem to believe that their method of detecting DPI in JavaScript is accurate). Also, when multiple screens are connected to a device forming a unified display, the viewport and even a single DOM element can span multiple screens with different DPIs, which would make these calculations quite challenging.
Most of the methods described in the other answers will almost always result in an output of 96 dpi, even though most screens nowadays have a higher DPI. For example, the screen of my ThinkPad T14 has 157 dpi, according to this calculator, but all the methods described here and my operating system tell me that it has 96 dpi.
Your idea of assigning a CSS width of 1in to a DOM element does not work. It seems that a CSS inch is defined as 96 CSS pixels. By my understanding, a CSS pixel is defined as a pixel multiplied by the devicePixelRatio, which traditionally is 1, but can be higher or lower depending on the zoom level configured in the graphical interface of the operating system and in the browser.
It seems that the approach of using resolution media queries produces at least some results on a few devices, but they are often still off by a factor of more than 2. Still, on most devices this approach also results in a value of 96 dpi.
I think your best approach is to combine the suggestion of the "sniffer" image with a matrix of known DPIs for devices (via user agent and other methods). It won't be exact and will be a pain to maintain, but without knowing more about the app you're trying to make that's the best suggestion I can offer.
Can't you do anything else? For instance, if you are generating an image to be recognized by a camera (i.e. you run your program, swipe your cellphone across a camera, magic happens), can't you use something size-independent?
If this is an application to be deployed in controlled environments, can you provide a calibration utility? (you could make something simple like print business cards with a small ruler in it, use it during the calibration process).
I just found this link: http://dpi.lv/. Basically it is a webtool to discover the client device resolution, dpi, and screen size.
I visited on my computer and mobile phone and it provides the correct resolution and DPI for me. There is a github repo for it, so you can see how it works.
Generate a list of known DPI:
https://stackoverflow.com/a/6793227
Detect the exact device. Using something like:
navigator.userAgent.toLowerCase();
For example, when detecting mobile:
window.isMobile=/iphone|ipod|ipad|android|blackberry|opera mini|opera mobi|skyfire|maemo|windows phone|palm|iemobile|symbian|symbianos|fennec/i.test(navigator.userAgent.toLowerCase());
And profit!
Readable code from #Endless reply:
const dpi = (function () {
let i = 1;
while ( !hasMatch(i) ) i *= 2;
function getValue(start, end) {
if (start > end) return -1;
let average = (start + end) / 2;
if ( hasMatch(average) ) {
if ( start == average || !hasMatch(average - 1) ) {
return average;
} else {
return getValue(start, average - 1);
}
} else {
return getValue(average + 1, end);
}
}
function hasMatch(x) {
return matchMedia(`(max-resolution: ${x}dpi)`).matches;
}
return getValue(i / 2, i) | 0;
})();
Maybe I'm a little bit steering off this topic...
I was working on a html canvas project, which was intended to provide a drawing canvas for people to draw lines on. I wanted to set canvas's size to 198x280mm which is fit for A4 printing.
So I started to search for a resolution to convert 'mm' to 'px' and to display the canvas suitably on both PC and mobile.
I tried solution from #Endless ,code as:
const canvas = document.getElementById("canvas");
function findFirstPositive(b, a, i, c) {
c=(d,e)=>e>=d?(a=d+(e-d)/2,0<b(a)&&(a==d||0>=b(a-1))?a:0>=b(a)?c(a+1,e):c(d,a-1)):-1
for (i = 1; 0 >= b(i);) i *= 2
return c(i / 2, i)|0
}
const dpi = findFirstPositive(x => matchMedia(`(max-resolution: ${x}dpi)`).matches)
let w = 198 * dpi / 25.4;
let h = 280 * dpi / 25.4;
canvas.width = w;
canvas.height = h;
It worked well on PC browser, showing dpi=96 and size was 748x1058 px;work well on PC
However turned to mobile devices, it was much larger than I expected: size: 1902x2689 px.can't work on mobile
After searching for keywords like devicePixelRatio, I suddenly realize that, I don't actually need to show real A4 size on mobile screen (under which situation it's actually hard to use), I just need the canvas's size fit for printing, so I simply set the size to:
let [w,h] = [748,1058];
canvas.width = w;
canvas.height = h;
...and it is well printed:well printed

Categories

Resources