here's code for Android and iOS to read the accelerometer. Unfortunately, i implemented both slightly different but the values returned are compatible. I'll cover the iOS first, because it's smaller and this way might be the preferred way to put this into Juce.
This way adds a function `virtual void getGravity(float& x, float& y, float& z)' to ComponentPeer like so,
class JUCE_API ComponentPeer
{
public:
...
virtual void getGravity(float& x, float& y, float& z)
{
x = 0;
y = 0;
z = 0;
}
...
};
here's a patch for modules\juce_gui_basics\native\juce_ios_UIViewComponentPeer.mm
--- v:\sw\juce\modules\juce_gui_basics\native\juce_ios_UIViewComponentPeer.mm 2013-11-07 15:48:26.000000000 -0000
+++ c:\sw\juce\modules\juce_gui_basics\native\juce_ios_UIViewComponentPeer.mm 2013-11-16 18:29:57.000000000 -0000
@@ -95,27 +95,32 @@
- (BOOL) canBecomeFirstResponder;
- (BOOL) textView: (UITextView*) textView shouldChangeTextInRange: (NSRange) range replacementText: (NSString*) text;
@end
//==============================================================================
-@interface JuceUIViewController : UIViewController
+@interface JuceUIViewController : UIViewController<UIAccelerometerDelegate>
{
+ UIAccelerometer* accelerometer;
+@public
+ float accel[3];
}
- (NSUInteger) supportedInterfaceOrientations;
- (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation) interfaceOrientation;
- (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation duration: (NSTimeInterval) duration;
- (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation;
+- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration;
- (void) viewDidLoad;
- (void) viewWillAppear: (BOOL) animated;
- (void) viewDidAppear: (BOOL) animated;
- (void) viewWillLayoutSubviews;
- (void) viewDidLayoutSubviews;
@end
+
//==============================================================================
@interface JuceUIWindow : UIWindow
{
@private
UIViewComponentPeer* owner;
@@ -248,12 +253,42 @@
return r;
}
MultiTouchMapper<UITouch*> currentTouches;
+
+ void getGravity(float& x, float& y, float& z)
+ {
+ Desktop& desktop = Desktop::getInstance();
+ Desktop::DisplayOrientation rot = desktop.getCurrentOrientation();
+
+
+ z = controller->accel[2];
+
+ switch (rot)
+ {
+ case Desktop::upsideDown:
+ x = -controller->accel[0];
+ y = -controller->accel[1];
+ break;
+ case Desktop::rotatedClockwise:
+ x = controller->accel[1];
+ y = -controller->accel[0];
+ break;
+ case Desktop::rotatedAntiClockwise:
+ x = -controller->accel[1];
+ y = controller->accel[0];
+ break;
+ default:
+ x = controller->accel[0];
+ y = controller->accel[1];
+ }
+
+ }
+
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponentPeer)
class AsyncRepaintMessage : public CallbackMessage
{
public:
@@ -310,12 +345,17 @@
}
- (void) viewDidLoad
{
JuceUIView* juceView = (JuceUIView*) [self view];
jassert (juceView != nil && juceView->owner != nullptr);
+
+ accelerometer = [UIAccelerometer sharedAccelerometer];
+ accelerometer.updateInterval = 0.2;
+ accelerometer.delegate = self;
+
juceView->owner->updateTransformAndScreenBounds();
}
- (void) viewWillAppear: (BOOL) animated
{
(void) animated;
@@ -335,12 +375,23 @@
- (void) viewDidLayoutSubviews
{
[self viewDidLoad];
}
+- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
+{
+ //float f = 0.2f;
+
+ // apply low pass filter (or not!)
+ accel[0] = acceleration.x; // * f + accel[0] * (1 - f);
+ accel[1] = acceleration.y; // * f + accel[1] * (1 - f);
+ accel[2] = acceleration.z; // * f + accel[2] * (1 - f);
+
+}
+
@end
now the Android version is a bit more complicated. most of the changes are in the Java activity. I added a new class to Juce called `SystemFeatures' because i didnt really know where it should be put. Like i said before, it might be better to add this the same as above in ComponentPeer (although, in fact, it's global which is why i didnt put it in ComponentPeer for Android).
The code is all bits and pieces, so i can't just post a patch, instead i'll talk through the changes to juce.
(1) need new entry in modules\juce_core\native\juce_android_JNIHelpers.h
METHOD (getTiltAngleX, "getTiltAngleX", "()F") \
METHOD (getTiltAngleY, "getTiltAngleY", "()F") \
(2) Then I put calls to this somewhere global in Juce, like this:
float SystemFeatures::getTiltAngleX()
{
return android.activity.callFloatMethod (JuceAppActivity.getTiltAngleX);
}
float SystemFeatures::getTiltAngleY()
{
return android.activity.callFloatMethod (JuceAppActivity.getTiltAngleY);
}
(3) Then the hard bit is all the code to change the Java activity.
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
public final class JuceAppActivity extends Activity
...
@Override
public final void onCreate (Bundle savedInstanceState)
{
super.onCreate (savedInstanceState);
onCreateVerified();
}
private void onCreateVerified()
{
sensors = new Sensors (this);
viewHolder = new ViewHolder (this);
setContentView (viewHolder);
setVolumeControlStream (AudioManager.STREAM_MUSIC);
}
@Override
protected final void onPause()
{
if (DBG) Log.d(TAG, "onPause");
super.onPause();
if (viewHolder != null)
{
suspendApp();
// Android will pause GL thread
viewHolder.onPause();
sensors.onPause();
}
}
@Override
protected final void onResume()
{
if (DBG) Log.d(TAG, "onResume");
super.onResume(); // this must go first
if (viewHolder != null)
{
sensors.onResume();
// android will resume GL thread
viewHolder.onResume();
resumeApp();
}
}
public interface Pausable
{
void onPause();
void onResume();
}
// class to read sensors
private Sensors sensors;
public final class Sensors implements SensorEventListener, Pausable
{
public Sensors(Context ctx)
{
context = ctx;
mgr = (SensorManager) context.getSystemService(SENSOR_SERVICE);
//accelerometer = mgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
gravitySensor = mgr.getDefaultSensor(Sensor.TYPE_GRAVITY);
// reg early in-case `resume' not called
onResume();
}
@Override
public final void onResume()
{
if (gravitySensor != null && !_reg)
{
mgr.registerListener(this, gravitySensor,
SensorManager.SENSOR_DELAY_UI);
_reg = true;
}
}
@Override
public final void onPause()
{
if (_reg)
{
mgr.unregisterListener(this, gravitySensor);
_reg = false;
}
}
public void onAccuracyChanged(Sensor sensor, int accuracy)
{
// ignore
}
public void onSensorChanged(SensorEvent event)
{
switch(event.sensor.getType())
{
case Sensor.TYPE_GRAVITY:
for(int i=0; i<3; i++)
gravity[i] = event.values[i];
// cos(ang) -> angleX
angleX = gravity[1]/SensorManager.GRAVITY_EARTH;
if(angleX > 1.0) angleX = 1.0;
else if(angleX < -1.0) angleX = -1.0;
//angle = Math.toDegrees(Math.acos(ratio));
if(gravity[2] < 0) angleX = -angleX;
angleY = gravity[0]/SensorManager.GRAVITY_EARTH;
if(angleY > 1.0) angleY = 1.0;
else if(angleY < -1.0) angleY = -1.0;
break;
}
}
boolean _reg;
public double angleX;
public double angleY;
Context context;
private SensorManager mgr;
private float[] gravity = new float[3];
private Sensor gravitySensor;
};
public final float getTiltAngleX()
{
// return sin(ang), adjusted for -90.. 0.. 90
return -(float)sensors.angleX;
}
public final float getTiltAngleY()
{
// return sin(angle)
// where angle is afjusted for -90 .. 0 .. +90
// with 0 in the flat position
return (float)sensors.angleY;
}
...
final class ViewHolder extends ViewGroup implements Pausable
...
@Override
public final void onPause()
{
for (int i = getChildCount(); --i >= 0;)
{
View v = getChildAt (i);
if (v instanceof Pausable)
((Pausable) v).onPause();
}
}
@Override
public final void onResume()
{
for (int i = getChildCount(); --i >= 0;)
{
View v = getChildAt (i);
if (v instanceof Pausable)
((Pausable) v).onResume();
}
}
...
public final class ComponentPeerView extends ViewGroup
implements View.OnFocusChangeListener, Pausable
...
@Override
public final void onPause()
{
for (int i = getChildCount(); --i >= 0;)
{
View v = getChildAt (i);
if (v instanceof Pausable)
((Pausable) v).onPause();
}
}
@Override
public final void onResume()
{
for (int i = getChildCount(); --i >= 0;)
{
View v = getChildAt (i);
if (v instanceof Pausable)
((Pausable) v).onResume();
}
}
...
public final class OpenGLView extends GLSurfaceView
implements GLSurfaceView.Renderer, Pausable
...
@Override
public void onResume()
{
if (DBG) Log.d(TAG, "GLView.onResume");
super.onResume(); // must be last
}
@Override
public void onPause()
{
if (DBG) Log.d(TAG, "GLView.onPause");
super.onPause();
}
This includes my "Pausable" interface because everything needs to pause including sensors, viewholders, glviews and just about anyone. It's possible you can just take the sensor bit, although you'll have to be sure to pause during `onPause' and `onResume'. Here i pull all this together in one interface. Later the WebView needs to pause too (when that goes in).
hope this helps,