package com.fdangelo.circleworld.universeview; import com.badlogic.gdx.Application.ApplicationType; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.graphics.Camera; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.scenes.scene2d.Actor; import com.fdangelo.circleworld.GameLogic; import com.fdangelo.circleworld.GameLogicState; import com.fdangelo.circleworld.universeview.objects.AvatarInputMode; import com.fdangelo.circleworld.universeview.objects.AvatarViewInput; import com.fdangelo.circleworld.universeview.objects.InputAreas; import com.fdangelo.circleworld.universeview.objects.ShipInputMode; import com.fdangelo.circleworld.universeview.objects.ShipViewInput; import com.fdangelo.circleworld.universeview.tilemap.PlanetView; import com.fdangelo.circleworld.utils.Mathf; public final class UniverseViewCamera { private static UniverseViewCamera instance; private static final float SMOOTH_TIME = 0.5f; private static final float ZOOM_SMOOTH_TIME = 0.15f; private static final float MIN_CAMERA_DISTANCE = 0.01f; private static final float MAX_CAMERA_DISTANCE = 100; private float cameraDistance = 0.02f; private final float zoomSpeed = 10; private float scale = 1.0f; private final OrthographicCamera cam; private float camRotation; private boolean moving; private float movingFromInputPositionX; private float movingFromInputPositionY; private boolean zooming; private float zoomingTouchFinger1FromPositionX; private float zoomingTouchFinger1FromPositionY; private float zoomingTouchFinger2FromPositionX; private float zoomingTouchFinger2FromPositionY; private float zoomingCameraDistanceDelta; private Actor followingObject; private boolean followRotation; private boolean followScale; private final Vector2 followingObjectPositionDelta = new Vector2(); // private Vector2 followingObjectPositionDeltaVelocity = new Vector2(); private float followingObjectScaleDelta; // private float followingObjectScaleDeltaVelocity; private float followingObjectCameraDistanceDelta; // private float followingObjectCameraDistanceDeltaVelocity; private float followingObjectRotationDelta; private float followObjectSmoothTime; static private Vector3 tmpv3 = new Vector3(); public static UniverseViewCamera getInstance() { return instance; } public final Actor getFollowingObject() { return followingObject; } public final Camera getCamera() { return cam; } public UniverseViewCamera(final OrthographicCamera camera) { instance = this; cam = camera; } public final void update(final float deltaTime) { switch (GameLogic.getInstace().getState()) { case PlayingAvatar: case PlayingShip: updatePosition(deltaTime); updateZoomInput(deltaTime); updateZoom(); break; case Travelling: updatePositionSmooth(deltaTime); updateZoom(); break; case Loading: // Do nothing break; } } private final void updatePosition(final float deltaTime) { followObjectSmoothTime = 0; if (followingObject != null) { if (GameLogic.getInstace().getState() == GameLogicState.PlayingAvatar && AvatarViewInput.mode == AvatarInputMode.Move || GameLogic.getInstace().getState() == GameLogicState.PlayingShip && ShipViewInput.mode == ShipInputMode.Move) { followingObjectPositionDelta.set(Mathf.lerp(followingObjectPositionDelta, Vector2.Zero, deltaTime * (1.0f / SMOOTH_TIME))); followingObjectScaleDelta = Mathf.lerp(followingObjectScaleDelta, 0, deltaTime * (1.0f / SMOOTH_TIME)); followingObjectRotationDelta = Mathf.lerp(followingObjectRotationDelta, 0, deltaTime * (1.0f / SMOOTH_TIME)); followingObjectCameraDistanceDelta = Mathf.lerp(followingObjectCameraDistanceDelta, 0, deltaTime * (1.0f / SMOOTH_TIME)); } float newPositionX, newPositionY; if (GameLogic.getInstace().getState() == GameLogicState.PlayingAvatar) { // newPosition = followingObject.position + trans.up * // followingObjectPositionDelta.y + trans.right * // followingObjectPositionDelta.x; newPositionX = followingObject.getX(); newPositionY = followingObject.getY(); tmpv3.set(cam.up).scl(followingObjectPositionDelta.y); newPositionX += tmpv3.x; newPositionY += tmpv3.y; // Calculate "right" direction by making the cross product // between the camera's foward and up vectors tmpv3.set(cam.up).crs(cam.direction).scl(followingObjectPositionDelta.x); newPositionX += tmpv3.x; newPositionY += tmpv3.y; } else { newPositionX = followingObject.getX() + followingObjectPositionDelta.x; newPositionY = followingObject.getY() + followingObjectPositionDelta.y; } float newRotation; if (followRotation) { newRotation = followingObject.getRotation(); } else { newRotation = 0; } float newScale; if (followScale) { newScale = followingObject.getScaleX(); } else { newScale = 1.0f; } // Update camera position cam.position.x = newPositionX; cam.position.y = newPositionY; // Update camera rotation camRotation = newRotation + followingObjectRotationDelta; while (camRotation > 360.0f) { camRotation -= 360.0f; } while (camRotation < 0) { camRotation += 360.0f; } cam.up.set(0, 1, 0).rotate(camRotation, 0, 0, -1); // Update camera scale scale = newScale + followingObjectScaleDelta; } } // Called by GameLogic private final boolean updatePositionSmooth(final float deltaTime) { followObjectSmoothTime += deltaTime; // followingObjectPositionDelta = // Vector3.SmoothDamp(followingObjectPositionDelta, Vector2.zero, ref // followingObjectPositionDeltaVelocity, SMOOTH_TIME); float newPositionX, newPositionY; if (followingObject != null) { followingObjectPositionDelta.set(Mathf.lerp(followingObjectPositionDelta, Vector2.Zero, followObjectSmoothTime / 1.0f)); newPositionX = followingObject.getX(); newPositionY = followingObject.getY(); tmpv3.set(cam.up).scl(followingObjectPositionDelta.y); newPositionX += tmpv3.x; newPositionX += tmpv3.y; // Calculate "right" direction by making the cross product between // the camera's foward and up vectors tmpv3.set(cam.up).crs(cam.direction).scl(followingObjectPositionDelta.x); newPositionX += tmpv3.x; newPositionX += tmpv3.y; // TODO: Implemented camera rotation on libgdx // Quaternion newRotation = followingObject.rotation; final float newScale = followingObject.getScaleX(); cam.position.x = Mathf.lerp(cam.position.x, newPositionX, followObjectSmoothTime / 1.0f); cam.position.y = Mathf.lerp(cam.position.y, newPositionY, followObjectSmoothTime / 1.0f); ; // trans.rotation = Quaternion.Lerp(trans.rotation, newRotation, // followObjectSmoothTime / 1.0f); scale = Mathf.lerp(scale, newScale, followObjectSmoothTime / 1.0f); return followObjectSmoothTime > 1.0f; } else { return true; } } public final void updateZoomInput(final float deltaTime) { if (Gdx.app.getType() == ApplicationType.Android || Gdx.app.getType() == ApplicationType.iOS) { int touchCount = 0; int touch1x = -1, touch1y = -1; int touch2x = -1, touch2y = -1; if (Gdx.input.isTouched(0)) { touch1x = Gdx.input.getX(0); touch1y = Gdx.input.getY(0); touchCount++; } if (Gdx.input.isTouched(1)) { touch2x = Gdx.input.getX(1); touch2y = Gdx.input.getY(1); touchCount++; } if (touchCount == 2) { if (!zooming) { zooming = true; } else { final float deltaFrom = Mathf.len(zoomingTouchFinger1FromPositionX - zoomingTouchFinger2FromPositionX, zoomingTouchFinger1FromPositionY - zoomingTouchFinger2FromPositionY); final float deltaTo = Mathf.len(touch1x - touch2x, touch1y - touch2y); final float zoom = (deltaTo - deltaFrom) / Mathf.len(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); zoomingCameraDistanceDelta -= (cameraDistance + zoomingCameraDistanceDelta) * zoom * 4; } zoomingTouchFinger1FromPositionX = touch1x; zoomingTouchFinger1FromPositionY = touch1y; zoomingTouchFinger2FromPositionX = touch2x; zoomingTouchFinger2FromPositionY = touch2y; } else { zooming = false; } } else { // Use mouse // TODO: Fix scroll wheel rotation detection! // float zoom = Input.GetAxis("Mouse ScrollWheel"); // zoomingCameraDistanceDelta -= (cameraDistance + // zoomingCameraDistanceDelta) * zoom * zoomSpeed * deltaTime; if (Gdx.input.isKeyPressed(Input.Keys.Z)) { zoomingCameraDistanceDelta -= (cameraDistance + zoomingCameraDistanceDelta) * -1.0f * zoomSpeed * deltaTime * 0.1f; } else if (Gdx.input.isKeyPressed(Input.Keys.X)) { zoomingCameraDistanceDelta -= (cameraDistance + zoomingCameraDistanceDelta) * 1.0f * zoomSpeed * deltaTime * 0.1f; } } } private final void updateZoom() { final float oldDelta = zoomingCameraDistanceDelta; zoomingCameraDistanceDelta = Mathf.lerp(zoomingCameraDistanceDelta, 0.0f, ZOOM_SMOOTH_TIME / 1.0f); cameraDistance += (oldDelta - zoomingCameraDistanceDelta); cameraDistance = MathUtils.clamp(cameraDistance, MIN_CAMERA_DISTANCE, MAX_CAMERA_DISTANCE); cam.zoom = (cameraDistance + followingObjectCameraDistanceDelta) * scale; } public final void updateMove() { float movingToInputPositionX = movingFromInputPositionX; float movingToInputPositionY = movingFromInputPositionY; if (Gdx.app.getType() == ApplicationType.Android || Gdx.app.getType() == ApplicationType.iOS) { int touchCount = 0; int touch1x = -1, touch1y = -1; if (Gdx.input.isTouched(0)) { touch1x = Gdx.input.getX(0); touch1y = Gdx.input.getY(0); touchCount++; } if (Gdx.input.isTouched(1)) { touchCount++; } if (touchCount == 1) { if (!moving) { moving = true; movingFromInputPositionX = touch1x; movingFromInputPositionY = touch1y; movingToInputPositionX = movingFromInputPositionX; movingToInputPositionY = movingFromInputPositionY; } else { movingToInputPositionX = touch1x; movingToInputPositionY = touch1y; } } else { moving = false; } } else { // Use mouse if (Gdx.input.isTouched()) { if (!moving) { moving = true; movingFromInputPositionX = Gdx.input.getX(); movingFromInputPositionY = Gdx.input.getY(); movingToInputPositionX = movingFromInputPositionX; movingToInputPositionY = movingFromInputPositionY; } else { movingToInputPositionX = Gdx.input.getX(); movingToInputPositionY = Gdx.input.getY(); } } else { moving = false; } } if (moving) { if (movingFromInputPositionX != movingToInputPositionX || movingFromInputPositionY != movingToInputPositionY) { cam.unproject(tmpv3.set(movingFromInputPositionX, movingFromInputPositionY, 0)); final float movingFromWorldPositionX = tmpv3.x; final float movingFromWorldPositionY = tmpv3.y; cam.unproject(tmpv3.set(movingToInputPositionX, movingToInputPositionY, 0)); final float movingToWorldPositionX = tmpv3.x; final float movingToWorldPositionY = tmpv3.y; final float dx = movingToWorldPositionX - movingFromWorldPositionX; final float dy = movingToWorldPositionY - movingFromWorldPositionY; if (followingObject != null) { // Calculate "right" direction by making the cross product // between the camera's forward and up vectors tmpv3.set(cam.up).crs(cam.direction); final float deltaX = Vector3.dot(dx, dy, 0, tmpv3.x, tmpv3.y, tmpv3.z); final float deltaY = Vector3.dot(dx, dy, 0, cam.up.x, cam.up.y, cam.up.z); followingObjectPositionDelta.x -= deltaX; followingObjectPositionDelta.y -= deltaY; float newPositionX, newPositionY; // Vector3 newPosition = followingObject.position + trans.up // * followingObjectPositionDelta.y + trans.right * // followingObjectPositionDelta.x; // newPosition.z = CAMERA_Z; newPositionX = followingObject.getX(); newPositionY = followingObject.getY(); tmpv3.set(cam.up).scl(followingObjectPositionDelta.y); newPositionX += tmpv3.x; newPositionY += tmpv3.y; // Calculate "right" direction by making the cross product // between the camera's foward and up vectors tmpv3.set(cam.up).crs(cam.direction).scl(followingObjectPositionDelta.x); newPositionX += tmpv3.x; newPositionY += tmpv3.y; cam.position.x = newPositionX; cam.position.y = newPositionY; } movingFromInputPositionX = movingToInputPositionX; movingFromInputPositionY = movingToInputPositionY; } } } private boolean travelInput; private float travelInputStartPositionX; private float travelInputStartPositionY; private float travelInputLastPositionX; private float travelInputLastPositionY; public final void updateClickOnPlanetToTravel(final UniverseView universeView) { boolean clickTravel = false; float clickPositionX = 0; float clickPositionY = 0; int touchCount = 0; int touch1x = -1, touch1y = -1; if (Gdx.input.isTouched(0)) { touch1x = Gdx.input.getX(0); touch1y = Gdx.input.getY(0); touchCount++; } if (Gdx.input.isTouched(1)) { touchCount++; } if (touchCount == 1 && !InputAreas.isInputArea(touch1x, touch1y)) { if (!travelInput) { travelInput = true; travelInputStartPositionX = touch1x; travelInputStartPositionY = touch1y; } else { travelInputLastPositionX = touch1x; travelInputLastPositionY = touch1y; } } else { if (travelInput) { if (Mathf.len(travelInputStartPositionX - travelInputLastPositionX, travelInputStartPositionY - travelInputLastPositionY) < 10) { clickTravel = true; clickPositionX = travelInputLastPositionX; clickPositionY = travelInputLastPositionY; } travelInput = false; } } if (clickTravel) { cam.unproject(tmpv3.set(clickPositionX, clickPositionY, 0)); final float worldPosX = tmpv3.x; final float worldPosY = tmpv3.y; float dpi = Gdx.graphics.getDensity() * 160.0f; if (dpi == 0) { dpi = 72.0f; } final float clickTolerance = (dpi / cam.zoom) / 2.54f; // 1cm click // tolerance final int clickedThingIndex = universeView.getUniverse().findClosestRenderedThing(worldPosX, worldPosY, clickTolerance); if (clickedThingIndex >= 0) { final PlanetView targetPlanetView = universeView.getPlanetView((short) clickedThingIndex); if (universeView.getAvatarView().getUniverseObject().getParent() != targetPlanetView.getTilemapCircle()) { GameLogic.getInstace().travelToPlanet(targetPlanetView); } } } } public final void followObject(final Actor toFollow, final int followCameraParameters, final boolean smoothTransition) { followRotation = (followCameraParameters & FollowCameraParameters.FollowRotation) != 0; followScale = (followCameraParameters & FollowCameraParameters.FollowScale) != 0; if (followingObject != toFollow) { if (smoothTransition && followingObject != null && toFollow != null) { followingObjectPositionDelta.set(cam.position.x - toFollow.getX(), cam.position.y - toFollow.getY()); if (followScale) { followingObjectScaleDelta = scale - toFollow.getScaleX(); followingObjectCameraDistanceDelta = cameraDistance - cameraDistance * (scale / toFollow.getScaleX()); cameraDistance = cameraDistance * (scale / toFollow.getScaleX()); } else { followingObjectScaleDelta = scale - 1.0f; followingObjectCameraDistanceDelta = cameraDistance - cameraDistance * (scale / 1.0f); cameraDistance = cameraDistance * (scale / 1.0f); } if (followRotation) { followingObjectRotationDelta = camRotation - toFollow.getRotation(); } else { followingObjectRotationDelta = camRotation - 0; } } followingObject = toFollow; } } }