package org.andengine.engine.camera; import org.andengine.engine.camera.hud.HUD; import org.andengine.engine.handler.IUpdateHandler; import org.andengine.engine.handler.UpdateHandlerList; import org.andengine.entity.IEntity; import org.andengine.entity.primitive.Line; import org.andengine.entity.shape.RectangularShape; import org.andengine.input.touch.TouchEvent; import org.andengine.opengl.util.GLState; import org.andengine.util.Constants; import org.andengine.util.adt.transformation.Transformation; import org.andengine.util.algorithm.collision.RectangularShapeCollisionChecker; import org.andengine.util.math.MathUtils; /** * (c) 2010 Nicolas Gramlich * (c) 2011 Zynga Inc. * * @author Nicolas Gramlich * @since 10:24:18 - 25.03.2010 */ public class Camera implements IUpdateHandler { // =========================================================== // Constants // =========================================================== static final float[] VERTICES_TMP = new float[2]; private static final int UPDATEHANDLERS_CAPACITY_DEFAULT = 4; // =========================================================== // Fields // =========================================================== protected float mXMin; protected float mXMax; protected float mYMin; protected float mYMax; private float mZNear = -1.0f; private float mZFar = 1.0f; private HUD mHUD; private IEntity mChaseEntity; protected float mRotation = 0; protected float mCameraSceneRotation = 0; protected int mSurfaceX; protected int mSurfaceY; protected int mSurfaceWidth; protected int mSurfaceHeight; protected boolean mResizeOnSurfaceSizeChanged; protected UpdateHandlerList mUpdateHandlers; // =========================================================== // Constructors // =========================================================== public Camera(final float pX, final float pY, final float pWidth, final float pHeight) { this.set(pX, pY, pX + pWidth, pY + pHeight); } // =========================================================== // Getter & Setter // =========================================================== public float getXMin() { return this.mXMin; } public void setXMin(final float pXMin) { this.mXMin = pXMin; } public float getXMax() { return this.mXMax; } public void setXMax(final float pXMax) { this.mXMax = pXMax; } public float getYMin() { return this.mYMin; } public void setYMin(final float pYMin) { this.mYMin = pYMin; } public float getYMax() { return this.mYMax; } public void setYMax(final float pYMax) { this.mYMax = pYMax; } public void set(final float pXMin, final float pYMin, final float pXMax, final float pYMax) { this.mXMin = pXMin; this.mXMax = pXMax; this.mYMin = pYMin; this.mYMax = pYMax; } public float getZNear() { return this.mZNear; } public float getZFar() { return this.mZFar; } public void setZNear(final float pZNear) { this.mZNear = pZNear; } public void setZFar(final float pZFar) { this.mZFar = pZFar; } public void setZClippingPlanes(final float pNearZClippingPlane, final float pFarZClippingPlane) { this.mZNear = pNearZClippingPlane; this.mZFar = pFarZClippingPlane; } public float getWidth() { return this.mXMax - this.mXMin; } public float getHeight() { return this.mYMax - this.mYMin; } public float getWidthRaw() { return this.mXMax - this.mXMin; } public float getHeightRaw() { return this.mYMax - this.mYMin; } public float getCenterX() { return (this.mXMin + this.mXMax) * 0.5f; } public float getCenterY() { return (this.mYMin + this.mYMax) * 0.5f; } public void setCenter(final float pCenterX, final float pCenterY) { final float dX = pCenterX - this.getCenterX(); final float dY = pCenterY - this.getCenterY(); this.mXMin += dX; this.mXMax += dX; this.mYMin += dY; this.mYMax += dY; } public void offsetCenter(final float pX, final float pY) { this.setCenter(this.getCenterX() + pX, this.getCenterY() + pY); } public HUD getHUD() { return this.mHUD; } public void setHUD(final HUD pHUD) { this.mHUD = pHUD; if(pHUD != null) { pHUD.setCamera(this); } } public boolean hasHUD() { return this.mHUD != null; } public void setChaseEntity(final IEntity pChaseEntity) { this.mChaseEntity = pChaseEntity; } public boolean isRotated() { return this.mRotation != 0; } public float getRotation() { return this.mRotation; } public void setRotation(final float pRotation) { this.mRotation = pRotation; } public float getCameraSceneRotation() { return this.mCameraSceneRotation; } public void setCameraSceneRotation(final float pCameraSceneRotation) { this.mCameraSceneRotation = pCameraSceneRotation; } public int getSurfaceX() { return this.mSurfaceX; } public int getSurfaceY() { return this.mSurfaceY; } public int getSurfaceWidth() { return this.mSurfaceWidth; } public int getSurfaceHeight() { return this.mSurfaceHeight; } public void setSurfaceSize(final int pSurfaceX, final int pSurfaceY, final int pSurfaceWidth, final int pSurfaceHeight) { if(this.mSurfaceHeight == 0 && this.mSurfaceWidth == 0) { this.onSurfaceSizeInitialized(pSurfaceX, pSurfaceY, pSurfaceWidth, pSurfaceHeight); } else if(this.mSurfaceWidth != pSurfaceWidth || this.mSurfaceHeight != pSurfaceHeight) { this.onSurfaceSizeChanged(this.mSurfaceX, this.mSurfaceY, this.mSurfaceWidth, this.mSurfaceHeight, pSurfaceX, pSurfaceY, pSurfaceWidth, pSurfaceHeight); } } public boolean isResizeOnSurfaceSizeChanged() { return this.mResizeOnSurfaceSizeChanged; } public void setResizeOnSurfaceSizeChanged(final boolean pResizeOnSurfaceSizeChanged) { this.mResizeOnSurfaceSizeChanged = pResizeOnSurfaceSizeChanged; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Override public void onUpdate(final float pSecondsElapsed) { if(this.mUpdateHandlers != null) { this.mUpdateHandlers.onUpdate(pSecondsElapsed); } if(this.mHUD != null) { this.mHUD.onUpdate(pSecondsElapsed); } this.updateChaseEntity(); } @Override public void reset() { } // =========================================================== // Methods // =========================================================== public void onDrawHUD(final GLState pGLState) { if(this.mHUD != null) { this.mHUD.onDraw(pGLState, this); } } public void updateChaseEntity() { if(this.mChaseEntity != null) { final float[] centerCoordinates = this.mChaseEntity.getSceneCenterCoordinates(); this.setCenter(centerCoordinates[Constants.VERTEX_INDEX_X], centerCoordinates[Constants.VERTEX_INDEX_Y]); } } public boolean isLineVisible(final Line pLine) { return RectangularShapeCollisionChecker.isVisible(this, pLine); } public boolean isRectangularShapeVisible(final RectangularShape pRectangularShape) { return RectangularShapeCollisionChecker.isVisible(this, pRectangularShape); } public boolean isRectangularShapeVisible(final float pX, final float pY, final float pWidth, final float pHeight, final Transformation pLocalToSceneTransformation) { return RectangularShapeCollisionChecker.isVisible(this, pX, pY, pWidth, pHeight, pLocalToSceneTransformation); } public void onApplySceneMatrix(final GLState pGLState) { pGLState.orthoProjectionGLMatrixf(this.getXMin(), this.getXMax(), this.getYMax(), this.getYMin(), this.mZNear, this.mZFar); final float rotation = this.mRotation; if(rotation != 0) { Camera.applyRotation(pGLState, this.getCenterX(), this.getCenterY(), rotation); } } public void onApplySceneBackgroundMatrix(final GLState pGLState) { final float widthRaw = this.getWidthRaw(); final float heightRaw = this.getHeightRaw(); pGLState.orthoProjectionGLMatrixf(0, widthRaw, heightRaw, 0, this.mZNear, this.mZFar); final float rotation = this.mRotation; if(rotation != 0) { Camera.applyRotation(pGLState, widthRaw * 0.5f, heightRaw * 0.5f, rotation); } } public void onApplyCameraSceneMatrix(final GLState pGLState) { final float widthRaw = this.getWidthRaw(); final float heightRaw = this.getHeightRaw(); pGLState.orthoProjectionGLMatrixf(0, widthRaw, heightRaw, 0, this.mZNear, this.mZFar); final float cameraSceneRotation = this.mCameraSceneRotation; if(cameraSceneRotation != 0) { Camera.applyRotation(pGLState, widthRaw * 0.5f, heightRaw * 0.5f, cameraSceneRotation); } } private static void applyRotation(final GLState pGLState, final float pRotationCenterX, final float pRotationCenterY, final float pAngle) { pGLState.translateProjectionGLMatrixf(pRotationCenterX, pRotationCenterY, 0); pGLState.rotateProjectionGLMatrixf(pAngle, 0, 0, 1); pGLState.translateProjectionGLMatrixf(-pRotationCenterX, -pRotationCenterY, 0); } public void convertSceneToCameraSceneTouchEvent(final TouchEvent pSceneTouchEvent) { this.unapplySceneRotation(pSceneTouchEvent); this.applySceneToCameraSceneOffset(pSceneTouchEvent); this.applyCameraSceneRotation(pSceneTouchEvent); } public float[] getCameraSceneCoordinatesFromSceneCoordinates(final float pSceneX, final float pSceneY) { Camera.VERTICES_TMP[Constants.VERTEX_INDEX_X] = pSceneX; Camera.VERTICES_TMP[Constants.VERTEX_INDEX_Y] = pSceneY; return this.getCameraSceneCoordinatesFromSceneCoordinates(Camera.VERTICES_TMP); } public float[] getCameraSceneCoordinatesFromSceneCoordinates(final float[] pSceneCoordinates) { this.unapplySceneRotation(pSceneCoordinates); this.applySceneToCameraSceneOffset(pSceneCoordinates); this.applyCameraSceneRotation(pSceneCoordinates); return pSceneCoordinates; } public void convertCameraSceneToSceneTouchEvent(final TouchEvent pCameraSceneTouchEvent) { this.unapplyCameraSceneRotation(pCameraSceneTouchEvent); this.unapplySceneToCameraSceneOffset(pCameraSceneTouchEvent); this.applySceneRotation(pCameraSceneTouchEvent); } public float[] getSceneCoordinatesFromCameraSceneCoordinates(final float pCameraSceneX, final float pCameraSceneY) { Camera.VERTICES_TMP[Constants.VERTEX_INDEX_X] = pCameraSceneX; Camera.VERTICES_TMP[Constants.VERTEX_INDEX_Y] = pCameraSceneY; return this.getSceneCoordinatesFromCameraSceneCoordinates(Camera.VERTICES_TMP); } public float[] getSceneCoordinatesFromCameraSceneCoordinates(final float[] pCameraSceneCoordinates) { this.unapplyCameraSceneRotation(pCameraSceneCoordinates); this.unapplySceneToCameraSceneOffset(pCameraSceneCoordinates); this.applySceneRotation(pCameraSceneCoordinates); return pCameraSceneCoordinates; } protected void applySceneToCameraSceneOffset(final TouchEvent pSceneTouchEvent) { pSceneTouchEvent.offset(-this.mXMin, -this.mYMin); } protected void applySceneToCameraSceneOffset(final float[] pSceneCoordinates) { pSceneCoordinates[Constants.VERTEX_INDEX_X] -= this.mXMin; pSceneCoordinates[Constants.VERTEX_INDEX_Y] -= this.mYMin; } protected void unapplySceneToCameraSceneOffset(final TouchEvent pCameraSceneTouchEvent) { pCameraSceneTouchEvent.offset(this.mXMin, this.mYMin); } protected void unapplySceneToCameraSceneOffset(final float[] pCameraSceneCoordinates) { pCameraSceneCoordinates[Constants.VERTEX_INDEX_X] += this.mXMin; pCameraSceneCoordinates[Constants.VERTEX_INDEX_Y] += this.mYMin; } private void applySceneRotation(final float[] pCameraSceneCoordinates) { final float rotation = this.mRotation; if(rotation != 0) { MathUtils.rotateAroundCenter(pCameraSceneCoordinates, -rotation, this.getCenterX(), this.getCenterY()); } } private void applySceneRotation(final TouchEvent pCameraSceneTouchEvent) { final float rotation = this.mRotation; if(rotation != 0) { Camera.VERTICES_TMP[Constants.VERTEX_INDEX_X] = pCameraSceneTouchEvent.getX(); Camera.VERTICES_TMP[Constants.VERTEX_INDEX_Y] = pCameraSceneTouchEvent.getY(); MathUtils.rotateAroundCenter(Camera.VERTICES_TMP, -rotation, this.getCenterX(), this.getCenterY()); pCameraSceneTouchEvent.set(Camera.VERTICES_TMP[Constants.VERTEX_INDEX_X], Camera.VERTICES_TMP[Constants.VERTEX_INDEX_Y]); } } private void unapplySceneRotation(final float[] pSceneCoordinates) { final float rotation = this.mRotation; if(rotation != 0) { MathUtils.revertRotateAroundCenter(pSceneCoordinates, rotation, this.getCenterX(), this.getCenterY()); } } private void unapplySceneRotation(final TouchEvent pSceneTouchEvent) { final float rotation = this.mRotation; if(rotation != 0) { Camera.VERTICES_TMP[Constants.VERTEX_INDEX_X] = pSceneTouchEvent.getX(); Camera.VERTICES_TMP[Constants.VERTEX_INDEX_Y] = pSceneTouchEvent.getY(); MathUtils.revertRotateAroundCenter(Camera.VERTICES_TMP, rotation, this.getCenterX(), this.getCenterY()); pSceneTouchEvent.set(Camera.VERTICES_TMP[Constants.VERTEX_INDEX_X], Camera.VERTICES_TMP[Constants.VERTEX_INDEX_Y]); } } private void applyCameraSceneRotation(final float[] pSceneCoordinates) { final float cameraSceneRotation = -this.mCameraSceneRotation; if(cameraSceneRotation != 0) { MathUtils.rotateAroundCenter(pSceneCoordinates, cameraSceneRotation, (this.mXMax - this.mXMin) * 0.5f, (this.mYMax - this.mYMin) * 0.5f); } } private void applyCameraSceneRotation(final TouchEvent pSceneTouchEvent) { final float cameraSceneRotation = -this.mCameraSceneRotation; if(cameraSceneRotation != 0) { Camera.VERTICES_TMP[Constants.VERTEX_INDEX_X] = pSceneTouchEvent.getX(); Camera.VERTICES_TMP[Constants.VERTEX_INDEX_Y] = pSceneTouchEvent.getY(); MathUtils.rotateAroundCenter(Camera.VERTICES_TMP, cameraSceneRotation, (this.mXMax - this.mXMin) * 0.5f, (this.mYMax - this.mYMin) * 0.5f); pSceneTouchEvent.set(Camera.VERTICES_TMP[Constants.VERTEX_INDEX_X], Camera.VERTICES_TMP[Constants.VERTEX_INDEX_Y]); } } private void unapplyCameraSceneRotation(final float[] pCameraSceneCoordinates) { final float cameraSceneRotation = -this.mCameraSceneRotation; if(cameraSceneRotation != 0) { MathUtils.revertRotateAroundCenter(pCameraSceneCoordinates, cameraSceneRotation, (this.mXMax - this.mXMin) * 0.5f, (this.mYMax - this.mYMin) * 0.5f); } } private void unapplyCameraSceneRotation(final TouchEvent pCameraSceneTouchEvent) { final float cameraSceneRotation = -this.mCameraSceneRotation; if(cameraSceneRotation != 0) { Camera.VERTICES_TMP[Constants.VERTEX_INDEX_X] = pCameraSceneTouchEvent.getX(); Camera.VERTICES_TMP[Constants.VERTEX_INDEX_Y] = pCameraSceneTouchEvent.getY(); MathUtils.revertRotateAroundCenter(Camera.VERTICES_TMP, cameraSceneRotation, (this.mXMax - this.mXMin) * 0.5f, (this.mYMax - this.mYMin) * 0.5f); pCameraSceneTouchEvent.set(Camera.VERTICES_TMP[Constants.VERTEX_INDEX_X], Camera.VERTICES_TMP[Constants.VERTEX_INDEX_Y]); } } // TODO Camera already knows about its surfaceWidth, is there a need to pass it in here again? public void convertSurfaceToSceneTouchEvent(final TouchEvent pSurfaceTouchEvent, final int pSurfaceWidth, final int pSurfaceHeight) { final float relativeX; final float relativeY; final float surfaceTouchEventX = pSurfaceTouchEvent.getX(); final float surfaceTouchEventY = pSurfaceTouchEvent.getY(); final float rotation = this.mRotation; if(rotation == 0) { relativeX = surfaceTouchEventX / pSurfaceWidth; relativeY = surfaceTouchEventY / pSurfaceHeight; } else if(rotation == 180) { relativeX = 1 - (surfaceTouchEventX / pSurfaceWidth); relativeY = 1 - (surfaceTouchEventY / pSurfaceHeight); } else { Camera.VERTICES_TMP[Constants.VERTEX_INDEX_X] = surfaceTouchEventX; Camera.VERTICES_TMP[Constants.VERTEX_INDEX_Y] = surfaceTouchEventY; MathUtils.rotateAroundCenter(Camera.VERTICES_TMP, rotation, pSurfaceWidth >> 1, pSurfaceHeight >> 1); relativeX = Camera.VERTICES_TMP[Constants.VERTEX_INDEX_X] / pSurfaceWidth; relativeY = Camera.VERTICES_TMP[Constants.VERTEX_INDEX_Y] / pSurfaceHeight; } this.convertAxisAlignedSurfaceToSceneTouchEvent(pSurfaceTouchEvent, relativeX, relativeY); } private void convertAxisAlignedSurfaceToSceneTouchEvent(final TouchEvent pSurfaceTouchEvent, final float pRelativeX, final float pRelativeY) { final float xMin = this.getXMin(); final float xMax = this.getXMax(); final float yMin = this.getYMin(); final float yMax = this.getYMax(); final float x = xMin + pRelativeX * (xMax - xMin); final float y = yMin + pRelativeY * (yMax - yMin); pSurfaceTouchEvent.set(x, y); } public void convertSceneToSurfaceTouchEvent(final TouchEvent pSceneTouchEvent, final int pSurfaceWidth, final int pSurfaceHeight) { this.convertAxisAlignedSceneToSurfaceTouchEvent(pSceneTouchEvent, pSurfaceWidth, pSurfaceHeight); final float rotation = this.mRotation; if(rotation == 0) { /* Nothing to do. */ } else if(rotation == 180) { pSceneTouchEvent.set(pSurfaceWidth - pSceneTouchEvent.getX(), pSurfaceHeight - pSceneTouchEvent.getY()); } else { Camera.VERTICES_TMP[Constants.VERTEX_INDEX_X] = pSceneTouchEvent.getX(); Camera.VERTICES_TMP[Constants.VERTEX_INDEX_Y] = pSceneTouchEvent.getY(); MathUtils.revertRotateAroundCenter(Camera.VERTICES_TMP, rotation, pSurfaceWidth >> 1, pSurfaceHeight >> 1); pSceneTouchEvent.set(Camera.VERTICES_TMP[Constants.VERTEX_INDEX_X], Camera.VERTICES_TMP[Constants.VERTEX_INDEX_Y]); } } private void convertAxisAlignedSceneToSurfaceTouchEvent(final TouchEvent pSceneTouchEvent, final int pSurfaceWidth, final int pSurfaceHeight) { final float xMin = this.getXMin(); final float xMax = this.getXMax(); final float yMin = this.getYMin(); final float yMax = this.getYMax(); final float relativeX = (pSceneTouchEvent.getX() - xMin) / (xMax - xMin); final float relativeY = (pSceneTouchEvent.getY() - yMin) / (yMax - yMin); pSceneTouchEvent.set(relativeX * pSurfaceWidth, relativeY * pSurfaceHeight); } public void registerUpdateHandler(final IUpdateHandler pUpdateHandler) { if(this.mUpdateHandlers == null) { this.allocateUpdateHandlers(); } this.mUpdateHandlers.add(pUpdateHandler); } public boolean unregisterUpdateHandler(final IUpdateHandler pUpdateHandler) { if(this.mUpdateHandlers == null) { return false; } return this.mUpdateHandlers.remove(pUpdateHandler); } public boolean unregisterUpdateHandlers(final IUpdateHandlerMatcher pUpdateHandlerMatcher) { if(this.mUpdateHandlers == null) { return false; } return this.mUpdateHandlers.removeAll(pUpdateHandlerMatcher); } public void clearUpdateHandlers() { if(this.mUpdateHandlers == null) { return; } this.mUpdateHandlers.clear(); } private void allocateUpdateHandlers() { this.mUpdateHandlers = new UpdateHandlerList(Camera.UPDATEHANDLERS_CAPACITY_DEFAULT); } protected void onSurfaceSizeInitialized(final int pSurfaceX, final int pSurfaceY, final int pSurfaceWidth, final int pSurfaceHeight) { this.mSurfaceX = pSurfaceX; this.mSurfaceY = pSurfaceY; this.mSurfaceWidth = pSurfaceWidth; this.mSurfaceHeight = pSurfaceHeight; } protected void onSurfaceSizeChanged(final int pOldSurfaceX, final int pOldSurfaceY, final int pOldSurfaceWidth, final int pOldSurfaceHeight, final int pNewSurfaceX, final int pNewSurfaceY, final int pNewSurfaceWidth, final int pNewSurfaceHeight) { if(this.mResizeOnSurfaceSizeChanged) { final float surfaceWidthRatio = (float)pNewSurfaceWidth / pOldSurfaceWidth; final float surfaceHeightRatio = (float)pNewSurfaceHeight / pOldSurfaceHeight; final float centerX = this.getCenterX(); final float centerY = this.getCenterY(); final float newWidthRaw = this.getWidthRaw() * surfaceWidthRatio; final float newHeightRaw = this.getHeightRaw() * surfaceHeightRatio; final float newWidthRawHalf = newWidthRaw * 0.5f; final float newHeightRawHalf = newHeightRaw * 0.5f; final float xMin = centerX - newWidthRawHalf; final float yMin = centerY - newHeightRawHalf; final float xMax = centerX + newWidthRawHalf; final float yMax = centerY + newHeightRawHalf; this.set(xMin, yMin, xMax, yMax); } this.mSurfaceX = pNewSurfaceX; this.mSurfaceY = pNewSurfaceY; this.mSurfaceWidth = pNewSurfaceWidth; this.mSurfaceHeight = pNewSurfaceHeight; } // =========================================================== // Inner and Anonymous Classes // =========================================================== }