tomasslavicek

Xamarin C# SensorType.RotationVector, Gravity, Accelerometer

Jun 11th, 2015
453
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 13.28 KB | None | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Linq;
  5. using System.Text;
  6. using Android.Content;
  7. using Android.Hardware;
  8. using KoalaPhone.Screens;
  9. using Microsoft.Xna.Framework;
  10.  
  11. namespace KoalaPhone.AndroidHelpers.Listeners
  12. {
  13.     /// <summary>
  14.     /// Speciální listener pro čtení orientace z akcelerometru / kompasu v případě VR
  15.     /// - vrací úhel naklopení hlavy / koeficient
  16.     /// </summary>
  17.     public class VROrientListener
  18.     {
  19.         SensorManager sensorManager;
  20.         Sensor gravitySensor, rotationSensor, accSensor, magneticSensor; // rotace = vyhlazená
  21.         ISensorEventListener listener; // Gravitace (osa Y)
  22.         bool enabled;
  23.  
  24.         public Vector2 CursorPosCoef = Vector2.Zero; // Výsledná pozice VR kurzoru (-1 až 1 v ose X,Y)
  25.         public bool IsInitialized = false;
  26.  
  27.         public float LastAzimuth; // Parametry pro kalibraci (čte si je třída MultiListenerImpl)
  28.         public float AzimBaseAngle;
  29.         public Stopwatch SwPrecise; // POčítadlo, kolik uběhlo od posledního bouchnutí do telefonu
  30.        
  31.         public void InitializeSensorListener()
  32.         {
  33.             // Připraví objekt pro čtení z akcelerometru, napojí event (volaný, když se změní orientace)
  34.             sensorManager = (SensorManager)Game.Activity.GetSystemService(Context.SensorService);
  35.             gravitySensor = sensorManager.GetDefaultSensor(SensorType.Gravity);
  36.             rotationSensor = sensorManager.GetDefaultSensor(SensorType.RotationVector);
  37.             accSensor = sensorManager.GetDefaultSensor(SensorType.Accelerometer); // Pro detekci "bouchnutí"
  38.             magneticSensor = sensorManager.GetDefaultSensor(SensorType.MagneticField); // Cardboard tlačítko
  39.            
  40.             if (gravitySensor != null || rotationSensor != null)
  41.                 listener = new MultiListenerImpl();
  42.  
  43.             IsInitialized = true;
  44.         }
  45.  
  46.         public void CalibrateToCenter()
  47.         {
  48.             // Zkalibruje hodnoty, že tohle je střed obrazu
  49.             // - voláno na začátku + po stisku na displej
  50.             AzimBaseAngle = LastAzimuth;
  51.             //TODO kalibrovat i Y souřadnici?
  52.         }
  53.  
  54.         public void StartSensorReading()
  55.         {
  56.             if (gravitySensor == null)
  57.                 return;
  58.  
  59.             try
  60.             {
  61.                 if (!enabled)
  62.                 {
  63.                     // Spustí čtení z akcelerometru, gravitace a magnetometru (pokud už není spuštěné)
  64.                     if (gravitySensor != null)
  65.                         sensorManager.RegisterListener(listener, gravitySensor, SensorDelay.Game);
  66.                     if (rotationSensor != null)
  67.                         sensorManager.RegisterListener(listener, rotationSensor, SensorDelay.Game);
  68.                     if (accSensor != null)
  69.                         sensorManager.RegisterListener(listener, accSensor, SensorDelay.Game);
  70.                     if (magneticSensor != null)
  71.                         sensorManager.RegisterListener(listener, magneticSensor, SensorDelay.Fastest);
  72.                     enabled = true;
  73.  
  74.                     SwPrecise = new Stopwatch();
  75.                     SwPrecise.Start();
  76.                 }
  77.             }
  78.             catch (Exception ex)
  79.             {
  80.                 ExceptionLogger.LogException(ex, false, true);
  81.             }
  82.         }
  83.  
  84.         public void StopReading()
  85.         {
  86.             if (gravitySensor == null)
  87.                 return;
  88.  
  89.             try
  90.             {
  91.                 if (enabled)
  92.                 {
  93.                     // Vypne čtení z akcelerometru (voláno, když odešel z obrazovky atd.)
  94.                     sensorManager.UnregisterListener(listener);
  95.                     enabled = false;
  96.  
  97.                     SwPrecise.Stop();
  98.                 }
  99.             }
  100.             catch (Exception ex)
  101.             {
  102.                 ExceptionLogger.LogException(ex, false, true);
  103.             }
  104.         }
  105.        
  106.         public class MultiListenerImpl : Java.Lang.Object, ISensorEventListener
  107.         {
  108.             // Poslední hodnoty načtené z akcelerometru / magnetického kompasu
  109.             float[] rotationVals = new float[3];
  110.             float[] rotationMatrix = new float[16];
  111.             float[] orientation = new float[3];
  112.             float[] accVals = new float[3]; // Detekce bouchnutí do telefonu
  113.             bool calibrated = false;
  114.  
  115.             float[] magneticVals = new float[3]; // Cardboard tlačítko
  116.             List<float[]> magData = new List<float[]>();
  117.             const int MAGNETIC_COUNT = 40;
  118.             float[] offsets = new float[20];
  119.  
  120.             const float THRESHOLD = 8f; // Od kdy se to bere jako klepnutí na telefon
  121.             const float WAIT_TAP_MS = 250f; // Kolik čeká mezi jednotlivými kliknutími
  122.             long lastElapsedTime = 0;
  123.  
  124.             public void OnAccuracyChanged(Sensor sensor, SensorStatus accuracy)
  125.             {
  126.             }
  127.  
  128.             public void OnSensorChanged(SensorEvent e)
  129.             {
  130.                 Vector3 val = new Vector3(e.Values[0], e.Values[1], e.Values[2]);
  131.  
  132.                 if (e.Sensor.Type == SensorType.Gravity)
  133.                 {
  134.                     // Nastavím gravitaci (pohyb nahoru / dolů = osa Y)
  135.                     // - sensitivity: od 1 do 100 (1 je nejmenší, tj. nejmenší pohyb hlavy)
  136.                     //   výchozí: 50% (nastavení citlivosti má vliv i na osu X)
  137.                     float gravity = val.Z / (10f - ((Engine.VRSensitivity * 0.8f) / 10f));
  138.                     gravity -= 0.3f; //TODO tohle je to kalibrační nastavení na začátku, pro osu Y
  139.                     gravity = Math.Min(1f, gravity);
  140.                     gravity = Math.Max(-1f, gravity);
  141.  
  142.                     // Rovnou nastavím osu Y (je to všechno, co pro to potřebujem vědět)
  143.                     Engine.Game.VRListener.CursorPosCoef.Y = gravity;
  144.                     Engine.Game.VRSampler.SampleValue();
  145.                 }
  146.  
  147.                 if (e.Sensor.Type == SensorType.Accelerometer)
  148.                 {
  149.                     // Detekce velkého klepnutí (finálně potvrdí výběr položky)
  150.                     if ((accVals[0] == 0 && accVals[1] == 0 && accVals[2] == 0))
  151.                         CopyValues(e.Values, accVals);
  152.                     else
  153.                     {
  154.                         // Pokud už zná předchozí hodnotu, porovná ji
  155.                         float force = Math.Abs(e.Values[0] + e.Values[1] + e.Values[2] - accVals[0] - accVals[1] - accVals[2]);
  156.                         CopyValues(e.Values, accVals);
  157.  
  158.                         if (force > THRESHOLD && calibrated) //TODO doladit, aby to šlo vypínat (+ citlivost?)
  159.                         {
  160.                             // Ochrání si, aby bylo možné odklepnout jen 1x za 250 ms (nebralo to jako 2 dotyky)
  161.                             long elapsedTime = Engine.Game.VRListener.SwPrecise.ElapsedMilliseconds;
  162.                             if (elapsedTime > WAIT_TAP_MS)
  163.                             {
  164.                                 // Odklepne tlačítko
  165.                                 Engine.Game.ConfirmButtonFromVR();
  166.                                 Engine.Game.VRListener.SwPrecise.Reset();
  167.                                 Engine.Game.VRListener.SwPrecise.Start();
  168.                             }
  169.                         }
  170.                     }
  171.                 }
  172.  
  173.                 if (e.Sensor.Type == SensorType.MagneticField)
  174.                 {
  175.                     // Cardboard, detekce stisku tlačítka
  176.                     CopyValues(e.Values, magneticVals);
  177.  
  178.                     if (!(magneticVals[0] == 0 && magneticVals[1] == 0 && magneticVals[2] == 0))
  179.                     {
  180.                         if (magData.Count > MAGNETIC_COUNT)
  181.                             magData.RemoveAt(0);
  182.                         magData.Add(magneticVals);
  183.  
  184.                         if (magData.Count == MAGNETIC_COUNT)
  185.                             EvaluateCardboardButton();
  186.                     }
  187.                 }
  188.  
  189.                 if (e.Sensor.Type == SensorType.RotationVector)
  190.                 {
  191.                     // Pohyb doprava / doleva (= azimut, kam se dívá do stran)
  192.                     CopyValues(e.Values, rotationVals);
  193.                    
  194.                     SensorManager.GetRotationMatrixFromVector(rotationMatrix, rotationVals);
  195.                     SensorManager.GetOrientation(rotationMatrix, orientation);
  196.  
  197.                     // Azimut = úhel doprava / doleva, kam se díváme
  198.                     float azimuth = RadianToDegree(orientation[0]); //smoothedAzimuth); //
  199.                     if (azimuth < 0) azimuth += 360;
  200.                     if (azimuth > 360) azimuth -= 360;
  201.                     Engine.Game.VRListener.LastAzimuth = azimuth; // Podle tohohle se pak kalibruje azimut
  202.  
  203.                     if (!calibrated)
  204.                         if (!(rotationVals[0] == 0 && rotationVals[1] == 0 && rotationVals[2] == 0))
  205.                         {
  206.                             // Načetli jsme první reálné hodnoty, zkalibrujeme pohled
  207.                             Engine.Game.VRListener.CalibrateToCenter();
  208.                             calibrated = true; //TODO zkalibruje se i při dalším (příštím) spuštění?
  209.                         }
  210.  
  211.                     float sensitivityCoef = -((Engine.VRSensitivity / 100f) - 0.5f);
  212.                     float azimDiff = 40 + 35 * sensitivityCoef;
  213.  
  214.                     float min = Engine.Game.VRListener.AzimBaseAngle - azimDiff;
  215.                     float max = Engine.Game.VRListener.AzimBaseAngle + azimDiff;
  216.                     if (min < 0) min += 360;
  217.                     if (min > 360) min -= 360;
  218.                     if (max < 0) max += 360;
  219.                     if (max > 360) max -= 360;
  220.                     if (max < min) max += 360;
  221.  
  222.                     // Zjistí, kde v daném rozsahu se hodnota azimutu nachází
  223.                     // - krajní hodnoty nechá, aby byly krajní; namapuje na -1 až 1
  224.                     float azimCoef = (azimuth - min) / (max - min) * 2f - 1f;
  225.                     azimCoef = Math.Min(1f, azimCoef);
  226.                     azimCoef = Math.Max(-1f, azimCoef);
  227.                    
  228.                     // A nastavím osu X
  229.                     Engine.Game.VRListener.CursorPosCoef.X = azimCoef;
  230.                     Engine.Game.VRSampler.SampleValue();
  231.                 }
  232.  
  233.                 // Nevolám překreslení / znovu update! (smyčka kreslení VR je na tom nezávisle)
  234.                 // - pokud se nic nezměnilo, vykreslovaná textura je zapamatovaná a pořád stejná
  235.                 // - červená tečka se překresluje až přes texturu
  236.                 if (!Engine.RefreshVRDraw)
  237.                     Engine.Game.ResumeGameLoop();
  238.                 Engine.RefreshVRDraw = true;
  239.             }
  240.  
  241.             private void EvaluateCardboardButton()
  242.             {
  243.                 // Spočítá, jestli bylo Cardboard tlačítko smáčknuto (a příp. vyvolá metodu)
  244.                 // - zavoláno, pokud už máme plný buffer 40 hodnot
  245.                 float[] means = new float[2];
  246.                 float[] maximums = new float[2];
  247.                 float[] minimums = new float[2];
  248.                 float[] baseline = magData[magData.Count - 1];
  249.                 for (int i = 0; i < 2; i++)
  250.                 {
  251.                     int segmentStart = 20 * i;
  252.                     float[] offs = ComputeOffsets(segmentStart, baseline);
  253.                     means[i] = offs.Sum() / offs.Length;
  254.                     maximums[i] = offs.Concat(new[] { (-1.0F / 0.0F) }).Max();
  255.                     minimums[i] = offs.Concat(new[] { (1.0F / 0.0F) }).Min();
  256.                 }
  257.                 float min1 = minimums[0];
  258.                 float max2 = maximums[1];
  259.                 if ((min1 < 30.0F) && (max2 > 130.0F))
  260.                 {
  261.                     // Potvrdí tlačítko (podle předchozí pozice kurzoru, nastavené v SelectButonFromVR
  262.                     // z VRInputSamples)
  263.                     //Engine.Game.ConfirmButtonFromVR(); //TODO cardboard tlačítko
  264.                 }
  265.             }
  266.            
  267.             private float[] ComputeOffsets(int segmentStart, float[] baseline)
  268.             {
  269.                 // Spočítá offsety pro cardboard (jak to ulítává do stran)
  270.                 // - viz https://github.com/Zomega/Cardboard/blob/master/src/com/google/vrtoolkit/cardboard/sensors/MagnetSensor.java
  271.                 for (int i = 0; i < 20; i++)
  272.                 {
  273.                     float[] point = magData[segmentStart + i];
  274.                     float[] o = { point[0] - baseline[0], point[1] - baseline[1], point[2] - baseline[2] };
  275.                     float magnitude = (float)Math.Sqrt(o[0] * o[0] + o[1] * o[1] + o[2] * o[2]);
  276.                     offsets[i] = magnitude;
  277.                 }
  278.                 return offsets;
  279.             }
  280.            
  281.             private void CopyValues(IList<float> list, float[] vals)
  282.             {
  283.                 // Zkopíruje hodnoty do zadaného pole
  284.                 for (int i = 0; i < 3; i++)
  285.                     vals[i] = list[i];
  286.             }
  287.             private float RadianToDegree(float angle)
  288.             {
  289.                 return (float)(angle * (180.0 / Math.PI));
  290.             }
  291.         }
  292.  
  293.     }
  294. }
Advertisement
Add Comment
Please, Sign In to add comment