I am working on an android project where I need to detect the exact directions when user turns (i.e. left or right or bottom or top). I have found the directions too by using accelerometer with magnetic sensor, but I couldn't fix the maximum values to be turned.
The problem I face is this:
I am unable to make it detect the center point/position.
When a user turns the phone left from the central position, it detects it as a left turn.
However, when I return the position to the central position, the phone detects it as two movements: both left. My understanding is this: If the sensor had got the central position (point of origin) correctly fixed, the movements would be correctly detected. But, because the central position is not fixed, this is not happening.
Kindly guide me in fixing it. Do let me know if you need further clarifications.
My code is as below
public void onSensorChanged(SensorEvent event) {
azimuth = event.values[0]; // azimuth
pitch = event.values[1]; // pitch
roll = event.values[2]; // roll
if (pitch < -45 && pitch > -135) {
// top
currentSide = Side.TOP;
} else if (pitch > 45 && pitch < 135) {
// bottom
currentSide = Side.BOTTOM;
} else if (roll > 45) {
// right
currentSide = Side.RIGHT;
} else if (roll < -45) {
// left
currentSide = Side.LEFT;
}
if (currentSide != null && !currentSide.equals(oldSide)) {
switch (currentSide) {
case TOP :
listener.onTopUp();
break;
case BOTTOM :
listener.onBottomUp();
break;
case LEFT:
listener.onLeftUp();
break;
case RIGHT:
listener.onRightUp();
break;
}
oldSide = currentSide;
}
// forwards orientation to the OrientationListener
listener.onOrientationChanged(azimuth, pitch, roll);
}
Thanks for helping!
Related
Suppose a person is using this compass, and beginning from 90 degrees they start rotating either clockwise or counterclockwise. What's the best way to keep count of how many full 360 degree rotations they complete? Assuming they'll be rotating either only clockwise or only counterclockwise from beginning to end.
I kept coming up with solutions where if the beginning bearing is, for example, 90 degrees I keep checking the next bearing when the sensor data changes, and if it's consistently moving in one direction I know they're rotating. And if they keep rotating in that direction and make it back to 90 degrees, that counts as one rotation. My way seems very convoluted and inefficient and I'm having a hard time coming up with a better way.
In this scenario, I'd be expecting multiple full rotations.
I'd appreciate any help. Thank you!
I found this related answer and am trying to put together a code sample for that. If someone has already done something similar, please post it!
#Override
public void onSensorChanged(SensorEvent event)
{
switch(event.sensor.getType())
{
case Sensor.TYPE_GRAVITY:
{
mValuesAccelerometer = lowPass(event.values.clone(), mValuesAccelerometer);
break;
}
case Sensor.TYPE_MAGNETIC_FIELD:
{
mValuesMagneticField = lowPass(event.values.clone(), mValuesMagneticField);
break;
}
}
boolean success = SensorManager.getRotationMatrix(
mMatrixR,
mMatrixI,
mValuesAccelerometer,
mValuesMagneticField);
if (success)
{
SensorManager.getOrientation(mMatrixR, mMatrixValues);
float azimuth = toDegrees(mMatrixValues[0]);
float pitch = toDegrees(mMatrixValues[1]);
float roll = toDegrees(mMatrixValues[2]);
if (azimuth < 0.0d)
{
//The bearing in degrees
azimuth += 360.0d;
}
}
}
If you're sure that they'll be moving in only 1 direction, to optimize your code you can have checkpoints for degrees instead of continuously monitoring if they're still moving in the right direction.
Here's a rough algo to do that
//You noted 90 degree as starting point
// checkpoint 1 will be 180 keep it as a boolean
// now you've reached 180 if the meter gets to 180 before going to next checkpoint
// which is 270 then make 180 false. it means they turned back.
// if they make it to 270 then wait for 0 degrees and do the same.
// if they make it back to 90 like that. You got a rotation and hopefully
// a bit of complexity is reduced as you're just checking for 4 checkpoints
I don't have any code handy at the moment.
This is a tracking problem with a reading that overflows. You need to keep track of the last reading and hope the user doesn't do more than a half turn between each reading.... (because of the Nyquist theorem)
Here is the basic pseudo code.
var totalChange = 0;
var lastAzimuth = -1000;
function CountTurns(az)
{
if (az > 180) az -= 360; // do this if your azimuth is always positive i.e. 0-360.
if (lastAzimuth == -1000)
{
lastAzimuth = az;
}
diff = az - lastAzimuth;
if (diff > 180)
diff -= 360;
if (diff < -180)
diff += 360;
lastAzimuth = az;
totalChange += diff;
return totalChange / 360;
}
Create 3 integers
int rotationCount=0
int currentDegrees=0
int previousDegrees=89
not a java programmer so i dont know how you handle the onSensorChanged event but basically perform a check within a while loop
while (currentDegrees + 90 < 360)
{
if (currentDegrees + 90 == 0)
{
if (previousDegrees == 359)
{
rotationCount = rotationCount + 1
}
}
else if (currentDegrees + 90 == 359)
{
if (previousDegrees == 0)
{
rotationCount = rotationCount - 1
}
}
previousDegrees = currentDegrees + 90
}
sorry about the syntax, this is just an example of how to do so..
Visualize what I will say and you'll definitely hit your goal in no time.
As you don't need to think of the full 360 degree, but you can take half of that and use the signs differences to your advantage.
Take a look at this figure :
We have a circle that is divided to two sides (left and right).
The left side will take negative 180 degree. (West Side).
The right side will take positive 180 degree. (East Side).
Current positing will be always 0 as (North) and positive 180 as (South).
IF the compass goes positive (meaning goes to the right direction)
Then add +1 on each turn.
IF the compass goes negative (meaning goes to the left direction).
Then subtract -1 on each turn
IF the compass hit OR is 0, then it's current position (NORTH).
IF the compass hit OR is 90, then it's (East).
IF the compass hit OR is 180, then it's (South)
IF the compass hit OR is -90, then it's (West).
This will turn out that whenever the person goes East, the counter will add +1 until it reaches 180, Then it'll change from positive to negative, which will subtract -1 on each turn until it reaches 0. That would be a full 360 rotation.
I want to change the OSMdroid MapView orientation to face the direction which the user is going (calculated with Location.bearingTo between the previous and current user location on each onLocationChanged, and converted into normal degrees instead of the -180/180° East of True North degrees).
This direction is correct, I'm rotating an arrow image towards this direction and it points towards the right direction without fail.
However, when I want to orientate the MapView to these userDirection using the setMapOrientation method (documented here), this isn't working as I want it to be. When I orientate the map towards the user's direction, the arrow image should always be pointing north, right? Because this is want I want to achieve: to make it seem like the arrow is always pointing forward (like a GPS tracker: your location on GPS is always represented by an icon going forward, my arrow is pointing to all kinds of directions because the map orientation is wrong).
I'm guessing the osmdroid.MapView orientation is expecting another sort of degree value, but I've tried converting back to East of True North degrees, didn't work. Or my logic is completely wrong and it is working correctly.
How do set the orientation for the MapView so that it is always facing the user's current direction, so that the arrow is always pointing forward (and not going backwards, right or left, ... )?
I think what you are referring to as is "True North" orientation of Map using the compass True North. For this you need the device Compass or Sensor Listener to get the direction, after getting the heading you need to set it for the MapView. Here is the Snippet which is very helpful.
private void compassHeadingUp() {
if(enableCompassHeadUp){
mSensorManager.registerListener(mySensorEventListener,
SensorManager.SENSOR_ORIENTATION,
SensorManager.SENSOR_DELAY_FASTEST);
} else {
mSensorManager.unregisterListener(mySensorEventListener);
mDirection = 0;
}
}
public SensorListener mySensorEventListener = new SensorListener(){
#Override
public void onAccuracyChanged(int arg0, int arg1) {
}
#Override
public void onSensorChanged(int sensor, float[] values) {
synchronized (this) {
float mHeading = values[0];
if(Math.abs(mDirection-mHeading) > Constance.ROTATION_SENSITIVITY){
mMapView.setMapOrientation(-mHeading);
mDirection = mHeading;
}
Matrix matrix = new Matrix();
mCompassImageView.setScaleType(ScaleType.MATRIX);
matrix.postRotate((float) -mHeading, mCompassImageView.getDrawable().getBounds().width()/2, mCompassImageView.getDrawable().getBounds().height()/2);
//Set your Arrow image view to the matrix
mCompassImageView.setImageMatrix(matrix);
}
}
};
I have solved this issue by inverting degrees like this:
float bearing = location.getBearing();
float t = (360 - bearing);
if (t < 0) {
t += 360;
}
if (t > 360) {
t -= 360;
}
//help smooth everything out
t = (int) t;
t = t / 5;
t = (int) t;
t = t * 5;
mapOSM.setMapOrientation(t);
I'm trying to setup my android game to use android sensors for controlling the game.
When the phone is tilted forwards slightly, I want the thrusters to fire.
And tilting the phone left or right rotates it in the rescpetive direction.
At the moment, I'm using TYPE_ORIENTATION as follows:
public void onSensorChanged(SensorEvent event) {
if(mMode == STATE_RUNNING) {
synchronized (mSurfaceHolder) {
//rotation
float pitch = event.values[2]; // pitch
if(pitch <= 15 & pitch >= -15) {
mRotating = 0;
} else if(pitch < -15) {
mRotating += 1;
} else if(pitch > 15) {
mRotating -= 1;
}
//thrust by tilting forward!
if(event.values[0] > 0) {
thrusterFiring = true;
} else {
thrusterFiring = false;
}
}
}
}
But I'm not sure if this is quite right? Additionally, you have to filt the phone quite a bit forwards in order for the thrusters to fire - I'd like it so you only need to tilt a little!
Any help appreciated!
I am getting various sensor readings from my device (programing for android) and i am looking to get the roll (which seems to be a number 1-100) converted into an angle in degrees and also convert the magnetometer heading into degrees..
any simple equations would be appreciated.. my geometry is a fleeting memory..
public void onSensorChanged(SensorEvent evt) {
int type=evt.sensor.getType();
if(type == Sensor.TYPE_ORIENTATION){
azimuth = evt.values[0]; // azimuth rotation around the z-axis
pitch = evt.values[1]; // pitch rotation around the x-axis
roll = evt.values[2]; // roll rotation around the y-axis
}
//Smoothing the sensor data a bit seems like a good idea.
if (type == Sensor.TYPE_MAGNETIC_FIELD) {
orientation[0]=(orientation[0]*1+evt.values[0])*0.5f;
orientation[1]=(orientation[1]*1+evt.values[1])*0.5f;
orientation[2]=(orientation[2]*1+evt.values[2])*0.5f;
} else if (type == Sensor.TYPE_ACCELEROMETER) {
acceleration[0]=(acceleration[0]*2+evt.values[0])*0.33334f;
acceleration[1]=(acceleration[1]*2+evt.values[1])*0.33334f;
acceleration[2]=(acceleration[2]*2+evt.values[2])*0.33334f;
}
if ((type==Sensor.TYPE_MAGNETIC_FIELD) || (type==Sensor.TYPE_ACCELEROMETER)) {
float newMat[]=new float[16];
//Toast toast = Toast.makeText(ctx.getApplicationContext(), "accel", Toast.LENGTH_SHORT);
//toast.show();
SensorManager.getRotationMatrix(newMat, null, acceleration, orientation);
if(displayOri==0||displayOri==2){
SensorManager.remapCoordinateSystem(newMat,SensorManager.AXIS_X*-1, SensorManager.AXIS_MINUS_Y*-1,newMat);
}else{
SensorManager.remapCoordinateSystem(newMat,SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X,newMat);
}
matrix=newMat;
}
}
I should add that i am not sure i just want roll.. my app locks in landscape mode but obviously the user can turn their phone to any angle on any access.. so i probably need all three of the above to get the angle im looking for.
the angle in question is as if the user is looking through their phone, no matter how they are holding it, at the real world and i want the angle they are looking off the horizon..
for instance if they are looking at the horizon i want 90degrees returned, if they are looking straight up in the sky i should get 180, straight down -180.. then i will also want the degrees from magnetic north that they are looking.. using the magnetometer
values[2] should allready contain degree-value, that's mentioned in a reference:
values[2]: Roll, rotation around Y axis (-90<=roll<=90), with positive
values when the z-axis moves toward the x-axis.
Update
Take a look at this picture: http://developer.android.com/images/axis_device.png
Here you see blue axis - Y axis. When your phone turns around it, it's called "rolling". The angle of the rotation will be contained in values[2].
I'm working on a simple platformer and I've been through a couple of collision systems before finally finding the solution that has thus far been as stable as I could hope for. That is until the player collides with a block whose CENTER position in the y axis equals 0. It's very wierd although I suspect it's only in the y axis because I check the x movement/collision first. I have no idea.
I do a simple AABB type collision check/response where I apply the x velocity first then if overlap, reposition player so that right player bound = left block bound bringing them out of overlap. I then go through the same with the y axis taking the player vertical direction to work out whether player has hit bottom side or top side of block. The only controls are jump as the player has an acceleration force in positive x hence the player will never travel left.
The problem is that the player moves off the blue dotted block but when it hits the red dotted one a collision is detected in the Y axis thus the player gets moved up out of overlap but then when the next frame executes the player's velocity in x is added as usual but a collision gets registered and it then positions the player to the left of the red block. The next frame detects a collision with the blue block and thus it positions the player on top of it as shown below.
The setup up below makes the player loop this sequence over
Info:
player.centerPosition = (2, 2)
player.width = 0.5f
player.height = 0.8f
blueBlock.centerPosition = (1, 1)
redBlock.centerPosition = (4, 0)
block.width = 3
block.height = 1
private void checkBlockCollision(float deltaTime) {
List<GameObject> colliders = grid.getPotentialColliders(player);
int len = colliders.size();
for (int axis=0;axis<2;axis++) { // 0 = X-axis, 1 = Y-axis
if (axis == 0) {
player.position.add(player.velocity.x*deltaTime, 0);
player.updateBounds();
} else {
player.position.add(0, player.velocity.y*deltaTime);
player.updateBounds();
}
for (int i=0;i<len;i++) { // Cycle through all blocks found in broad phase
GameObject collider = colliders.get(i);
if (OverlapTester.overlapRectangles(player.bounds, collider.bounds)) {
if (axis == 0) {
player.position.x = collider.position.x - (Player.PLAYER_WIDTH + collider.bounds.width)/2;
player.velocity.x = 0f;
Log.d("TAG", "Move player LEFT");
} else {
if (player.velocity.y > 0) {
player.position.y = collider.position.y - (Player.PLAYER_HEIGHT + collider.bounds.height)/2;
player.velocity.y = -player.velocity.y*0.333f;
Log.d("TAG", "Move player DOWN");
} else {
player.position.y = collider.position.y + (Player.PLAYER_HEIGHT + collider.bounds.height)/2;
player.velocity.y = 0;
player.state = Player.PLAYER_STATE_GROUNDED;
Log.d("TAG", "Move player UP");
}
}
}
} // end for loop colliders
} // end for loop axis
} // END METHOD
If anyone can shed some light on what the truck is going on here that would be amazing.
Thanks for reading and I can provide any further info or source to anyone interested.
Marios Kalogerou
SOLUTION:
I found a quick and dirty fix to the my problem. I just simply moved the player up an extra 0.001 units and this actually seperated the objects. Strange that since other blocks worked fine. Thanks again if you read through that and I hope my solution helps anyone with similar issues.