Ejemplo n.º 1
0
 /// <summary>
 /// Transforms an XYZ point in world space to a <see cref="LatLon"/>.
 /// </summary>
 public static LatLon TransformWorldPointToLatLon(this MapRenderer mapRenderer, Vector3 pointInWorldSpace)
 {
     return(mapRenderer.TransformWorldPointToMercator(pointInWorldSpace).ToLatLon());
 }
        private void Update()
        {
            var touchCount = Input.touchCount;

            if (touchCount == 0)
            {
                if (_isInteracting)
                {
                    _isInteracting = false;
                    MapInteractionController.OnInteractionEnded?.Invoke();
                }

                _initialTouchPointDelta = 0.0f;
                _lastTouchCount         = touchCount;

                return;
            }

            var touch0          = Input.GetTouch(0);
            var touchPoint      = touch0.position;
            var touchPointDelta = 0.0f; // Used when there are two touch points (for pinch/zoom).

            // A single touch point is a pan, a double-tap, or a tap-and-hold.
            if (touchCount == 1)
            {
                // Disable zoom with two touch points.
                _initialTouchPointDelta = 0.0f;

                // Check for a double tap.
                if (touch0.phase == TouchPhase.Ended && touch0.tapCount > 1)
                {
                    // Do a double tap and early out.
                    var ray = Camera.ScreenPointToRay(touchPoint);
                    if (MapRenderer.Raycast(ray, out var hitInfo))
                    {
                        MapInteractionController.DoubleTapZoom(hitInfo.Location, 1.0f);
                    }

                    _tapAndHoldBeginTime = float.MaxValue; // Reset tap and hold.
                    _lastTouchCount      = 0;              // Reset interactions.

                    return;
                }

                // Check for tap and hold.
                if (_isInteracting)
                {
                    // If we're in the middle of an interaction (in this case, a pan), tap and hold doesn't apply.
                    if (_tapAndHoldBeginTime != float.MaxValue)
                    {
                        _tapAndHoldBeginTime = float.MaxValue;
                    }
                }
                else
                {
                    // Track tap and hold.
                    if (touch0.phase == TouchPhase.Began)
                    {
                        _tapAndHoldBeginTime = Time.time;
                    }
                    else if (!HasInitialPointMovedPastThreshold(touchPoint))
                    {
                        // The touch point has not moved enough to start an interaction, so we are in a stationary tap.
                        // Fire off the TapAndHold event once we exceed the hold threshold.
                        if ((Time.time - _tapAndHoldBeginTime) >= TapAndHoldThresholdInSeconds)
                        {
                            var ray = Camera.ScreenPointToRay(touchPoint);
                            if (MapRenderer.Raycast(ray, out var hitInfo))
                            {
                                MapInteractionController.OnTapAndHold?.Invoke(hitInfo.Location);

                                // Reset so we don't continually fire off TapAndHold events on subsequent frames where touch point continues
                                // to be stationary.
                                _tapAndHoldBeginTime = float.MaxValue;
                            }
                        }
                    }

                    // Reset tap and hold state if this touch has been ended or cancelled.
                    if (touch0.phase == TouchPhase.Canceled || touch0.phase == TouchPhase.Ended)
                    {
                        _tapAndHoldBeginTime = float.MaxValue;
                    }
                }
            }
            else // Touch count > 1, start tracking pinch.
            {
                // Touch delta can be calculated from first and second points.
                touchPointDelta = (Input.GetTouch(1).position - touch0.position).magnitude;

                // Touch point will be average between first and second.
                touchPoint += Input.GetTouch(1).position;
                touchPoint *= 0.5f;

                _tapAndHoldBeginTime = float.MinValue; // Reset tap and hold.
            }

            // In case a second touch point is added or removed, or more generally, if this is the first frame of any touch interaction,
            // we'll need to reset the target coordiante... This involves raycasting the map to see where the touch initally hits.
            var resetInteractionTarget = _lastTouchCount != touchCount;

            if (resetInteractionTarget)
            {
                var ray = Camera.ScreenPointToRay(touchPoint);
                if (MapRenderer.Raycast(ray, out var hitInfo))
                {
                    // We have a hit, so set up some initial variables.
                    _interactionTargetCoordinate = hitInfo.Location.LatLon.ToMercatorCoordinate();
                    _interactionTargetAltitude   = hitInfo.Location.AltitudeInMeters;
                    _initialTouchPointDelta      = 0.0f;
                    _initialTouchPoint           = touchPoint;
                    _lastTouchCount = touchCount;
                }
                else
                {
                    // The touch point didn't hit the map, so end any active interactions.
                    if (_isInteracting)
                    {
                        _lastTouchCount = 0;
                        _isInteracting  = false;
                        MapInteractionController.OnInteractionEnded?.Invoke();
                    }
                }
            }
            else
            {
                // If we've made it to this case, the touch point has hit the map and we're either waiting for a movement
                // to occur which exceeds the touch interaction thresholds, or a movement is actively happening and we
                // need to update the map's state (i.e., the center and zoom properties).

                var touchPointDeltaToInitialDeltaRatio = touchPointDelta / _initialTouchPointDelta;

                if (!_isInteracting)
                {
                    // Check if the initial interaction touch points have moved past a threshold to consider this a pan or zoom.
                    if (touchPointDeltaToInitialDeltaRatio > 1.05 || HasInitialPointMovedPastThreshold(touchPoint))
                    {
                        _isInteracting = true;
                        MapInteractionController.OnInteractionStarted?.Invoke();
                    }
                }

                if (_isInteracting)
                {
                    // We're inside an intersection, handle pinch/zoom first.
                    if (touchPointDelta > 0.0f)
                    {
                        var isInitialZoomFrame = _initialTouchPointDelta == 0.0;
                        if (isInitialZoomFrame)
                        {
                            _initialTouchPointDelta        = touchPointDelta;
                            _initialMapDimensionInMercator = Mathf.Pow(2, MapRenderer.ZoomLevel - 1);
                        }
                        else
                        {
                            var newMapDimensionInMercator = touchPointDeltaToInitialDeltaRatio * _initialMapDimensionInMercator;
                            var newZoomLevel = Math.Log(newMapDimensionInMercator) / Math.Log(2) + 1;
                            MapRenderer.ZoomLevel = Mathf.Clamp((float)newZoomLevel, MapRenderer.MinimumZoomLevel, MapRenderer.MaximumZoomLevel);
                        }
                    }

                    // Handle panning last. Zoom is handled above in the pinch case, and it should be updated prior to panning.
                    {
                        var ray = Camera.ScreenPointToRay(touchPoint);
                        MapInteractionController.PanAndZoom(ray, _interactionTargetCoordinate, _interactionTargetAltitude, 0.0f);
                    }
                }
            }
        }
 /// <summary>
 /// Computes approximate scale of the map relative to Unity's world space, i.e. the number of real-world meters in the map
 /// per a single unit in Unity's world space. Uses the MapRenderer's center as the reference location.
 /// </summary>
 /// <remarks>
 /// Scale can change based on the latitude of the MapRenderer's center location due to distortion of the Mercator projection.
 /// <br/>
 /// The resulting scale will be inaccurate if a non-uniform scaling is applied to the MapRenderer's transform.
 /// </remarks>
 public static double ComputeUnityToMapScaleRatio(this MapRenderer mapRenderer)
 {
     return(ComputeUnityToMapScaleRatio(mapRenderer, mapRenderer.Center));
 }
        private void Update()
        {
            var mousePosition = Input.mousePosition;

            var zoomSpeed = 0.0f;

            if (IsPixelOverCamera(mousePosition) && !IsPointerOverGameObject())
            {
                zoomSpeed = 10 * _zoomSpeed * Input.GetAxis("Mouse ScrollWheel");
            }

            if (_isMouseDown && (_isInteracting || zoomSpeed > 0 || HasInitialPointMovedPastThreshold(mousePosition)))
            {
                // If this is the first frame of the interaction, invoke the interaction started event.
                if (!_isInteracting)
                {
                    _isInteracting = true;
                    MapInteractionController.OnInteractionStarted?.Invoke();
                }

                var ray = Camera.ScreenPointToRay(mousePosition);
                MapInteractionController.PanAndZoom(ray, _interactionTargetCoordinate, _interactionTargetAltitude, zoomSpeed);
            }
            else
            {
                // Zoom may still occur via the scroll wheel even if the mouse isn't down.
                if (zoomSpeed != 0)
                {
                    // If this is the first frame of the interaction, invoke the interaction started event.
                    if (!_isInteracting)
                    {
                        _isInteracting = true;
                        MapInteractionController.OnInteractionStarted?.Invoke();
                    }

                    var ray = Camera.ScreenPointToRay(mousePosition);
                    MapInteractionController.Zoom(zoomSpeed, ray, true);
                }
                else
                {
                    // There is no zoom interaction, so if this is the first frame of no interaction happening, invoke the interaction ended event.
                    if (_isInteracting)
                    {
                        _isInteracting = false;
                        MapInteractionController.OnInteractionEnded?.Invoke();
                    }
                }

                // If an interaction (i.e. a scroll wheel zoom) isn't occurring, check for tap and hold.
                if (!_isInteracting)
                {
                    if ((Time.time - _lastMouseDownTime) > TapAndHoldThresholdInSeconds)
                    {
                        var ray = Camera.ScreenPointToRay(mousePosition);
                        if (MapRenderer.Raycast(ray, out var hitInfo))
                        {
                            MapInteractionController.OnTapAndHold.Invoke(hitInfo.Location);
                            _lastMouseDownTime = float.MaxValue;
                        }
                    }
                }
            }

            if (_isInteracting)
            {
                // If we were panning or zooming the map, reset the last mouse up/down times used to track double tap and tap and hold.
                _lastMouseDownTime = float.MaxValue;
                _lastMouseUpTime   = float.MinValue;
            }
        }