Is there a way to detect physical device orientation in Cordova by accessing accelerometer, compass, or others?
I'd like to detect if a device is either sideways (ie landscape) or upright (ie portrait). Other than: cordova detecting orientation change I won't be able to utilise orientation via window.orientation because it fails in case a user has locked their orientation.
I don't need to know the mode that user is in. Just need to know if the device is sideways or upright.
I found a blog entry that describes how device orientation can be read from accelerometer despite a locked device orientation.
The relevant code snippet is provided in Objective-C, but naturally applies to Phonegap since it can be translated and used via Cordova's Device Motion Plugin.
Here's the original:
float x = -[acceleration x];
float y = [acceleration y];
float angle = atan2(y, x);
if(angle >= −2.25 && angle <= −0.75) {
//OrientationPortrait
} else if(angle >= −0.75 && angle <= 0.75){
//OrientationLandscapeRight
} else if(angle >= 0.75 && angle <= 2.25) {
//OrientationPortraitUpsideDown
} else if(angle <= −2.25 || angle >= 2.25) {
//OrientationLandscapeLeft];
}
Explanation: For any real arguments x and y that are not both equal to zero, atan2(y, x) is the angle in radians between the positive x-axis of a plane and the point given by the specified coordinates on it. The angle is positive for counter-clockwise angles, and negative for clockwise angles.
Related
I am using JavaScript. I need to know when user changed the orientation of the phone and I also need to know to which orientation ( 0, 90, -90 or 180).
I tried to use orientationchange from the docs.
window.addEventListener("orientationchange", function(event) {
console.log("the orientation of the device is now " + event.target.screen.orientation.angle);
});
Unfortunatelly, This doesn't work on IOS Safari. There's no orientation object in event.target.screen.
I tried using window.orientation (seems like it's working, but from the docs, it's deprecated and MUST be avoided.
I tried resize listener .
window.onresize = () => {
if(window.innerWidth >= window.innerHeight){
//landscape
}
else{
//portrait
}
};
This has 2 problems. 1) Sometimes this doesn't detect correctly on some iPhones or maybe on the same iPhone, but at different times. 2) I have no idea how to know the angle.(0,90, -90, 180). I don't know much about screens and innerHeight or innerWidth or stuff like that.
Any proper solution ?
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
}
You can use the deviceorientation event on the window.
window.addEventListener("deviceorientation", function(e){
const {absolute, alpha, beta, gamma} = e;
});
According to MDN:
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.
I've written a JS SDK that listens to mobile device rotation, providing 3 inputs:
α : An angle can range between 0 and 360 degrees
β : An Angle between -180 and 180 degrees
γ : An Angle between -90 to 90 degrees
Documentation for device rotation
I have tried using Euler Angles to determine the device orientation but encountered the gimbal lock effect, that made calculation explode when the device was pointing up. That lead me to use Quaternion, that does not suffer from the gimbal lock effect.
I've found this js library that converts α,β and γ to a Quaternion, so for the following values:
α : 81.7324
β : 74.8036
γ : -84.3221
I get this Quaternion for ZXY order:
w: 0.7120695154301472
x: 0.6893688637611577
y: -0.10864439143062626
z: 0.07696733776346154
Code:
var rad = Math.PI / 180;
window.addEventListener("deviceorientation", function(ev) {
// Update the rotation object
var q = Quaternion.fromEuler(ev.alpha * rad, ev.beta * rad, ev.gamma * rad, 'ZXY');
// Set the CSS style to the element you want to rotate
elm.style.transform = "matrix3d(" + q.conjugate().toMatrix4() + ")";
}, true);
Visualizing the device orientation using 4d CSS matrix derived from the Quaternion Reflected the right device orientation (DEMO, use mobile):
Wrong visualizing with Euler Angels and the developer tools (DEMO, use mobile):
I would like to write a method that gets α,β and γ and outputs if the device is in one of the following orientations:
portrait
portrait upside down
landscape left
landscape right
display up
display down
Defining each orientation as a range of +- 45° around the relevant axes.
What approach should I take?
Given that you've already managed to convert the Euler angles into a unit quaternion, here's a simple way to determine the orientation of the device:
Take a world-space vector pointing straight up (i.e. along the +z axis) and use the quaternion (or its conjugate) to rotate it into device coordinates. (Note that you could also do this using the Euler angles directly, or using a rotation matrix, or using any other representation of the device rotation that you can apply to transform a vector.)
Take the transformed vector, and find the component with the largest absolute value. This will tell you which axis of your device is pointing closest to vertical, and the sign of the component value tells you whether it's pointing up or down.
In particular:
if the device x axis is the most vertical, the device is in a landscape orientation;
if the device y axis is the most vertical, the device is in a portrait orientation;
if the device z axis is the most vertical, the device has the screen pointing up or down.
Here's a simple JS demo that should work at least on Chrome — or it would, except that the device orientation API doesn't seem to work in Stack Snippets at all. :( For a live demo, try this CodePen instead.
const orientations = [
['landscape left', 'landscape right'], // device x axis points up/down
['portrait', 'portrait upside down'], // device y axis points up/down
['display up', 'display down'], // device z axis points up/down
];
const rad = Math.PI / 180;
function onOrientationChange (ev) {
const q = Quaternion.fromEuler(ev.alpha * rad, ev.beta * rad, ev.gamma * rad, 'ZXY');
// transform an upward-pointing vector to device coordinates
const vec = q.conjugate().rotateVector([0, 0, 1]);
// find the axis with the largest absolute value
const [value, axis] = vec.reduce((acc, cur, idx) => (Math.abs(cur) < Math.abs(acc[0]) ? acc : [cur, idx]), [0, 0]);
const orientation = orientations[axis][1 * (value < 0)];
document.querySelector('#angles').textContent = `alpha = ${ev.alpha.toFixed(1)}°, beta = ${ev.beta.toFixed(1)}°, gamma = ${ev.gamma.toFixed(1)}°`;
document.querySelector('#vec').textContent = `vec = ${vec.map(a => a.toFixed(3))}, dominant axis = ${axis}, value = ${value.toFixed(3)}`;
document.querySelector('#orientation').textContent = `orientation = ${orientation}`;
}
onOrientationChange({ alpha: 0, beta: 0, gamma: 0 });
window.addEventListener("deviceorientation", onOrientationChange, true);
<script src="https://cdn.jsdelivr.net/npm/quaternion#1.1.0/quaternion.min.js"></script>
<div id="angles"></div>
<div id="vec"></div>
<div id="orientation"></div>
Note that there's apparently some inconsistency between browsers in the signs and ranges of the Euler angles supplied by the device orientation API, potentially causing the wrong signs to be computed on other browsers. You might need to do some browser sniffing to fix this, or use a wrapper library like gyronorm.js.
I am currently developing a kind of 2d game from a certain perspective (camera) and making it look like a 3d game.
The perspective never changes (turns).
Whatever. I have now reached the point of coding the hitboxes and the player is already able to recognize Colissions with other objects (Entitys).
Now I want to keep the player from running into other entities (hitboxes). My idea was to perform a colission detection as described below and then calculate the intersection so that I know on which side of the hitbox the player and the entity collide. Finally, I set the x or y speed of the player to 0, depending on where the overlap occurs.
I don't really know how to detect the Overlapping Axis / Axes, all I've found so far is that there is a Colission.
Colission:
function getColission(a, b) {
return (a.x <= b.x+b.sizeX && a.x+a.sizeX >= b.x) &&
(a.y <= b.y+b.sizeY && a.y+a.sizeY >= b.y) &&
(a.z <= b.z+b.sizeX && a.z+a.sizeZ >= b.z);
}
I have searched all over for a solution to my problem on smooth rotation. I can get it 99% working however there is one small issue that keeps messing with me. The rotations works fine except when the target makes a drastic change. The basic idea is I have a ball and a player when the player comes in contact with the ball he changes the balls direction and speed. The player follows the ball correctly however when he comes in contact with the ball or the ball's location is reset thus making the delta between the angles greater than 45 degrees the player instantly jumps to fact the ball. Here is the code that I have partly working. Thank you for any help in advance.
var newFacing = Math.atan2(theBall.y-player.y,theBall.x-player.x);
var diff = (Math.abs(newFacing) - Math.abs(player.facing));
if (diff < Math.PI*0.1 && diff > -Math.PI*0.1){
player.facing = newFacing;
}else if (diff > Math.PI) {
player.facing -= Math.PI*0.1;
} else {
player.facing += Math.PI*0.1;
}
if (player.facing > Math.PI) {
player.facing -= Math.PI*2;
} else if (player.facing < -Math.PI) {
player.facing += Math.PI*2;
}
Updated: Changed the || to && and added a condition to ensure I do not go above or below PI radians. There are still some odd rotational issues at some angles. For example if the ball passes below the player he will rotate clockwise to follow rather than the logical counter clockwise. At times the player will do a full 360 before tracking the ball again. Without tracing it I am not sure what conditions are causing this.
I wrote a very simple collision detection demo:
http://jsfiddle.net/colintoh/UzPg2/5/
As you can see, the objects sometimes doesn't connect at all but yet the collision is being triggered. The radius for the balls are 10px so the algo triggered the collision whenever the distance between two balls center is less than 20px. I reduced it to 18px for a better visual but the empty collision still happens randomly. Am I doing something wrong?
It looks like you are not using the right formula for distance between two points. See http://www.purplemath.com/modules/distform.htm for a full explanation.
You are doing this:
this.ballCollide = function(balli) {
if (Math.abs((this.x) - (balli.x)) < (2*radius - buffer)) {
if (Math.abs((this.y) - (balli.y)) < (2*radius - buffer)) {
// Do collision
}
}
};
That's a square bounding box, not a circular one. To get a circular bounding box, you can do something like this, based on the formula in the referenced web page:
this.ballCollide = function(balli) {
var deltax = this.x - balli.x;
var deltay = this.y - balli.y;
if (Math.sqrt(deltax * deltax + deltay * deltay) < 2 * radius - buffer) {
// Do collision
}
};
See http://jsfiddle.net/UzPg2/14/ for a working example.
Note that a perfect circular bounding box is a much slower algorithm than a square bounding box approximation.
Following Jarrod Roberson's point (a perfect circle is always inside a perfect square), you'd do that by basically combining your original code with the code I posted, like this (and you could combine them both into one conditional switch if you wanted to):
var deltax = this.x - balli.x;
var deltay = this.y - balli.y;
var dist = 2 * radius - buffer;
if (Math.abs(deltax) < dist && Math.abs(deltay) < dist) {
if (Math.sqrt(deltax * deltax + deltay * deltay) < dist) {
// Do collision
}
}
See http://jsfiddle.net/UzPg2/21/ for a working example (I've left the buffer as your variable is called at 2, but I personally think it looks better with a value of 1).
There are also many other ways you can optimize this for speed if you need to, but Jarrod's suggestion gives you the biggest immediate speed boost.
You're only checking for collisions on two axis, x and y. You need to use Pythagoras' theorem to detect on all axis at the cost of efficiency. For example.
Your algorithm will detect a collision around the point where these two balls are, since if you draw a tangent line along the x or y axis from one ball it goes through the other ball: http://jsfiddle.net/XpXzW/1/
Here you can see where they should actually collide:
http://jsfiddle.net/wdVmQ/1/
If you change your collision detection algorithm to check for perfect collisions (it will be less efficient) you can get rid of your buffer too:
http://jsfiddle.net/ucxER/
(Using Pythagoras' theorem the formula for a collision is:
Math.sqrt((this.x - balli.x)*(this.x - balli.x)
+ (this.y - balli.y)*(this.y - balli.y)) < 2*radius
Also what Jarrod commented is very smart. You can speed it up by using a technique like that. Since the square root is only calculated when the balls are close to each other:
http://jsfiddle.net/bKDXs/