I'm implementing a kind of a "compass arrow" that follows destination depending on physical orientation of the device using magnetic field sensor. Suddenly I faced with a little problem.
Obtaining bearing and azimuth is OK, but performing a realistic animation turned into a really hard task. I tried to use different interpolators to make animation more "physical" (i. e. as in real compass, which arrow oscillate after hairpin rotation, accelerate and decelerate during movement etc).
Now I'm using interpolator.accelerate_decelerate and everything is quite good until updates start arriving quickly. That makes animations overlap each other and the arrow becomes twitchy and nervous. I want to avoid this. I tried to implement a queue to make every next animation wait until previous ends, or drop updates that come very quickly. That made animation look smooth, but arrow's behavior turned into absolutely illogical.
So I have 2 questions:
1) is there some way to make animated transitions more smooth in the cases when animations overlap each other?
2) is there a way to stop animation that is currently processing and get intermediate position of an object?
My code is below. An UpdateRotation() method handles orientation and bearing updates and executes animation of external viewArrow view.
public class DirectionArrow {
// View that represents the arrow
final View viewArrow;
// speed of rotation of the arrow, degrees/sec
final double rotationSpeed;
// current values of bearing and azimuth
float bearingCurrent = 0;
float azimuthCurrent = 0;
/*******************************************************************************/
/**
* Basic constructor
*
* #param view View representing an arrow that should be rotated
* #param rotationSpeed Speed of rotation in deg/sec. Recommended from 50 (slow) to 500 (fast)
*/
public DirectionArrow(View view, double rotationSpeed) {
this.viewArrow = view;
this.rotationSpeed = rotationSpeed;
}
/**
* Extended constructor
*
* #param viewArrow View representing an arrow that should be rotated
* #param rotationSpeed Speed of rotation in deg/sec. Recommended from 50 (slow) to 500 (fast)
* #param bearing Initial bearing
* #param azimuth Initial azimuth
*/
public DirectionArrow(View viewArrow, double rotationSpeed, float bearing, float azimuth){
this.viewArrow = viewArrow;
this.rotationSpeed = rotationSpeed;
UpdateRotation(bearing, azimuth);
}
/**
* Invoke this to update orientation and animate the arrow
*
* #param bearingNew New bearing value, set >180 or <-180 if you don't need to update it
* #param azimuthNew New azimuth value, set >360 or <0 if you don't need to update it
*/
public void UpdateRotation(float bearingNew, float azimuthNew){
// look if any parameter shouldn't be updated
if (bearingNew < -180 || bearingNew > 180){
bearingNew = bearingCurrent;
}
if (azimuthNew < 0 || azimuthNew > 360){
azimuthNew = azimuthCurrent;
}
// log
Log.println(Log.DEBUG, "compass", "Setting rotation: B=" + bearingNew + " A=" + azimuthNew);
// calculate rotation value
float rotationFrom = bearingCurrent - azimuthCurrent;
float rotationTo = bearingNew - azimuthNew;
// correct rotation angles
if (rotationFrom < -180) {
rotationFrom += 360;
}
while (rotationTo - rotationFrom < -180) {
rotationTo += 360;
}
while (rotationTo - rotationFrom > 180) {
rotationTo -= 360;
}
// log again
Log.println(Log.DEBUG, "compass", "Start Rotation to " + rotationTo);
// create an animation object
RotateAnimation rotateAnimation = new RotateAnimation(rotationFrom, rotationTo,
Animation.RELATIVE_TO_SELF, (float) 0.5, Animation.RELATIVE_TO_SELF, (float) 0.5);
// set up an interpolator
rotateAnimation.setInterpolator(viewArrow.getContext(), interpolator.accelerate_decelerate);
// force view to remember its position after animation
rotateAnimation.setFillAfter(true);
// set duration depending on speed
rotateAnimation.setDuration((long) (Math.abs(rotationFrom - rotationTo) / rotationSpeed * 1000));
// start animation
viewArrow.startAnimation(rotateAnimation);
// update cureent rotation
bearingCurrent = bearingNew;
azimuthCurrent = azimuthNew;
}
}
Here is my custom ImageDraw class where I implemted physical behavior of the pointing arrow based on equation of circular motion of dipole in magnetic field.
It don't uses any animators nor interpolators--on every iteration angular position is recalculated based on physical parameters. These parameters can be widely adjusted via setPhysical method. For example, to make rotations more smooth and slow, increase alpha (damping coefficient), to make arrow more responsitive, increase mB (coefficient of magnetic field), to make arrow oscillate on rotations, increase inertiaMoment.
Animation and redraw is performed implicitly by invoke of invalidate() on every iteration. There is no need to handle it explicitly.
To update the angle at which the arrow should rotate, just call rotationUpdate (by user's choice or using device orientation sensor callback).
/**
* Class CompassView extends Android ImageView to perform cool, real-life animation of objects
* such compass needle in magnetic field. Rotation is performed relative to the center of image.
*
* It uses angular motion equation of magnetic dipole in magnetic field to implement such animation.
* To vary behaviour (damping, oscillation, responsiveness and so on) set various physical properties.
*
* Use `setPhysical()` to vary physical properties.
* Use `rotationUpdate()` to change angle of "magnetic field" at which image should rotate.
*
*/
public class CompassView extends ImageView {
static final public float TIME_DELTA_THRESHOLD = 0.25f; // maximum time difference between iterations, s
static final public float ANGLE_DELTA_THRESHOLD = 0.1f; // minimum rotation change to be redrawn, deg
static final public float INERTIA_MOMENT_DEFAULT = 0.1f; // default physical properties
static final public float ALPHA_DEFAULT = 10;
static final public float MB_DEFAULT = 1000;
long time1, time2; // timestamps of previous iterations--used in numerical integration
float angle1, angle2, angle0; // angles of previous iterations
float angleLastDrawn; // last drawn anglular position
boolean animationOn = false; // if animation should be performed
float inertiaMoment = INERTIA_MOMENT_DEFAULT; // moment of inertia
float alpha = ALPHA_DEFAULT; // damping coefficient
float mB = MB_DEFAULT; // magnetic field coefficient
/**
* Constructor inherited from ImageView
*
* #param context
*/
public CompassView(Context context) {
super(context);
}
/**
* Constructor inherited from ImageView
*
* #param context
* #param attrs
*/
public CompassView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* Constructor inherited from ImageView
*
* #param context
* #param attrs
* #param defStyle
*/
public CompassView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* onDraw override.
* If animation is "on", view is invalidated after each redraw,
* to perform recalculation on every loop of UI redraw
*/
#Override
public void onDraw(Canvas canvas){
if (animationOn){
if (angleRecalculate(new Date().getTime())){
this.setRotation(angle1);
}
} else {
this.setRotation(angle1);
}
super.onDraw(canvas);
if (animationOn){
this.invalidate();
}
}
/**
* Use this to set physical properties.
* Negative values will be replaced by default values
*
* #param inertiaMoment Moment of inertia (default 0.1)
* #param alpha Damping coefficient (default 10)
* #param mB Magnetic field coefficient (default 1000)
*/
public void setPhysical(float inertiaMoment, float alpha, float mB){
this.inertiaMoment = inertiaMoment >= 0 ? inertiaMoment : this.INERTIA_MOMENT_DEFAULT;
this.alpha = alpha >= 0 ? alpha : ALPHA_DEFAULT;
this.mB = mB >= 0 ? mB : MB_DEFAULT;
}
/**
* Use this to set new "magnetic field" angle at which image should rotate
*
* #param angleNew new magnetic field angle, deg., relative to vertical axis.
* #param animate true, if image shoud rotate using animation, false to set new rotation instantly
*/
public void rotationUpdate(final float angleNew, final boolean animate){
if (animate){
if (Math.abs(angle0 - angleNew) > ANGLE_DELTA_THRESHOLD){
angle0 = angleNew;
this.invalidate();
}
animationOn = true;
} else {
angle1 = angleNew;
angle2 = angleNew;
angle0 = angleNew;
angleLastDrawn = angleNew;
this.invalidate();
animationOn = false;
}
}
/**
* Recalculate angles using equation of dipole circular motion
*
* #param timeNew timestamp of method invoke
* #return if there is a need to redraw rotation
*/
protected boolean angleRecalculate(final long timeNew){
// recalculate angle using simple numerical integration of motion equation
float deltaT1 = (timeNew - time1)/1000f;
if (deltaT1 > TIME_DELTA_THRESHOLD){
deltaT1 = TIME_DELTA_THRESHOLD;
time1 = timeNew + Math.round(TIME_DELTA_THRESHOLD * 1000);
}
float deltaT2 = (time1 - time2)/1000f;
if (deltaT2 > TIME_DELTA_THRESHOLD){
deltaT2 = TIME_DELTA_THRESHOLD;
}
// circular acceleration coefficient
float koefI = inertiaMoment / deltaT1 / deltaT2;
// circular velocity coefficient
float koefAlpha = alpha / deltaT1;
// angular momentum coefficient
float koefk = mB * (float)(Math.sin(Math.toRadians(angle0))*Math.cos(Math.toRadians(angle1)) -
(Math.sin(Math.toRadians(angle1))*Math.cos(Math.toRadians(angle0))));
float angleNew = ( koefI*(angle1 * 2f - angle2) + koefAlpha*angle1 + koefk) / (koefI + koefAlpha);
// reassign previous iteration variables
angle2 = angle1;
angle1 = angleNew;
time2 = time1;
time1 = timeNew;
// if angles changed less then threshold, return false - no need to redraw the view
if (Math.abs(angleLastDrawn - angle1) < ANGLE_DELTA_THRESHOLD){
return false;
} else {
angleLastDrawn = angle1;
return true;
}
}
Are you filtering your sensor data? The Magnetometer is a pain low pass filtering isn't really enough. You could use weighted-smoothing or maybe rounding data would be helpful:
Math.round( xyz * 10) / 10; ?
You could also reduce the frequency at which you get sensor updates. That might help.
mSensorManager.registerListener(this, mMagnetometer, 10000);
Espessially for gilonm, nice implementation of fixed size queue and getting its mean value:
float queue[ARRAY_LENGTH] = {0};
int queueFront = queue.length - 1 // position of front element
float meanValue = 0; // calculated mean value
float pushNewAndGetMean(float newValue){
// recalculate mean value
meanValue = meanValue + (newValue - queue[queueFront]) / queue.length;
// overwrite value in front pointer position
queue[queueFront] = newValue;
// shift front pointer 1 step right or to '0' if end of array reached
queueStart = (queueFront + 1) % array.length;
return meanValue
};
Here, not dependent on array length, you make just 2 reassignments of variables (instead of N) and use only 3 elements in mean calculation (instead of N). This makes algorithm O(1) complexity instead of O(N).
What you could do is where you get your data from the sensors - you can just use and array to do an average of say last 5 readings - that should smooth things down.
something like this:
Declare an array private float azimArray[] = {0,0,0,0,0};
Now where you get sensor data, use:
azimArray[0] = azimArray[1];
azimArray[1] = azimArray[2];
azimArray[2] = azimArray[3];
azimArray[3] = azimArray[4];
azimArray[4] = event.values[0]; //get actual sensor data into last array cell
currentAzimuth = Math.round(azimArray[0]+azimArray[1]+azimArray[2]+azimArray[3]+azimArray[4]/5);
Now currentAzimuth holds the rounded average of last 5 readings, which should smooth things down for you.
Hope this helped!
Related
I need help with the following problem since I have invested many days without arriving at an acceptable solution.
I'm doing an Android game (using libgdx) where the main character (named Hero) is seen from above (top-down view game) and walks on a field.
The user moves the character by moving his finger along the screen. The finger does't need to be on the character.
The character uses two animations, one animation when he moves forward (that is, when his "y" is greater than zero since the user looks at the game from the "sky") and another animation when he moves backwards (that is, when his "y" is less than zero, remember I'm developing a top-down view game).
Finally, I need the character to always move at a CONSTANT speed.
In short, I would like to handle the character with the finger and move it in the direction that marks my finger, always a CONSTANT speed.
This would be very easy if I could set the position of the character each delta time, but I'm using box2d which only knows about linearVelocity, impulses, forces, etc.
I tried using mouseJoint where hitbody is my main character (Hero) and groundBody is an invisible body.
// Invisible zero size ground body
// to which we can connect the mouse joint
Body groundBody;
BodyDef bodyDef = new BodyDef();
groundBody = world.createBody(bodyDef);
/* player is an instance of my Hero's class, which has a box2d body and
update, draw methods, etc.
*/
hitBody = player.getB2body();
...
InputProcessor:
#Override
public boolean touchDown(int i, int i1, int i2, int i3) {
gameCam.unproject(testPoint.set(i, i1, 0));
MouseJointDef def = new MouseJointDef();
def.bodyA = groundBody;
def.bodyB = hitBody;
def.collideConnected = true;
def.target.set(testPoint.x, testPoint.y);
def.maxForce = 1000.0f * hitBody.getMass();
mouseJoint = (MouseJoint) world.createJoint(def);
hitBody.setAwake(true);
}
#Override
public boolean touchUp(int i, int i1, int i2, int i3) {
player.getB2body().setLinearVelocity(0,0);
// if a mouse joint exists we simply destroy it
if (mouseJoint != null) {
world.destroyJoint(mouseJoint);
mouseJoint = null;
}
return false;
}
#Override
public boolean touchDragged(int i, int i1, int i2) {
// if a mouse joint exists we simply update
// the target of the joint based on the new
// mouse coordinates
if (mouseJoint != null) {
gameCam.unproject(testPoint.set(i, i1, 0));
mouseJoint.setTarget(target.set(testPoint.x, testPoint.y));
evaluateMovementDirection();
}
return false;
}
private void evaluateMovementDirection() {
float vy = player.getB2body().getLinearVelocity().y;
float vx = player.getB2body().getLinearVelocity().x;
// Test to Box2D for velocity on the y-axis.
// If Hero is going positive in y-axis he is moving forward.
// If Hero is going negative in y-axis he is moving backwards.
if (vy > 0.0f) {
player.onMovingUp(); // In draw, I'll use a "moving forward" animation
} else if (vy < 0.0f) {
player.onMovingDown(); // In draw, I'll use a "movieng backwards" animation
} else {
player.onStanding(); // vy == 0 In draw, I'll use a texture showing my Hero standig.
}
}
The problem I get with this, is that if I move my finger very fast, the character moves very fast. I would like the character to always move AT CONSTANT SPEED.
The other approach I tried is to use the pan event:
GestureListener:
#Override
public boolean pan(float x, float y, float deltaX, float deltaY) {
/*
* DeltaX is positive when I move my finger to the left, negative otherwise.
* DeltaY is positive when I move my finger down, negative otherwise.
*/
// In b2body y-axes sign is the opposite.
deltaY = -deltaY;
// DeltaX and deltaY are in pixels, therefore delta is in metres.
Vector2 delta = new Vector2(deltaX / Constants.PPM, deltaY / Constants.PPM);
// Deltas too small are discarded
if (delta.len() > Constants.HERO_SENSIBILITY_METERS) {
/*
* origin.x = player.getB2body().getPosition().x
* origin.y = player.getB2body().getPosition().y
*
* destination.x = origin.x + delta.x
* destination.y = origin.y + delta.y
*
* To go from origin to destination we must subtract their position vectors: destination - origin.
* Thus destination - origin is (delta.x, delta.y).
*/
Vector2 newVelocity = new Vector2(delta.x, delta.y);
// Get the direction of the previous vector (normalization)
newVelocity.nor();
// Apply constant velocity on that direction
newVelocity.x = newVelocity.x * Constants.HERO_LINEAR_VELOCITY;
newVelocity.y = newVelocity.y * Constants.HERO_LINEAR_VELOCITY;
// To avoid shaking, we only consider the newVelocity if its direction is slightly different from the direction of the actual velocity.
// In order to determine the difference in both directions (actual and new) we calculate their angle.
if (Math.abs(player.getB2body().getLinearVelocity().angle() - newVelocity.angle()) > Constants.HERO_ANGLE_SENSIBILITY_DEGREES) {
// Apply the new velocity
player.getB2body().setLinearVelocity(newVelocity);
evaluateMovementDirection();
}
} else {
// Stop
player.getB2body().setLinearVelocity(0, 0);
evaluateMovementDirection();
}
return true;
}
The problem I have with this is that the movement is very unstable and "dirty". The character is shaking.
I tried this approach (thanks #hexafraction).
Using this code, my charecter moves more fluid along the screen.
It's not perfect, but it's something...
#Override
public boolean pan(float x, float y, float deltaX, float deltaY) {
/*
* DeltaX is positive when I move my finger to the left, negative otherwise.
* DeltaY is positive when I move my finger down, negative otherwise.
* Both are in pixels, thus to get meters I must divide by Constants.PPM.
*/
// In b2body y-axes sign is the opposite.
deltaY = -deltaY;
/*
* origin.x = player.getB2body().getPosition().x
* origin.y = player.getB2body().getPosition().y
*
* destination.x = origin.x + deltaX / Constants.PPM
* destination.y = origin.y + deltaY / Constants.PPM
*
* To go from origin to destination we must subtract their position vectors: destination - origin.
* Thus, destination - origin is (deltaX / Constants.PPM, deltaY / Constants.PPM).
*/
candidateVelocity.x = deltaX / Constants.PPM;
candidateVelocity.y = deltaY / Constants.PPM;
// Get the direction of the previous vector (normalization)
candidateVelocity.nor();
// Apply constant velocity on that direction
candidateVelocity.x = candidateVelocity.x * Constants.HERO_LINEAR_VELOCITY;
candidateVelocity.y = candidateVelocity.y * Constants.HERO_LINEAR_VELOCITY;
// Linear interpolation to avoid character shaking
heroVelocity.lerp(candidateVelocity, Constants.HERO_ALPHA_LERP);
// Apply the result
player.getB2body().setLinearVelocity(heroVelocity);
// Depending on the result, we change the animation if needed
evaluateMovementDirection();
}
return true;
}
I need a suggestion on how to resolve this. I mean, move a box2d character with my finger along the screen at CONSTANT SPEED.
Thank you very much.
Calculate the direction in which you want to move:
dragPos.sub(currentPos);
Normalize it and multiply with the constant speed:
dragPos.sub(currentPos).nor().scl(CONSTANT_SPEED);
I have been using Compass Code from website http://www.ssaurel.com/blog/learn-how-to-make-a-compass-application-for-android/ .
Actually i am newly developing a compass application i have been getting an error Cannot Resolve symbol 'LowPassFilter'. I have imported every possible files still the same error
#Override
public void onSensorChanged(SensorEvent event) {
boolean accelOrMagnetic = false;
// get accelerometer data
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
// we need to use a low pass filter to make data smoothed
smoothed = **LowPassFilter**.filter(event.values, gravity);
gravity[0] = smoothed[0];
gravity[1] = smoothed[1];
gravity[2] = smoothed[2];
accelOrMagnetic = true;
} else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
smoothed = **LowPassFilter**.filter(event.values, geomagnetic);
geomagnetic[0] = smoothed[0];
geomagnetic[1] = smoothed[1];
geomagnetic[2] = smoothed[2];
accelOrMagnetic = true;
}
// get rotation matrix to get gravity and magnetic data
SensorManager.getRotationMatrix(rotation, null, gravity, geomagnetic);
// get bearing to target
SensorManager.getOrientation(rotation, orientation);
// east degrees of true North
bearing = orientation[0];
// convert from radians to degrees
bearing = Math.toDegrees(bearing);
// fix difference between true North and magnetical North
if (geomagneticField != null) {
bearing += geomagneticField.getDeclination();
}
// bearing must be in 0-360
if (bearing < 0) {
bearing += 360;
}
// update compass view
compassView.setBearing((float) bearing);
if (accelOrMagnetic) {
compassView.postInvalidate();
}
updateTextDirection(bearing); // display text direction on screen function
}
I have found the answer at http://www.java2s.com/Open-Source/Android_Free_Code/Sensor/compass/com_jwetherell_compass_commonLowPassFilter_java.htm.
package com.jwetherell.compass.common;
/* w w w . j a v a 2 s .co m*/
/**
* This class implements a low-pass filter. A low-pass filter is an electronic
* filter that passes low-frequency signals but attenuates (reduces the
* amplitude of) signals with frequencies higher than the cutoff frequency. The
* actual amount of attenuation for each frequency varies from filter to filter.
* It is sometimes called a high-cut filter, or treble cut filter when used in
* audio applications.
*
* #author Justin Wetherell (phishman3579#gmail.com)
*/
public class LowPassFilter {
/*
* Time smoothing constant for low-pass filter 0 ? ? ? 1 ; a smaller value
* basically means more smoothing See:
* http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization
*/
private static final float ALPHA = 0.2f;
private LowPassFilter() {
}
/**
* Filter the given input against the previous values and return a low-pass
* filtered result.
*
* #param input
* float array to smooth.
* #param prev
* float array representing the previous values.
* #return float array smoothed with a low-pass filter.
*/
public static float[] filter(float[] input, float[] prev) {
if (input == null || prev == null) throw new NullPointerException("input and prev float arrays must be non-NULL");
if (input.length != prev.length) throw new IllegalArgumentException("input and prev must be the same length");
for (int i = 0; i < input.length; i++) {
prev[i] = prev[i] + ALPHA * (input[i] - prev[i]);
}
return prev;
}
}
I am looking for a way to connect pan gesture with percentage of animation completion. Let me show you what I mean.
This image represents an animation that I want to execute, namely a moving Image actor or a sprite. The animation gets executed by pan gesture. Animation is 100% complete and at stage 6 when user slides for a 200px. If user slided only 100px, it would be 50% complete and at stage 3. If the user didn't execute pan gesture the animation stays at 0% and at stage 1. I am looking for tips on how to start building such a model. I believe it is called interactive. Do you have any suggestions?
You can use a GestureDetector to handle the panning input. The gestureListener.pan method can update an animation position parameter.
private int screenPixelsToAnimationPositionRatio = 0.01f; //will need to tweak this and probably adjust it at runtime based on screen dimensions and resolution
private float animationPosition = 0; //from 0 to 1, fraction of animation complete
public void create () {
//...
GestureAdapter gestureAdapter = new GestureAdapter {
#Override
public boolean pan (float x, float y, float deltaX, float deltaY) {
animationPosition += deltaX * screenPixelsToAnimationPositionRatio;
animationPosition = MathUtils.clamp(animationPosition, 0, 1);
return true;
}
};
GestureDetector gestureDetector = new GestureDetector(gestureAdapter);
Gdx.input.setInputProcessor(gestureDetector); //or put the detector in an InputMultiplexer with your other input listeners.
}
Then you would create a method that can update your object's position and rotation based on the current value of animationPosition. You would need to figure out the equations that determine the movement you want. For example, something that looks sort of like what you illustrated above:
private void updateAnimation (){
x = animationPosition * 30;
float y = 0, rotation = 0;
if (animationPosition >= 0.25f) {
float jumpPosition = Math.min(1, (animationPosition - 0.25f) / 0.5f);
y = 30 * (1 - Interpolation.pow2In.apply(Math.abs(2 * jumpPosition - 1)));
rotation = 180 * jumpPosition;
}
mySprite.setPosition(x, y);
mySprite.setRotation(rotation);
}
Then call this update method somewhere in render.
I've been assigned to make a simple android app game in which the user controls a ball on the screen by tilting the phone. The app utilizes a custom view to draw all the objects and it uses a Runnable object to animate the ball. Note that x and y represent the ball's postilion, vx and vy represent the ball's velocity and fx and fy represent the forces being applied to it. ix and iy represent the phone's tilt; I set these two to dummy values in order to test the app without worrying about setting up the sensor manager for the time being :
private Runnable animator = new Runnable() {
#Override
public void run() {
boolean needNewFrame = false;
long now = AnimationUtils.currentAnimationTimeMillis();
float dt = Math.min(now - lastTime, 50) / 1000f;
fx = - alpha * vx + beta * ix;
fy = - alpha * vy + beta * iy;
vx = fx * dt;
vy = fx * dt;
if(x<xMin||x>xMax-ball_radius||y<yMin||y>yMax-ball_radius)
bounce();
x = x + vx * dt;
y = y + vy * dt;
lastTime = now;
needNewFrame = true;
postDelayed(this, 20);
invalidate();
}
};
I've set up the animator like so but it won't run. I tried calling post(animator) on the views initialization but that didn't work. How do I fix this?
Also, how do I set the ix and iy variables from the phone's tilt? From what I understand, the Sensor Manager is meant to be setup from an activity class.
Actually, the animation does play successfully. It's just that the animation was playing very slowly.
I tried to create a small application that calculates the light through the light sensor, but different devices give me completely different values. I used the max value to calculate a percentage, but my xperia and my samsung are in the same place (near the wall outside my house, under the roof) and the first device gives me 90% (90k/102k) and the second one gives me 12% (8k/64k). Is there a way to avoid this problem?
public final void onSensorChanged(SensorEvent event)
{
if( event.sensor.getType() == Sensor.TYPE_LIGHT)
{
currentLux=(int) event.values[0];
float perc=(currentLux*100)/max;
lightTextBox.setText(lightTextBox.getText()+String.valueOf(perc)+" * ");
}
}
Since every sensor have different values for the same light level, so what I did is to record the minimum and the maximum values that will be captured during the application lifetime, and I stored these values as a Min and Max, after a few days of using the application, all the devices will have almost the same value that I can reliable on.
as the initial state, I considered the min value is 1000 and the max value is 0. (I stored it in the SharedPreferences).
then I will update these values by considering the current Lux value of the sensor.
and then I will calculate the percentage of the lighting.
this is the code that I wrote:
float luxValue = o.getAsFloat("double_light_lux");
//here I update the value in sharedPreferences
updateMaxAndMinValue(context, luxValue);
float percentageValue = scale(LightMetadata.getMinValue(context),
LightMetadata.getMaxValue(context),
0,
100,
luxValue);
the updating function:
public static void updateMaxAndMinValue(Context context, float luxValue) {
float currentMax = LightMetadata.getMaxValue(context); //0 as init.
float currentMin = LightMetadata.getMinValue(context); //1000 as init.
if (luxValue > currentMax)
LightMetadata.setMaxValue(context, luxValue);
if (luxValue < currentMin)
LightMetadata.setMinValue(context, luxValue);
}
and I calculate the percentage by scaling the value from min -> max to 0 -> 100
this is the function:
/**
* #param min old min value
* #param max old max value
* #param a new min value
* #param b new max value
* #param value the value that want to scale
* #return new scaled value
*/
public static float scale(float min, float max, float a, float b, float value) {
// (b-a)(x - min)
//f(x) = -------------- + a
// max - min
float scaled;
float numerator = (b - a) * (value - min);
float denominator = max - min;
scaled = (numerator / denominator) + a;
return scaled;
}