/// <summary> /// Change orientation of bubble surface or tube /// </summary> /// <param name="e">Event arguments</param> private void ChangeLevelOrientation(DeviceOrientationChangedEventArgs e) { _bubbleSpeed = new Simple3DVector(); if (DeviceOrientationHelper.IsFlat(e.CurrentOrientation)) { SurfaceShown = true; DeviceOrientationInfo priorDoi = DeviceOrientationHelper.GetDeviceOrientationInfo(e.PreviousOrientation); _bubblePosition = new Simple3DVector(priorDoi.NormalGravityVector.X, priorDoi.NormalGravityVector.Y, 0); } else { DeviceOrientationInfo doi = DeviceOrientationHelper.GetDeviceOrientationInfo(e.CurrentOrientation); if (DeviceOrientationHelper.IsFlat(e.PreviousOrientation) || (e.PreviousOrientation == DeviceOrientation.Unknown)) { MoveLevel.Angle = doi.AngleOnXYPlan; _bubblePosition = new Simple3DVector(); SurfaceShown = false; } double accumulatedLoops = MoveLevel.Angle / 360; accumulatedLoops = Math.Floor(accumulatedLoops + 0.5); _tubeRotationAnimation.To = 360 * accumulatedLoops + doi.AngleOnXYPlan; if ((_tubeRotationAnimation.To - MoveLevel.Angle) > 180) { _tubeRotationAnimation.To -= 360; } if ((_tubeRotationAnimation.To - MoveLevel.Angle) < -180) { _tubeRotationAnimation.To += 360; } _tubeRotationStoryboard.Begin(); _bubbleDirection = doi.HorizontalAxisPolarity; } _levelOrientation = e.CurrentOrientation; }
/// <summary> /// Update the tube level visuals /// </summary> private void UpdateTubeBubble(AccelerometerHelperReadingEventArgs e) { // ANGLE TEXT // ---------- // Use filtered accelemeter data (st5ady) double x = e.OptimallyFilteredAcceleration.X; double y = e.OptimallyFilteredAcceleration.Y; double horizontalAcceleration; double verticalAcceleration; // Choose appropriate axis based on orientation of level horizontalAcceleration = _bubbleDirection * (DeviceOrientationHelper.IsPortrait(_levelOrientation) ? x : y); verticalAcceleration = _bubbleDirection * (DeviceOrientationHelper.IsPortrait(_levelOrientation) ? y : -x); // rotate XY plan by -90 // Convert acceleration vector coordinates to Angles and Magnitude // Update reading on screen of instant inclination assuming steady device (gravity = measured acceleration) double magnitudeXYZ = e.OptimallyFilteredAcceleration.Magnitude; double angle = 0.0; if (magnitudeXYZ != 0.0) { angle = Math.Asin(horizontalAcceleration / magnitudeXYZ) * 180.0 / Math.PI; } _angle = angle; // Display angles as if they were buoyancy force instead of gravity (opposite) since it is // more natural to match targeted bubble location _angleText = String.Format("{0:0.0}°", _angle); // BUBBLE POSITION // --------------- // ---------------------------------------------------------- // For simplicity we are approximating that the bubble experiences a lateral attraction force // proportional to the distance to its target location (top of the glass based on inclination). // We will neglect the vertical speed of the bubble since the radius of the glass curve is much greater // than the radius of radius of usable glass surface // Assume tube curve has a 1m radius // Destination position is x and y // Current position is _bubblePosition.X (use only one dimension) // Update Buoyancy double lateralAcceleration = (horizontalAcceleration - _bubblePosition.X) * BuoyancyCoef * StandardGravityInMetric; // Update drag: double drag = _bubbleSpeed.X * (-ViscosityCoef); // Update speed: lateralAcceleration += drag; lateralAcceleration /= AccelerometerRefreshRate; // impulse _bubbleSpeed += new Simple3DVector(lateralAcceleration, 0, 0); // Update position _bubblePosition += (_bubbleSpeed / AccelerometerRefreshRate); double edgeRadius = Math.Sin(EdgeGlassAngle); // Get resulting direction and magnitude of bubble position given X double magnitudeFlat = Math.Abs(_bubblePosition.X); double direction = Math.Sign(_bubblePosition.X); bool atEdge = false; if (magnitudeFlat > edgeRadius) { // Bubble reaches the edge magnitudeFlat = edgeRadius; // lossy bouncing when reaching edges // change direction _bubbleSpeed *= -EdgeBouncingLossCoef; // limit bubble position to edge _bubblePosition = new Simple3DVector(magnitudeFlat * direction, 0, 0); atEdge = true; } // Calculate position of bubble MoveBubble.X = (_bubblePosition.X / edgeRadius) * ((UsableLateralAmplitude / 2) - (BubbleCanvas.Width / 4)); // Change bubble shape double stretchRatio; if (atEdge) { TubeBubbleSkew.AngleX = 0; stretchRatio = 1.0 + (Math.Abs(Math.Sin(angle / 180.0 * Math.PI)) - Math.Sin(EdgeGlassAngle)) * AngleBasedTubeBubbleStrechingAtEdgeCoef; TubeBubbleScale.ScaleX = 1.0 / stretchRatio; TubeBubbleScale.ScaleY = SideWayBubbleAtEdgeBoostOnYAxis * stretchRatio; } else { double horizontalSpeed = Math.Abs(_bubbleSpeed.X); double horizontalDirection = Math.Sign(_bubbleSpeed.X); // Stretch is proportional to horizontal speed stretchRatio = Math.Min(horizontalSpeed * SpeedBubbleStrechingCoef, MaximumBubbleXYStretchRatio - 1.0) + 1.0; if (stretchRatio < MinimumStretchRatio) { stretchRatio = MinimumStretchRatio; } TubeBubbleSkew.AngleX = MaximumTubeBubbleSkew * (stretchRatio - 1.0) / (MaximumBubbleXYStretchRatio - 1.0) * horizontalDirection; // Stretch is also proportional to buoyancy stretchRatio -= TubeBubbleStretchBuoyancyCoef * ((verticalAcceleration / magnitudeXYZ) - MedianVerticalAcceleration); if (stretchRatio < MinimumStretchRatio) { stretchRatio = MinimumStretchRatio; } TubeBubbleScale.ScaleX = 1.0 / stretchRatio; TubeBubbleScale.ScaleY = MaximumBubbleXYStretchRatio * stretchRatio; } }