// World Political Map - Globe Edition for Unity - Main Script // Created by Ramiro Oliva (Kronnect) // Don't modify this script - changes could be lost if you upgrade to a more recent version of WPM using UnityEngine; using System; namespace WPM { public partial class WorldMapGlobe : MonoBehaviour { float currentYaw, currentPitch; bool pitchIsMaxed; Transform pitchTransform, yawTransform; Transform _pivotTransform; float dragSensibility { get { return _navigationMode == NAVIGATION_MODE.EARTH_ROTATES ? _mouseDragSensitivity : _cameraRotationSensibility; } } float zoomSpeed { get { return _navigationMode == NAVIGATION_MODE.EARTH_ROTATES ? _mouseWheelSensitivity : _cameraZoomSpeed; } } public Transform pivotTransform { get { if (_pivotTransform == null) { CheckCameraPivot(); } return _pivotTransform; } } void OrbitStart() { currentPitch = pitch; currentYaw = yaw; } void OrbitReset() { orbitRotateToActive = false; } void CheckCameraPivot() { Camera cam = mainCamera; if (cam == null) return; Transform camParent = cam.transform.parent; if (_enableOrbit) { if (camParent == null || !camParent.name.Equals("Yaw")) { // add pivot GameObject pivot = new GameObject("CameraPivot"); _pivotTransform = pivot.transform; if (camParent != null) { pivotTransform.SetParent(camParent, false); } pivot.transform.position = cam.transform.position; pivot.transform.rotation = cam.transform.rotation; // add pitch GameObject pitchPivot = new GameObject("Pitch"); pitchTransform = pitchPivot.transform; pitchTransform.SetParent(pivotTransform, false); // add yaw GameObject yawPivot = new GameObject("Yaw"); yawTransform = yawPivot.transform; yawTransform.SetParent(pitchTransform, false); // reparent cam cam.transform.SetParent(yawTransform, false); } else { yawTransform = cam.transform.parent; pitchTransform = yawTransform.parent; _pivotTransform = pitchTransform.parent; } } else { if (camParent != null && camParent.name.Equals("Yaw")) { Transform grandParent = camParent.transform.parent; if (grandParent != null && grandParent.name.Equals("Pitch")) { Transform root = grandParent.parent; if (root != null) { cam.transform.SetParent(root.parent, true); } DestroyImmediate(root.gameObject); } } pitchTransform = yawTransform = null; _pivotTransform = cam.transform; } } void RotateCameraAroundAxis(float angle) { angle *= Time.deltaTime * 60f; Vector3 axis = (transform.position - pivotTransform.position).normalized; if (_navigationMode == NAVIGATION_MODE.EARTH_ROTATES) { transform.Rotate(axis, angle, Space.World); } else { pivotTransform.Rotate(axis, angle, Space.World); } } void PerformZoomInOut(float distance) { Vector3 vector = zoomDir * (Vector3.Distance(transform.position, pivotTransform.position) * distance * zoomSpeed * Time.deltaTime * 60f); if (_zoomMode == ZOOM_MODE.CAMERA_MOVES) { pivotTransform.position = pivotTransform.position - vector; } else { transform.position = transform.position + vector; } } float GetCameraMinDistance(Camera cam) { return _zoomMinDistance + radius + (cam.nearClipPlane + 0.001f); } // this version fixes camera plane crossing globe surface when zooming in too much bool CheckCamMinMaxDistance(Camera cam) { Vector3 v = pivotTransform.position - transform.position; float camDist = v.magnitude; Vector3 dir = new Vector3(v.x / camDist, v.y / camDist, v.z / camDist); float minDist = GetCameraMinDistance(cam); float maxDist = _zoomMaxDistance + radius + cam.nearClipPlane; if (_zoomMode == ZOOM_MODE.CAMERA_MOVES) { if (camDist < minDist) { _pivotTransform.position += dir * (minDist - camDist); return false; } else if (camDist > maxDist) { _pivotTransform.position += dir * (maxDist - camDist); return false; } } else { if (camDist < minDist) { transform.position += dir * (camDist - minDist); return false; } else if (camDist > maxDist) { transform.position += dir * (camDist - maxDist); return false; } } return true; } void KeepCameraStraight() { if (currentYaw != 0) return; if (_keepStraight && !flyToActive) { if (_navigationMode == NAVIGATION_MODE.EARTH_ROTATES) { StraightenGlobe(SMOOTH_STRAIGHTEN_ON_POLES, true); } else { if (currentPitch != 0) { pivotTransform.rotation = GetCameraStraightLookRotation(); } else { pivotTransform.rotation = Quaternion.Lerp(pivotTransform.rotation, GetCameraStraightLookRotation(), 100f * Time.deltaTime); } } } } /// /// Manages two finger zoom and rotation /// /// Impulse for zooming float CheckTwoFingerZoomGestures(Camera cam) { // Check pinch gesture if (input.touchCount < 2) return 0; Touch touchZero = input.GetTouch(0); Touch touchOne = input.GetTouch(1); Vector2 touchZeroDelta = touchZero.deltaPosition.normalized; Vector2 touchOneDelta = touchOne.deltaPosition.normalized; float pinch = Vector2.Dot(touchZeroDelta, touchOneDelta); if (pinch < -0.5f) { pinchZooming = true; // zooming or rotating? Vector2 center = (touchZero.position + touchOne.position) * 0.5f; Vector2 touchZeroToCenter = (center - touchZero.position).normalized; Vector2 touchOneToCenter = (center - touchOne.position).normalized; float dtZero = Mathf.Abs(Vector2.Dot(touchZeroToCenter, touchZeroDelta)); float dtOne = Mathf.Abs(Vector2.Dot(touchOneToCenter, touchOneDelta)); if (dtZero > 0.8f || dtOne > 0.8f) { // Zoom in/out // Find the position in the previous frame of each touch. Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition; Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition; // Find the magnitude of the vector (the distance) between the touches in each frame. float prevTouchDeltaMag = (touchZeroPrevPos - touchOnePrevPos).magnitude; float touchDeltaMag = (touchZero.position - touchOne.position).magnitude; // Find the difference in the distances between each frame. float deltaMagnitudeDiff = prevTouchDeltaMag - touchDeltaMag; deltaMagnitudeDiff = ApplyDragThreshold(deltaMagnitudeDiff, _mouseDragThreshold); // Pass the delta to the wheel accel if (deltaMagnitudeDiff != 0) { wheelAccel = 0; UpdateCursorLocation(); return deltaMagnitudeDiff * 0.001f; } } else { // Rotate with fingers Vector2 v2 = touchZero.position - touchOne.position; if (v2.sqrMagnitude > 10 * 10) { float newAngle = Mathf.Atan2(v2.y, v2.x) * Mathf.Rad2Deg; if (oldPinchRotationAngle != 999) { float rotAngle = Mathf.DeltaAngle(newAngle, oldPinchRotationAngle); if (_navigationMode == NAVIGATION_MODE.EARTH_ROTATES) { rotAngle *= -1f; } RotateCameraAroundAxis(rotAngle); } oldPinchRotationAngle = newAngle; } } } return 0; } void PerformDragDamping(Camera cam) { float t = (Time.time - dragDampingStart) / (dragDampingDuration + 0.001f); t = 1f - t * t; if (t < 0) { t = 0; dragDampingStart = 0; } else if (t > 1f) { t = 1f; } if (_navigationMode == NAVIGATION_MODE.EARTH_ROTATES) { transform.Rotate(cam.transform.up, dragDirection.x * t, Space.World); Vector3 axisY = Vector3.Cross(transform.position - cam.transform.position, cam.transform.up); transform.Rotate(axisY, dragDirection.y * t, Space.World); } else { if (_rotationAxisAllowed == ROTATION_AXIS_ALLOWED.X_AXIS_ONLY) { pivotTransform.RotateAround(transform.position, cam.transform.up, -dragDirection.x * t); } else { RotateAround(pivotTransform, transform.position, cam.transform.up, -dragDirection.x * t); RotateAround(pivotTransform, transform.position, cam.transform.right, dragDirection.y * t); } } } void CheckAngleConstraint() { // Check constraint around position Vector3 oldCamPos = pivotTransform.position; Vector3 globePos = transform.position; transform.position = Misc.Vector3zero; pivotTransform.position -= globePos; Vector3 camPos = pivotTransform.position; Ray ray = new Ray(camPos, pivotTransform.forward); Vector3 hitPoint; if (GetGlobeIntersection(ray, out hitPoint)) { Vector3 contraintWPos = transform.TransformPoint(constraintPosition); Vector3 axis = Vector3.Cross(contraintWPos, hitPoint); float angleDiff = SignedAngleBetween(contraintWPos, hitPoint, axis); if (Mathf.Abs(angleDiff) > constraintPositionAngle + 0.0001f) { if (angleDiff > 0) { angleDiff = constraintPositionAngle - angleDiff; } else { angleDiff -= constraintPositionAngle; } if (_navigationMode == NAVIGATION_MODE.CAMERA_ROTATES) { axis = Vector3.Cross(contraintWPos - camPos, hitPoint - camPos); Vector3 prevUp = pivotTransform.up; pivotTransform.Rotate(axis, angleDiff, Space.World); pivotTransform.LookAt(camPos + pivotTransform.forward, prevUp); // keep straight } else { axis.z = 0; transform.Rotate(axis, -angleDiff, Space.World); } dragDampingStart = 0; } } pivotTransform.position = oldCamPos; transform.position = globePos; } void CheckLatitudeConstraint() { Vector3 hitPos; Ray ray = new Ray(pivotTransform.position, pivotTransform.forward); if (!GetGlobeIntersection(ray, out hitPos)) return; hitPos = transform.InverseTransformPoint(hitPos); float sy = hitPos.y * 2f; float maxLat = Mathf.Sin(constraintLatitudeMaxAngle * Mathf.Deg2Rad); float minLat = Mathf.Sin(constraintLatitudeMinAngle * Mathf.Deg2Rad); float correction; if (sy > maxLat) { correction = maxLat - sy; } else if (sy < minLat) { correction = minLat - sy; } else { return; } float corr = Mathf.Asin(correction) * Mathf.Rad2Deg; if (_navigationMode == NAVIGATION_MODE.CAMERA_ROTATES) { Vector3 axis = Vector3.Cross(transform.up, _pivotTransform.forward); Vector3 prevUp = _pivotTransform.transform.up; RotateAround(pivotTransform, transform.position, axis, corr); pivotTransform.LookAt(transform.position, prevUp); } else { Vector3 axis = Vector3.Cross(transform.up, _pivotTransform.forward); transform.Rotate(axis, -corr, Space.World); } dragDampingStart = 0; } void CheckRotationKeys() { bool pressed = false; Vector3 dragKeyVert = Misc.Vector3zero; if (input.GetKey(KeyCode.W)) { dragKeyVert = Misc.Vector3down; pressed = true; } else if (input.GetKey(KeyCode.S)) { dragKeyVert = Misc.Vector3up; pressed = true; } Vector3 dragKeyHoriz = Misc.Vector3zero; if (input.GetKey(KeyCode.A)) { dragKeyHoriz = Misc.Vector3right; pressed = true; } else if (input.GetKey(KeyCode.D)) { dragKeyHoriz = Misc.Vector3left; pressed = true; } if (pressed) { dragDirection = dragKeyVert + dragKeyHoriz; float distFactor = Mathf.Min((Vector3.Distance(pivotTransform.position, transform.position) - radius) / radius, 1f); dragDirection *= distFactor * dragSensibility; if (_dragConstantSpeed) { dragDirection *= 18f; dragDampingStart = Time.time + dragDampingDuration; } else { dragDampingStart = Time.time; } } } void PerformDragOnScreenEdges() { bool onEdges = IsPointerOnScreenEdges(); if (onEdges) { if (!mouseStartedDragging) { mouseDragStart = input.mousePosition; mouseStartedDragging = true; } Vector2 delta = ((Vector2)input.mousePosition - new Vector2(Screen.width, Screen.height) * 0.5f).normalized; DragTowards(delta); } } public virtual void DragTowards(Vector2 dragDirection) { Camera cam = mainCamera; float distFactor = Mathf.Min((GetCameraDistance() - radius) / radius, 1f); if (_rotationAxisAllowed == ROTATION_AXIS_ALLOWED.X_AXIS_ONLY) { dragDirection.y = 0; } else { dragDirection.y = ApplyDragThreshold(dragDirection.y, _mouseDragThreshold); } if (_rotationAxisAllowed == ROTATION_AXIS_ALLOWED.Y_AXIS_ONLY) { dragDirection.x = 0; } else { dragDirection.x = ApplyDragThreshold(dragDirection.x, _mouseDragThreshold); } dragDirection *= distFactor * _dragOnScreenEdgesSpeed * Time.deltaTime * 60f; if (dragDirection.x != 0 || dragDirection.y != 0) { hasDragged = true; if (_navigationMode == NAVIGATION_MODE.EARTH_ROTATES) { transform.Rotate(cam.transform.up, dragDirection.x, Space.World); Vector3 axisY = Vector3.Cross(transform.position - cam.transform.position, cam.transform.up); transform.Rotate(axisY, dragDirection.y, Space.World); } else { if (_rotationAxisAllowed == ROTATION_AXIS_ALLOWED.X_AXIS_ONLY) { pivotTransform.RotateAround(transform.position, cam.transform.up, -dragDirection.x); } else { RotateAround(pivotTransform, transform.position, cam.transform.up, -dragDirection.x); RotateAround(pivotTransform, transform.position, cam.transform.right, dragDirection.y); } } } StopAnyNavigation(); } void PerformClickAndThrow() { float t = 1f - 60f * Time.deltaTime * _dragConstantSpeedDampingMultiplier; if (t < 0) t = 0; dragAngle *= t; hasDragged = true; if (_navigationMode == NAVIGATION_MODE.EARTH_ROTATES) { Vector3 angles = Quaternion.AngleAxis(dragAngle, dragAxis).eulerAngles; transform.Rotate(angles); } else { RotateAround(pivotTransform, transform.position, dragAxis, -dragAngle); } } void PerformOrbit(Camera cam) { if (yawTransform == null) return; Vector3 targetPosition = GetOrbitTargetPositionWS(); float camDistanceToTargetPosition = Vector3.Distance(pivotTransform.position, targetPosition); bool triggerRotateEnds = false; if (_enableOrbit) { if (orbitRotateToActive) { float t = (Time.time - orbitRotateToStartTime) / orbitRotateToDuration; if (t >= 1f) { t = 1f; triggerRotateEnds = true; } t = Mathf.SmoothStep(0, 1, t); yaw = Mathf.Lerp(orbitRotateToYawStart, orbitRotateToYawEnd, t); pitch = Mathf.Lerp(orbitRotateToPitchStart, orbitRotateToPitchEnd, t); } if (_pitchAdjustByDistance) { float distanceLerp = (camDistanceToTargetPosition - _pitchMinDistanceTilt) / (_pitchMaxDistanceTilt - _pitchMinDistanceTilt); distanceLerp = Mathf.Clamp01(distanceLerp); float maxPitchByDistance = Mathf.Lerp(maxPitch, 0, distanceLerp); if (pitch > maxPitchByDistance || (_orbitTiltOnZoomIn && zoomDistance < 0 && pitchIsMaxed)) { pitch = maxPitchByDistance; } pitchIsMaxed = pitch == maxPitchByDistance; } pitch = Mathf.Clamp(pitch, 0, _maxPitch); } float enabledPitch = _enableOrbit ? _pitch : 0; float enabledYaw = _enableOrbit ? _yaw : 0; if (Application.isPlaying) { currentPitch = Mathf.Lerp(currentPitch, enabledPitch, Time.deltaTime * _orbitSmoothSpeed); if (enabledPitch == 0) { const float EPSILON = 0.001f; if (Mathf.Abs(currentPitch - enabledPitch) < EPSILON) { currentPitch = 0; } } currentYaw = Mathf.Lerp(currentYaw, enabledYaw, Time.deltaTime * _orbitSmoothSpeed); if (enabledYaw == 0) { const float EPSILON = 0.001f; if (Mathf.Abs(currentYaw - enabledYaw) < EPSILON) { currentYaw = 0; } } } else { currentYaw = enabledYaw; currentPitch = enabledPitch; } pitchTransform.localRotation = Quaternion.Euler(-currentPitch, 0, 0); cam.transform.localRotation = Quaternion.identity; Vector3 pos = targetPosition - cam.transform.forward * camDistanceToTargetPosition; cam.transform.position = pos; Vector3 orbitAxis = (pivotTransform.position - targetPosition).normalized; RotateAround(cam.transform, targetPosition, orbitAxis, currentYaw); // If camera gets below minimum distance to globe, shift it above //pos = cam.transform.position; //float camDist = Vector3.Distance(transform.position, pos); //float minDist = GetCameraMinDistance(cam); //if (camDist < minDist) { // Vector3 outDir = (pos - transform.position).normalized; // cam.transform.position = transform.position + outDir * minDist; // cam.transform.LookAt(targetPosition, cam.transform.up); //} if (triggerRotateEnds) { StopRotateTo(true); } } Vector3 GetOrbitTargetPositionWS() { if (_orbitTarget != null) { return _orbitTarget.transform.position; } Vector3 orbitAxis = (pivotTransform.position - transform.position).normalized; float elevation = targetElevation * (radius / EARTH_RADIUS_KM); return transform.position + orbitAxis * (radius + elevation); } bool orbitRotateToActive, orbitRotateToComplete; float orbitRotateToYawStart, orbitRotateToYawEnd; float orbitRotateToPitchStart, orbitRotateToPitchEnd; float orbitRotateToStartTime, orbitRotateToDuration; public CallbackHandler OrbitRotateTo(float targetYaw, float targetPitch, float duration = 0.5f) { yaw %= 360; currentYaw %= 360; targetYaw %= 360; if (targetYaw - yaw > 180) { targetYaw -= 360; } if (targetYaw - yaw < -180) { targetYaw += 360; } orbitRotateToYawStart = yaw; orbitRotateToYawEnd = targetYaw; orbitRotateToPitchStart = pitch; orbitRotateToPitchEnd = targetPitch; orbitRotateToStartTime = Time.time; orbitRotateToDuration = duration; orbitRotateToActive = true; orbitRotateToComplete = false; if (OnOrbitRotateStart != null) { OnOrbitRotateStart(); } return new CallbackHandlerForOrbitRotateTo(this); } /// /// Used internally to rotate the globe or the camera during FlyTo operations. Use FlyTo method. /// void NavigateToDestination() { float delta, t; if (flyToDuration == 0) { delta = flyToDuration; t = 1; } else { delta = (Time.time - flyToStartTime); t = Mathf.SmoothStep(0, 1, delta / flyToDuration); } if (flyToMode == NAVIGATION_MODE.EARTH_ROTATES || _earthInvertedMode) { if (transform.position != flyToGlobeStartPosition) { flyToGlobeStartPosition = transform.position; flyToEndQuaternion = GetQuaternion(flyToEndDestination.normalized); } // Apply new zoom level if (flyToCameraDistanceEnabled) { SetCameraDistance(LerpCameraDistance(t)); } transform.rotation = Quaternion.Lerp(flyToStartQuaternion, flyToEndQuaternion, t); } else { // Apply new zoom level if (flyToCameraDistanceEnabled) { SetCameraDistance(LerpCameraDistance(t)); } float camDistance = Vector3.Distance(pivotTransform.position, transform.position); pivotTransform.position = transform.position + Vector3.Lerp(flyToCameraStartPosition, flyToCameraEndPosition, t).normalized * camDistance; Vector3 upVector = Vector3.Lerp(flyToCameraStartUpVector, transform.up, t); pivotTransform.transform.LookAt(transform.position, upVector); } if (delta >= flyToDuration) { StopNavigation(true); } } /// /// Used internally to rotate the globe or the camera during FlyTo operations. Use FlyTo method. /// void ZoomToDestination() { float delta, t; Camera cam = mainCamera; if (zoomToDuration == 0) { delta = zoomToDuration; t = 1; } else { delta = (Time.time - zoomToStartTime); t = Mathf.SmoothStep(0, 1, delta / zoomToDuration); } float distance = Mathf.Lerp(zoomToStartDistance, zoomToEndDistance, t); SetCameraDistance(distance); if (delta >= zoomToDuration) { StopZooming(true); } } /// /// Stops current FlyTo() operation /// void StopNavigation(bool hasCompleted) { if (!flyToActive) return; flyToActive = false; flyToComplete = hasCompleted; if (hasCompleted && OnFlyEnd != null) { OnFlyEnd(GetCurrentMapLocation()); } } /// /// Stops current RotateTo() operation /// void StopRotateTo(bool hasCompleted) { if (!orbitRotateToActive) return; orbitRotateToActive = false; orbitRotateToComplete = hasCompleted; if (hasCompleted && OnOrbitRotateEnd != null) { OnOrbitRotateEnd(); } } /// /// Stops current ZoomTo operation /// void StopZooming(bool hasCompleted) { if (!zoomToActive) return; zoomToActive = false; zoomComplete = hasCompleted; if (hasCompleted && OnZoomEnd != null) { OnZoomEnd(GetZoomLevel()); } } } }