package org.andengine.entity; import java.util.ArrayList; import java.util.List; import org.andengine.engine.camera.Camera; import org.andengine.engine.handler.IUpdateHandler; import org.andengine.engine.handler.UpdateHandlerList; import org.andengine.entity.modifier.EntityModifierList; import org.andengine.entity.modifier.IEntityModifier; import org.andengine.entity.modifier.IEntityModifier.IEntityModifierMatcher; import org.andengine.opengl.util.GLState; import org.andengine.util.Constants; import org.andengine.util.adt.list.SmartList; import org.andengine.util.adt.transformation.Transformation; import org.andengine.util.call.ParameterCallable; import org.andengine.util.color.Color; /** * (c) 2010 Nicolas Gramlich * (c) 2011 Zynga Inc. * * @author Nicolas Gramlich * @since 12:00:48 - 08.03.2010 */ public class Entity implements IEntity { // =========================================================== // Constants // =========================================================== private static final int CHILDREN_CAPACITY_DEFAULT = 4; private static final int ENTITYMODIFIERS_CAPACITY_DEFAULT = 4; private static final int UPDATEHANDLERS_CAPACITY_DEFAULT = 4; private static final float[] VERTICES_SCENE_TO_LOCAL_TMP = new float[2]; private static final float[] VERTICES_LOCAL_TO_SCENE_TMP = new float[2]; private static final ParameterCallable<IEntity> PARAMETERCALLABLE_DETACHCHILD = new ParameterCallable<IEntity>() { @Override public void call(final IEntity pEntity) { pEntity.setParent(null); pEntity.onDetached(); } }; // =========================================================== // Fields // =========================================================== protected boolean mDisposed; protected boolean mVisible = true; protected boolean mCullingEnabled; protected boolean mIgnoreUpdate; protected boolean mChildrenVisible = true; protected boolean mChildrenIgnoreUpdate; protected boolean mChildrenSortPending; protected int mTag = IEntity.TAG_INVALID; protected int mZIndex = 0; private IEntity mParent; protected SmartList<IEntity> mChildren; private EntityModifierList mEntityModifiers; private UpdateHandlerList mUpdateHandlers; protected Color mColor = new Color(1, 1, 1, 1); protected float mX; protected float mY; protected float mRotation = 0; protected float mRotationCenterX = 0; protected float mRotationCenterY = 0; protected float mScaleX = 1; protected float mScaleY = 1; protected float mScaleCenterX = 0; protected float mScaleCenterY = 0; protected float mSkewX = 0; protected float mSkewY = 0; protected float mSkewCenterX = 0; protected float mSkewCenterY = 0; private boolean mLocalToParentTransformationDirty = true; private boolean mParentToLocalTransformationDirty = true; private Transformation mLocalToParentTransformation; private Transformation mParentToLocalTransformation; private Transformation mLocalToSceneTransformation; private Transformation mSceneToLocalTransformation; private Object mUserData; // =========================================================== // Constructors // =========================================================== public Entity() { this(0, 0); } public Entity(final float pX, final float pY) { this.mX = pX; this.mY = pY; } // =========================================================== // Getter & Setter // =========================================================== // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== protected void onUpdateColor() { } @Override public boolean isDisposed() { return this.mDisposed; } @Override public boolean isVisible() { return this.mVisible; } @Override public void setVisible(final boolean pVisible) { this.mVisible = pVisible; } @Override public boolean isCullingEnabled() { return this.mCullingEnabled; } @Override public void setCullingEnabled(final boolean pCullingEnabled) { this.mCullingEnabled = pCullingEnabled; } @Override public boolean isCulled(final Camera pCamera) { return false; } @Override public boolean isChildrenVisible() { return this.mChildrenVisible; } @Override public void setChildrenVisible(final boolean pChildrenVisible) { this.mChildrenVisible = pChildrenVisible; } @Override public boolean isIgnoreUpdate() { return this.mIgnoreUpdate; } @Override public void setIgnoreUpdate(final boolean pIgnoreUpdate) { this.mIgnoreUpdate = pIgnoreUpdate; } @Override public boolean isChildrenIgnoreUpdate() { return this.mChildrenIgnoreUpdate; } @Override public void setChildrenIgnoreUpdate(final boolean pChildrenIgnoreUpdate) { this.mChildrenIgnoreUpdate = pChildrenIgnoreUpdate; } @Override public boolean hasParent() { return this.mParent != null; } @Override public IEntity getParent() { return this.mParent; } @Override public void setParent(final IEntity pEntity) { this.mParent = pEntity; } @Override public int getTag() { return this.mTag; } @Override public void setTag(final int pTag) { this.mTag = pTag; } @Override public int getZIndex() { return this.mZIndex; } @Override public void setZIndex(final int pZIndex) { this.mZIndex = pZIndex; } @Override public float getX() { return this.mX; } @Override public float getY() { return this.mY; } @Override public void setX(final float pX) { this.mX = pX; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public void setY(final float pY) { this.mY = pY; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public void setPosition(final IEntity pOtherEntity) { this.setPosition(pOtherEntity.getX(), pOtherEntity.getY()); } @Override public void setPosition(final float pX, final float pY) { this.mX = pX; this.mY = pY; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public float getRotation() { return this.mRotation; } @Override public boolean isRotated() { return this.mRotation != 0; } @Override public void setRotation(final float pRotation) { this.mRotation = pRotation; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public float getRotationCenterX() { return this.mRotationCenterX; } @Override public float getRotationCenterY() { return this.mRotationCenterY; } @Override public void setRotationCenterX(final float pRotationCenterX) { this.mRotationCenterX = pRotationCenterX; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public void setRotationCenterY(final float pRotationCenterY) { this.mRotationCenterY = pRotationCenterY; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public void setRotationCenter(final float pRotationCenterX, final float pRotationCenterY) { this.mRotationCenterX = pRotationCenterX; this.mRotationCenterY = pRotationCenterY; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public boolean isScaled() { return (this.mScaleX != 1) || (this.mScaleY != 1); } @Override public float getScaleX() { return this.mScaleX; } @Override public float getScaleY() { return this.mScaleY; } @Override public void setScaleX(final float pScaleX) { this.mScaleX = pScaleX; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public void setScaleY(final float pScaleY) { this.mScaleY = pScaleY; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public void setScale(final float pScale) { this.mScaleX = pScale; this.mScaleY = pScale; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public void setScale(final float pScaleX, final float pScaleY) { this.mScaleX = pScaleX; this.mScaleY = pScaleY; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public float getScaleCenterX() { return this.mScaleCenterX; } @Override public float getScaleCenterY() { return this.mScaleCenterY; } @Override public void setScaleCenterX(final float pScaleCenterX) { this.mScaleCenterX = pScaleCenterX; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public void setScaleCenterY(final float pScaleCenterY) { this.mScaleCenterY = pScaleCenterY; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public void setScaleCenter(final float pScaleCenterX, final float pScaleCenterY) { this.mScaleCenterX = pScaleCenterX; this.mScaleCenterY = pScaleCenterY; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public boolean isSkewed() { return (this.mSkewX != 0) || (this.mSkewY != 0); } @Override public float getSkewX() { return this.mSkewX; } @Override public float getSkewY() { return this.mSkewY; } @Override public void setSkewX(final float pSkewX) { this.mSkewX = pSkewX; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public void setSkewY(final float pSkewY) { this.mSkewY = pSkewY; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public void setSkew(final float pSkew) { this.mSkewX = pSkew; this.mSkewY = pSkew; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public void setSkew(final float pSkewX, final float pSkewY) { this.mSkewX = pSkewX; this.mSkewY = pSkewY; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public float getSkewCenterX() { return this.mSkewCenterX; } @Override public float getSkewCenterY() { return this.mSkewCenterY; } @Override public void setSkewCenterX(final float pSkewCenterX) { this.mSkewCenterX = pSkewCenterX; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public void setSkewCenterY(final float pSkewCenterY) { this.mSkewCenterY = pSkewCenterY; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public void setSkewCenter(final float pSkewCenterX, final float pSkewCenterY) { this.mSkewCenterX = pSkewCenterX; this.mSkewCenterY = pSkewCenterY; this.mLocalToParentTransformationDirty = true; this.mParentToLocalTransformationDirty = true; } @Override public boolean isRotatedOrScaledOrSkewed() { return (this.mRotation != 0) || (this.mScaleX != 1) || (this.mScaleY != 1) || (this.mSkewX != 0) || (this.mSkewY != 0); } @Override public float getRed() { return this.mColor.getRed(); } @Override public float getGreen() { return this.mColor.getGreen(); } @Override public float getBlue() { return this.mColor.getBlue(); } @Override public float getAlpha() { return this.mColor.getAlpha(); } @Override public Color getColor() { return this.mColor; } @Override public void setColor(final Color pColor) { this.mColor.set(pColor); this.onUpdateColor(); } /** * @param pRed from <code>0.0f</code> to <code>1.0f</code> */ @Override public void setRed(final float pRed) { if(this.mColor.setRedChecking(pRed)) { this.onUpdateColor(); } } /** * @param pGreen from <code>0.0f</code> to <code>1.0f</code> */ @Override public void setGreen(final float pGreen) { if(this.mColor.setGreenChecking(pGreen)) { this.onUpdateColor(); } } /** * @param pBlue from <code>0.0f</code> to <code>1.0f</code> */ @Override public void setBlue(final float pBlue) { if(this.mColor.setBlueChecking(pBlue)) { this.onUpdateColor(); } } /** * @param pAlpha from <code>0.0f</code> (transparent) to <code>1.0f</code> (opaque) */ @Override public void setAlpha(final float pAlpha) { if(this.mColor.setAlphaChecking(pAlpha)) { this.onUpdateColor(); } } /** * @param pRed from <code>0.0f</code> to <code>1.0f</code> * @param pGreen from <code>0.0f</code> to <code>1.0f</code> * @param pBlue from <code>0.0f</code> to <code>1.0f</code> */ @Override public void setColor(final float pRed, final float pGreen, final float pBlue) { if(this.mColor.setChecking(pRed, pGreen, pBlue)) { // TODO Is this check worth it? this.onUpdateColor(); } } /** * @param pRed from <code>0.0f</code> to <code>1.0f</code> * @param pGreen from <code>0.0f</code> to <code>1.0f</code> * @param pBlue from <code>0.0f</code> to <code>1.0f</code> * @param pAlpha from <code>0.0f</code> (transparent) to <code>1.0f</code> (opaque) */ @Override public void setColor(final float pRed, final float pGreen, final float pBlue, final float pAlpha) { if(this.mColor.setChecking(pRed, pGreen, pBlue, pAlpha)) { // TODO Is this check worth it? this.onUpdateColor(); } } @Override public int getChildCount() { if(this.mChildren == null) { return 0; } return this.mChildren.size(); } @Override public IEntity getChildByTag(final int pTag) { if(this.mChildren == null) { return null; } for(int i = this.mChildren.size() - 1; i >= 0; i--) { final IEntity child = this.mChildren.get(i); if(child.getTag() == pTag) { return child; } } return null; } @Override public IEntity getChildByIndex(final int pIndex) { if(this.mChildren == null) { return null; } return this.mChildren.get(pIndex); } @Override public IEntity getChildByMatcher(final IEntityMatcher pEntityMatcher) { if(this.mChildren == null) { return null; } return this.mChildren.get(pEntityMatcher); } @Override public IEntity getFirstChild() { if(this.mChildren == null) { return null; } return this.mChildren.get(0); } @Override public IEntity getLastChild() { if(this.mChildren == null) { return null; } return this.mChildren.get(this.mChildren.size() - 1); } @Override public ArrayList<IEntity> query(final IEntityMatcher pEntityMatcher) { return this.query(pEntityMatcher, new ArrayList<IEntity>()); } @Override public IEntity queryFirst(final IEntityMatcher pEntityMatcher) { return this.queryFirstForSubclass(pEntityMatcher); } @SuppressWarnings("unchecked") @Override public <S extends IEntity> S queryFirstForSubclass(final IEntityMatcher pEntityMatcher) { final int childCount = this.getChildCount(); for(int i = 0; i < childCount; i++) { final IEntity child = this.mChildren.get(i); if(pEntityMatcher.matches(child)) { return (S)child; } final S childQueryFirst = child.queryFirstForSubclass(pEntityMatcher); if(childQueryFirst != null) { return childQueryFirst; } } return null; } @Override public <L extends List<IEntity>> L query(final IEntityMatcher pEntityMatcher, final L pResult) { final int childCount = this.getChildCount(); for(int i = 0; i < childCount; i++) { final IEntity child = this.mChildren.get(i); if(pEntityMatcher.matches(child)) { pResult.add(child); } child.query(pEntityMatcher, pResult); } return pResult; } @Override public <S extends IEntity> ArrayList<S> queryForSubclass(final IEntityMatcher pEntityMatcher) throws ClassCastException { return this.queryForSubclass(pEntityMatcher, new ArrayList<S>()); } @SuppressWarnings("unchecked") @Override public <L extends List<S>, S extends IEntity> L queryForSubclass(final IEntityMatcher pEntityMatcher, final L pResult) throws ClassCastException { final int childCount = this.getChildCount(); for(int i = 0; i < childCount; i++) { final IEntity child = this.mChildren.get(i); if(pEntityMatcher.matches(child)) { pResult.add((S)child); } child.queryForSubclass(pEntityMatcher, pResult); } return pResult; } @Override public boolean detachSelf() { final IEntity parent = this.mParent; if(parent != null) { return parent.detachChild(this); } else { return false; } } @Override public void detachChildren() { if(this.mChildren == null) { return; } this.mChildren.clear(Entity.PARAMETERCALLABLE_DETACHCHILD); } @Override public void attachChild(final IEntity pEntity) throws IllegalStateException { this.assertEntityHasNoParent(pEntity); if(this.mChildren == null) { this.allocateChildren(); } this.mChildren.add(pEntity); pEntity.setParent(this); pEntity.onAttached(); } @Override public void sortChildren() { this.sortChildren(true); } @Override public void sortChildren(final boolean pImmediate) { if(this.mChildren == null) { return; } if(pImmediate) { ZIndexSorter.getInstance().sort(this.mChildren); } else { this.mChildrenSortPending = true; } } @Override public void sortChildren(final IEntityComparator pEntityComparator) { if(this.mChildren == null) { return; } ZIndexSorter.getInstance().sort(this.mChildren, pEntityComparator); } @Override public boolean detachChild(final IEntity pEntity) { if(this.mChildren == null) { return false; } return this.mChildren.remove(pEntity, Entity.PARAMETERCALLABLE_DETACHCHILD); } @Override public IEntity detachChild(final int pTag) { if(this.mChildren == null) { return null; } for(int i = this.mChildren.size() - 1; i >= 0; i--) { if(this.mChildren.get(i).getTag() == pTag) { final IEntity removed = this.mChildren.remove(i); Entity.PARAMETERCALLABLE_DETACHCHILD.call(removed); return removed; } } return null; } @Override public IEntity detachChild(final IEntityMatcher pEntityMatcher) { if(this.mChildren == null) { return null; } return this.mChildren.remove(pEntityMatcher, Entity.PARAMETERCALLABLE_DETACHCHILD); } @Override public boolean detachChildren(final IEntityMatcher pEntityMatcher) { if(this.mChildren == null) { return false; } return this.mChildren.removeAll(pEntityMatcher, Entity.PARAMETERCALLABLE_DETACHCHILD); } @Override public void callOnChildren(final IEntityParameterCallable pEntityParameterCallable) { if(this.mChildren == null) { return; } this.mChildren.call(pEntityParameterCallable); } @Override public void callOnChildren(final IEntityParameterCallable pEntityParameterCallable, final IEntityMatcher pEntityMatcher) { if(this.mChildren == null) { return; } this.mChildren.call(pEntityMatcher, pEntityParameterCallable); } @Override public void registerUpdateHandler(final IUpdateHandler pUpdateHandler) { if(this.mUpdateHandlers == null) { this.allocateUpdateHandlers(); } this.mUpdateHandlers.add(pUpdateHandler); } @Override public boolean unregisterUpdateHandler(final IUpdateHandler pUpdateHandler) { if(this.mUpdateHandlers == null) { return false; } return this.mUpdateHandlers.remove(pUpdateHandler); } @Override public boolean unregisterUpdateHandlers(final IUpdateHandlerMatcher pUpdateHandlerMatcher) { if(this.mUpdateHandlers == null) { return false; } return this.mUpdateHandlers.removeAll(pUpdateHandlerMatcher); } @Override public int getUpdateHandlerCount() { if(this.mUpdateHandlers == null) { return 0; } return this.mUpdateHandlers.size(); } @Override public void clearUpdateHandlers() { if(this.mUpdateHandlers == null) { return; } this.mUpdateHandlers.clear(); } @Override public void registerEntityModifier(final IEntityModifier pEntityModifier) { if(this.mEntityModifiers == null) { this.allocateEntityModifiers(); } this.mEntityModifiers.add(pEntityModifier); } @Override public boolean unregisterEntityModifier(final IEntityModifier pEntityModifier) { if(this.mEntityModifiers == null) { return false; } return this.mEntityModifiers.remove(pEntityModifier); } @Override public boolean unregisterEntityModifiers(final IEntityModifierMatcher pEntityModifierMatcher) { if(this.mEntityModifiers == null) { return false; } return this.mEntityModifiers.removeAll(pEntityModifierMatcher); } @Override public int getEntityModifierCount() { if(this.mEntityModifiers == null) { return 0; } return this.mEntityModifiers.size(); } @Override public void clearEntityModifiers() { if(this.mEntityModifiers == null) { return; } this.mEntityModifiers.clear(); } @Override public float[] getSceneCenterCoordinates() { return this.convertLocalToSceneCoordinates(0, 0); } @Override public float[] getSceneCenterCoordinates(final float[] pReuse) { return this.convertLocalToSceneCoordinates(0, 0, pReuse); } @Override public Transformation getLocalToParentTransformation() { if(this.mLocalToParentTransformation == null) { this.mLocalToParentTransformation = new Transformation(); } final Transformation localToParentTransformation = this.mLocalToParentTransformation; if(this.mLocalToParentTransformationDirty) { localToParentTransformation.setToIdentity(); /* Scale. */ final float scaleX = this.mScaleX; final float scaleY = this.mScaleY; if((scaleX != 1) || (scaleY != 1)) { final float scaleCenterX = this.mScaleCenterX; final float scaleCenterY = this.mScaleCenterY; /* TODO Check if it is worth to check for scaleCenterX == 0 && scaleCenterY == 0 as the two postTranslate can be saved. * The same obviously applies for all similar occurrences of this pattern in this class. */ localToParentTransformation.postTranslate(-scaleCenterX, -scaleCenterY); localToParentTransformation.postScale(scaleX, scaleY); localToParentTransformation.postTranslate(scaleCenterX, scaleCenterY); } /* Skew. */ final float skewX = this.mSkewX; final float skewY = this.mSkewY; if((skewX != 0) || (skewY != 0)) { final float skewCenterX = this.mSkewCenterX; final float skewCenterY = this.mSkewCenterY; localToParentTransformation.postTranslate(-skewCenterX, -skewCenterY); localToParentTransformation.postSkew(skewX, skewY); localToParentTransformation.postTranslate(skewCenterX, skewCenterY); } /* Rotation. */ final float rotation = this.mRotation; if(rotation != 0) { final float rotationCenterX = this.mRotationCenterX; final float rotationCenterY = this.mRotationCenterY; localToParentTransformation.postTranslate(-rotationCenterX, -rotationCenterY); localToParentTransformation.postRotate(rotation); localToParentTransformation.postTranslate(rotationCenterX, rotationCenterY); } /* Translation. */ localToParentTransformation.postTranslate(this.mX, this.mY); this.mLocalToParentTransformationDirty = false; } return localToParentTransformation; } @Override public Transformation getParentToLocalTransformation() { if(this.mParentToLocalTransformation == null) { this.mParentToLocalTransformation = new Transformation(); } final Transformation parentToLocalTransformation = this.mParentToLocalTransformation; if(this.mParentToLocalTransformationDirty) { parentToLocalTransformation.setToIdentity(); /* Translation. */ parentToLocalTransformation.postTranslate(-this.mX, -this.mY); /* Rotation. */ final float rotation = this.mRotation; if(rotation != 0) { final float rotationCenterX = this.mRotationCenterX; final float rotationCenterY = this.mRotationCenterY; parentToLocalTransformation.postTranslate(-rotationCenterX, -rotationCenterY); parentToLocalTransformation.postRotate(-rotation); parentToLocalTransformation.postTranslate(rotationCenterX, rotationCenterY); } /* Skew. */ final float skewX = this.mSkewX; final float skewY = this.mSkewY; if((skewX != 0) || (skewY != 0)) { final float skewCenterX = this.mSkewCenterX; final float skewCenterY = this.mSkewCenterY; parentToLocalTransformation.postTranslate(-skewCenterX, -skewCenterY); parentToLocalTransformation.postSkew(-skewX, -skewY); parentToLocalTransformation.postTranslate(skewCenterX, skewCenterY); } /* Scale. */ final float scaleX = this.mScaleX; final float scaleY = this.mScaleY; if((scaleX != 1) || (scaleY != 1)) { final float scaleCenterX = this.mScaleCenterX; final float scaleCenterY = this.mScaleCenterY; parentToLocalTransformation.postTranslate(-scaleCenterX, -scaleCenterY); parentToLocalTransformation.postScale(1 / scaleX, 1 / scaleY); // TODO Division could be replaced by a multiplication of 'scale(X/Y)Inverse'... parentToLocalTransformation.postTranslate(scaleCenterX, scaleCenterY); } this.mParentToLocalTransformationDirty = false; } return parentToLocalTransformation; } @Override public Transformation getLocalToSceneTransformation() { if(this.mLocalToSceneTransformation == null) { this.mLocalToSceneTransformation = new Transformation(); } // TODO Cache if parent(recursive) not dirty. final Transformation localToSceneTransformation = this.mLocalToSceneTransformation; localToSceneTransformation.setTo(this.getLocalToParentTransformation()); final IEntity parent = this.mParent; if(parent != null) { localToSceneTransformation.postConcat(parent.getLocalToSceneTransformation()); } return localToSceneTransformation; } @Override public Transformation getSceneToLocalTransformation() { if(this.mSceneToLocalTransformation == null) { this.mSceneToLocalTransformation = new Transformation(); } // TODO Cache if parent(recursive) not dirty. final Transformation sceneToLocalTransformation = this.mSceneToLocalTransformation; sceneToLocalTransformation.setTo(this.getParentToLocalTransformation()); final IEntity parent = this.mParent; if(parent != null) { sceneToLocalTransformation.preConcat(parent.getSceneToLocalTransformation()); } return sceneToLocalTransformation; } /* (non-Javadoc) * @see org.andengine.entity.IEntity#convertLocalToSceneCoordinates(float, float) */ @Override public float[] convertLocalToSceneCoordinates(final float pX, final float pY) { return this.convertLocalToSceneCoordinates(pX, pY, Entity.VERTICES_LOCAL_TO_SCENE_TMP); } /* (non-Javadoc) * @see org.andengine.entity.IEntity#convertLocalToSceneCoordinates(float, float, float[]) */ @Override public float[] convertLocalToSceneCoordinates(final float pX, final float pY, final float[] pReuse) { final Transformation localToSceneTransformation = this.getLocalToSceneTransformation(); pReuse[Constants.VERTEX_INDEX_X] = pX; pReuse[Constants.VERTEX_INDEX_Y] = pY; localToSceneTransformation.transform(pReuse); return pReuse; } /* (non-Javadoc) * @see org.andengine.entity.IEntity#convertLocalToSceneCoordinates(float[]) */ @Override public float[] convertLocalToSceneCoordinates(final float[] pCoordinates) { return this.convertLocalToSceneCoordinates(pCoordinates, Entity.VERTICES_LOCAL_TO_SCENE_TMP); } /* (non-Javadoc) * @see org.andengine.entity.IEntity#convertLocalToSceneCoordinates(float[], float[]) */ @Override public float[] convertLocalToSceneCoordinates(final float[] pCoordinates, final float[] pReuse) { final Transformation localToSceneTransformation = this.getLocalToSceneTransformation(); pReuse[Constants.VERTEX_INDEX_X] = pCoordinates[Constants.VERTEX_INDEX_X]; pReuse[Constants.VERTEX_INDEX_Y] = pCoordinates[Constants.VERTEX_INDEX_Y]; localToSceneTransformation.transform(pReuse); return pReuse; } /* (non-Javadoc) * @see org.andengine.entity.IEntity#convertSceneToLocalCoordinates(float, float) */ @Override public float[] convertSceneToLocalCoordinates(final float pX, final float pY) { return this.convertSceneToLocalCoordinates(pX, pY, Entity.VERTICES_SCENE_TO_LOCAL_TMP); } /* (non-Javadoc) * @see org.andengine.entity.IEntity#convertSceneToLocalCoordinates(float, float, float[]) */ @Override public float[] convertSceneToLocalCoordinates(final float pX, final float pY, final float[] pReuse) { pReuse[Constants.VERTEX_INDEX_X] = pX; pReuse[Constants.VERTEX_INDEX_Y] = pY; this.getSceneToLocalTransformation().transform(pReuse); return pReuse; } /* (non-Javadoc) * @see org.andengine.entity.IEntity#convertSceneToLocalCoordinates(float[]) */ @Override public float[] convertSceneToLocalCoordinates(final float[] pCoordinates) { return this.convertSceneToLocalCoordinates(pCoordinates, Entity.VERTICES_SCENE_TO_LOCAL_TMP); } /* (non-Javadoc) * @see org.andengine.entity.IEntity#convertSceneToLocalCoordinates(float[], float[]) */ @Override public float[] convertSceneToLocalCoordinates(final float[] pCoordinates, final float[] pReuse) { pReuse[Constants.VERTEX_INDEX_X] = pCoordinates[Constants.VERTEX_INDEX_X]; pReuse[Constants.VERTEX_INDEX_Y] = pCoordinates[Constants.VERTEX_INDEX_Y]; this.getSceneToLocalTransformation().transform(pReuse); return pReuse; } @Override public void onAttached() { } @Override public void onDetached() { } @Override public Object getUserData() { return this.mUserData; } @Override public void setUserData(final Object pUserData) { this.mUserData = pUserData; } @Override public final void onDraw(final GLState pGLState, final Camera pCamera) { if(this.mVisible && !(this.mCullingEnabled && this.isCulled(pCamera))) { this.onManagedDraw(pGLState, pCamera); } } @Override public final void onUpdate(final float pSecondsElapsed) { if(!this.mIgnoreUpdate) { this.onManagedUpdate(pSecondsElapsed); } } @Override public void reset() { this.mVisible = true; this.mCullingEnabled = false; this.mIgnoreUpdate = false; this.mChildrenVisible = true; this.mChildrenIgnoreUpdate = false; this.mRotation = 0; this.mScaleX = 1; this.mScaleY = 1; this.mSkewX = 0; this.mSkewY = 0; this.mColor.reset(); if(this.mEntityModifiers != null) { this.mEntityModifiers.reset(); } if(this.mChildren != null) { final SmartList<IEntity> entities = this.mChildren; for(int i = entities.size() - 1; i >= 0; i--) { entities.get(i).reset(); } } } @Override public void dispose() { if(!this.mDisposed) { this.mDisposed = true; } else { throw new AlreadyDisposedException(); } } @Override protected void finalize() throws Throwable { super.finalize(); if(!this.mDisposed) { this.dispose(); } } @Override public String toString() { final StringBuilder stringBuilder = new StringBuilder(); this.toString(stringBuilder); return stringBuilder.toString(); } @Override public void toString(final StringBuilder pStringBuilder) { pStringBuilder.append(this.getClass().getSimpleName()); if((this.mChildren != null) && (this.mChildren.size() > 0)) { pStringBuilder.append(" ["); final SmartList<IEntity> entities = this.mChildren; for(int i = 0; i < entities.size(); i++) { entities.get(i).toString(pStringBuilder); if(i < (entities.size() - 1)) { pStringBuilder.append(", "); } } pStringBuilder.append("]"); } } // =========================================================== // Methods // =========================================================== /** * @param pGLState the currently active {@link GLState} i.e. to apply transformations to. * @param pCamera the currently active {@link Camera} i.e. to be used for culling. */ protected void preDraw(final GLState pGLState, final Camera pCamera) { } /** * @param pGLState the currently active {@link GLState} i.e. to apply transformations to. * @param pCamera the currently active {@link Camera} i.e. to be used for culling. */ protected void draw(final GLState pGLState, final Camera pCamera) { } /** * @param pGLState the currently active {@link GLState} i.e. to apply transformations to. * @param pCamera the currently active {@link Camera} i.e. to be used for culling. */ protected void postDraw(final GLState pGLState, final Camera pCamera) { } private void allocateEntityModifiers() { this.mEntityModifiers = new EntityModifierList(this, Entity.ENTITYMODIFIERS_CAPACITY_DEFAULT); } private void allocateChildren() { this.mChildren = new SmartList<IEntity>(Entity.CHILDREN_CAPACITY_DEFAULT); } private void allocateUpdateHandlers() { this.mUpdateHandlers = new UpdateHandlerList(Entity.UPDATEHANDLERS_CAPACITY_DEFAULT); } protected void onApplyTransformations(final GLState pGLState) { /* Translation. */ this.applyTranslation(pGLState); /* Rotation. */ this.applyRotation(pGLState); /* Skew. */ this.applySkew(pGLState); /* Scale. */ this.applyScale(pGLState); } protected void applyTranslation(final GLState pGLState) { pGLState.translateModelViewGLMatrixf(this.mX, this.mY, 0); } protected void applyRotation(final GLState pGLState) { final float rotation = this.mRotation; if(rotation != 0) { final float rotationCenterX = this.mRotationCenterX; final float rotationCenterY = this.mRotationCenterY; pGLState.translateModelViewGLMatrixf(rotationCenterX, rotationCenterY, 0); pGLState.rotateModelViewGLMatrixf(rotation, 0, 0, 1); pGLState.translateModelViewGLMatrixf(-rotationCenterX, -rotationCenterY, 0); /* TODO There is a special, but very likely case when mRotationCenter and mScaleCenter are the same. * In that case the last glTranslatef of the rotation and the first glTranslatef of the scale is superfluous. * The problem is that applyRotation and applyScale would need to be "merged" in order to efficiently check for that condition. */ } } protected void applySkew(final GLState pGLState) { final float skewX = this.mSkewX; final float skewY = this.mSkewY; if((skewX != 0) || (skewY != 0)) { final float skewCenterX = this.mSkewCenterX; final float skewCenterY = this.mSkewCenterY; pGLState.translateModelViewGLMatrixf(skewCenterX, skewCenterY, 0); pGLState.skewModelViewGLMatrixf(skewX, skewY); pGLState.translateModelViewGLMatrixf(-skewCenterX, -skewCenterY, 0); } } protected void applyScale(final GLState pGLState) { final float scaleX = this.mScaleX; final float scaleY = this.mScaleY; if((scaleX != 1) || (scaleY != 1)) { final float scaleCenterX = this.mScaleCenterX; final float scaleCenterY = this.mScaleCenterY; pGLState.translateModelViewGLMatrixf(scaleCenterX, scaleCenterY, 0); pGLState.scaleModelViewGLMatrixf(scaleX, scaleY, 1); pGLState.translateModelViewGLMatrixf(-scaleCenterX, -scaleCenterY, 0); } } protected void onManagedDraw(final GLState pGLState, final Camera pCamera) { pGLState.pushModelViewGLMatrix(); { this.onApplyTransformations(pGLState); final SmartList<IEntity> children = this.mChildren; if((children == null) || !this.mChildrenVisible) { /* Draw only self. */ this.preDraw(pGLState, pCamera); this.draw(pGLState, pCamera); this.postDraw(pGLState, pCamera); } else { if(this.mChildrenSortPending) { ZIndexSorter.getInstance().sort(this.mChildren); this.mChildrenSortPending = false; } final int childCount = children.size(); int i = 0; { /* Draw children behind this Entity. */ for(; i < childCount; i++) { final IEntity child = children.get(i); if(child.getZIndex() < 0) { child.onDraw(pGLState, pCamera); } else { break; } } } /* Draw self. */ this.preDraw(pGLState, pCamera); this.draw(pGLState, pCamera); this.postDraw(pGLState, pCamera); { /* Draw children in front of this Entity. */ for(; i < childCount; i++) { children.get(i).onDraw(pGLState, pCamera); } } } } pGLState.popModelViewGLMatrix(); } protected void onManagedUpdate(final float pSecondsElapsed) { if(this.mEntityModifiers != null) { this.mEntityModifiers.onUpdate(pSecondsElapsed); } if(this.mUpdateHandlers != null) { this.mUpdateHandlers.onUpdate(pSecondsElapsed); } if((this.mChildren != null) && !this.mChildrenIgnoreUpdate) { final SmartList<IEntity> entities = this.mChildren; final int entityCount = entities.size(); for(int i = 0; i < entityCount; i++) { entities.get(i).onUpdate(pSecondsElapsed); } } } private void assertEntityHasNoParent(final IEntity pEntity) throws IllegalStateException { if(pEntity.hasParent()) { final String entityClassName = pEntity.getClass().getSimpleName(); final String currentParentClassName = pEntity.getParent().getClass().getSimpleName(); final String newParentClassName = this.getClass().getSimpleName(); throw new IllegalStateException("pEntity '" + entityClassName +"' already has a parent: '" + currentParentClassName + "'. New parent: '" + newParentClassName + "'!"); } } // =========================================================== // Inner and Anonymous Classes // =========================================================== }