package gl.scenegraph; import geo.GeoObj; import gl.Color; import gl.HasColor; import gl.HasPosition; import gl.HasRotation; import gl.HasScale; import gl.LightSource; import gl.ObjectPicker; import gl.Renderable; import gl.animations.GLAnimation; import javax.microedition.khronos.opengles.GL10; import listeners.SelectionListener; import system.Container; import util.EfficientList; import util.Log; import util.Vec; import util.Wrapper; import worldData.Obj; import worldData.RenderableEntity; import worldData.Updateable; import android.opengl.Matrix; import commands.Command; import commands.undoable.UndoableCommand; /** * This is a subclass of {@link RenderableEntity} and it can be used for any * type of World Object which has a position ( {@link HasPosition} ), a * {@link Color}, a rotation ( {@link HasRotation} ) and a scale ( * {@link HasScale} ). <br> * It can have children {@link MeshComponent#addChild(RenderableEntity)} so also * a {@link Shape} or {@link LightSource} can have direct children if required. * A special type of child is the {@link GLAnimation} (which is a * {@link RenderableEntity} as well). * * @author Spobo * */ public abstract class MeshComponent implements RenderableEntity, SelectionListener, HasPosition, HasColor, HasRotation, HasScale { private static final String LOG_TAG = "MeshComp"; /** * positive x value is in east direction (along red axis) positive y value * is i north direction (along green axis) positive z value is in sky * direction */ protected Vec myPosition; /** * a vector that describes how the MeshComp is rotated. For example: * Vec(90,0,0) would rotate it 90 degree around the x axis */ private Vec myRotation; private Vec myScale; private Color myColor; private Color myPickColor; @Deprecated private boolean graficAnimationActive = true; private RenderableEntity myChildren; private Updateable myParent; private Command myOnClickCommand; private Command myOnLongClickCommand; private Command myOnMapClickCommand; private Command myOnDoubleClickCommand; /** * how to extract euler angles from a rotation matrix * http://paulbourke.net/geometry/eulerangle/ TODO provide a method for this * extraction */ private float[] markerRotationMatrix; /** * for now only used for marker detection */ public void setRotationMatrix(float[] rotationMatrix) { this.markerRotationMatrix = rotationMatrix; } @Override public Vec getRotation() { return myRotation; } @Override public Vec getScale() { return myScale; } @Override public void setScale(Vec scale) { if (myScale == null) myScale = scale.copy(); else myScale.setToVec(scale); } @Override public void setRotation(Vec rotation) { if (myRotation == null) myRotation = rotation.copy(); else myRotation.setToVec(rotation); } @Override public void setColor(Color c) { if (myColor == null) myColor = c.copy(); else myColor.setTo(c); } @Override public Color getColor() { return myColor; } /** * Example. An object at position 5,5,5 is rotated by an rotation matrix * (set via {@link MeshComponent#setRotationMatrix(float[])} and we want to * know there the point 0,0,1 (which normaly without rotation would be at * 5,5,6 ) is now. then we can use this method and pass 0,0,1 and we will * get the correct world coordinates * * @param modelSpaceCoords * @return the coordinates in the world system */ public Vec getWorldCoordsFromModelSpacePosition(Vec modelSpaceCoords) { float[] resultVec = new float[3]; float[] modelSpaceCoordsVec = { modelSpaceCoords.x, modelSpaceCoords.y, modelSpaceCoords.z }; Matrix.multiplyMV(resultVec, 0, markerRotationMatrix, 0, modelSpaceCoordsVec, 0); return new Vec(resultVec[0] + myPosition.x, resultVec[1] + myPosition.y, resultVec[2] + myPosition.z); } @Override public Vec getPosition() { if (myPosition == null) myPosition = new Vec(); return myPosition; } @Override public void setPosition(Vec position) { if (myPosition == null) myPosition = position.copy(); else myPosition.setToVec(position); } protected MeshComponent(Color canBeNull) { this.myColor = canBeNull; } /** * resize the Mesh equally in all 3 dimensions * * @param scaleRate */ public void scaleEqual(float scaleRate) { this.myScale = new Vec(scaleRate, scaleRate, scaleRate); } private void loadPosition(GL10 gl) { if (myPosition != null) gl.glTranslatef(myPosition.x, myPosition.y, myPosition.z); } private void loadRotation(GL10 gl) { if (markerRotationMatrix != null) { gl.glMultMatrixf(markerRotationMatrix, 0); } if (myRotation != null) { /* * this order is important. first rotate around the blue-z-axis * (like a compass) then the the green-y-axis and red-x-axis. the * order of the x and y axis rotations normaly is not important but * first x and then y is better in this case because of * Vec.calcRotationVec which may be extendet to add also a y * rotation which then would have to be rotated last to not make the * x-axis rotation wrong. so z x y is the best rotation order but * normaly z y x would work too: */ gl.glRotatef(myRotation.z, 0, 0, 1); gl.glRotatef(myRotation.x, 1, 0, 0); gl.glRotatef(myRotation.y, 0, 1, 0); } } private void setScale(GL10 gl) { if (myScale != null) gl.glScalef(myScale.x, myScale.y, myScale.z); } @Override public synchronized void render(GL10 gl, Renderable parent) { // store current matrix and then modify it: gl.glPushMatrix(); loadPosition(gl); setScale(gl); loadRotation(gl); if (ObjectPicker.readyToDrawWithColor) { if (myPickColor != null) { gl.glColor4f(myPickColor.red, myPickColor.green, myPickColor.blue, myPickColor.alpha); } else { Log.d("Object Picker", "Object " + this + " had no picking color"); } } else if (myColor != null) { gl.glColor4f(myColor.red, myColor.green, myColor.blue, myColor.alpha); } if (myChildren != null) { myChildren.render(gl, this); } draw(gl, parent); // restore old matrix: gl.glPopMatrix(); } /** * Don't override the {@link Renderable#render(GL10, Renderable)} method if * you are creating a subclass of {@link MeshComponent}. Instead implement * this method and all the translation and rotation abilities of the * {@link MeshComponent} will be applied automatically * * @param gl * @param parent * @param stack */ public abstract void draw(GL10 gl, Renderable parent); @Override public boolean update(float timeDelta, Updateable parent) { setMyParent(parent); if ((myChildren != null) && (graficAnimationActive)) { // if the animation does not need to be animated anymore.. if (!myChildren.update(timeDelta, this)) { // ..remove it: Log.d(LOG_TAG, myChildren + " will now be removed from mesh because it " + "is finished (returned false on update())"); myChildren = null; } } return true; } /** * when this is called the mesh can be selected and the onClick, * onLongCLick.. {@link UndoableCommand}s set for this mesh will be executed * if it is clicked */ public void enableMeshPicking() { enableMeshPicking(this); } /** * @param selectionInterface * can be the MeshComponent itself or another * {@link SelectionListener} to inform that instead (eg the * parent {@link Obj} or {@link GeoObj}) */ public void enableMeshPicking(SelectionListener selectionInterface) { Log.d(LOG_TAG, "Enabling picking for: " + this); // create a random picking color: Color c = Color.getRandomRGBColor(); if (myColor != null) { // if the mesh has a color, use this to avoid screen-flashing; c.copyValues(myColor); } Log.v(LOG_TAG, " > Sending " + c + " to ColorPicker"); Wrapper selectionsWrapper = new Wrapper(selectionInterface); myPickColor = ObjectPicker.getInstance().registerMesh( selectionsWrapper, c); Log.v(LOG_TAG, " > myPickColor=" + myPickColor); } @Override public Updateable getMyParent() { return myParent; } @Override public void setMyParent(Updateable parent) { myParent = parent; } public void getAbsoluteMeshPosition(Vec pos) { if (myPosition != null) { pos.add(myPosition); } Updateable p = getMyParent(); if (p instanceof MeshComponent) { ((MeshComponent) p).getAbsoluteMeshPosition(pos); } } @Override public Command getOnClickCommand() { // if (myOnClickCommand == null) // return getMyParentObj().getOnClickCommand(); return myOnClickCommand; } @Override public Command getOnLongClickCommand() { // if (myOnLongClickCommand == null) // return getMyParentObj().getOnLongClickCommand(); return myOnLongClickCommand; } @Override public Command getOnMapClickCommand() { // if (myOnMapClickCommand == null) // return getMyParentObj().getOnMapClickCommand(); return myOnMapClickCommand; } @Override public Command getOnDoubleClickCommand() { // if (myOnDoubleClickCommand == null) // return getMyParentObj().getOnDoubleClickCommand(); return myOnDoubleClickCommand; } @Override public void setOnClickCommand(Command c) { enableMeshPicking(this); myOnClickCommand = c; } @Override public void setOnDoubleClickCommand(Command c) { enableMeshPicking(this); myOnDoubleClickCommand = c; } @Override public void setOnLongClickCommand(Command c) { enableMeshPicking(this); myOnLongClickCommand = c; } /** * @param c * @param objToInform * set the {@link SelectionListener} manually (eg the parent * {@link Obj} */ public void setOnClickCommand(Command c, SelectionListener objToInform) { enableMeshPicking(objToInform); myOnClickCommand = c; } public void setOnDoubleClickCommand(Command c, SelectionListener objToInform) { enableMeshPicking(objToInform); myOnDoubleClickCommand = c; } public void setOnLongClickCommand(Command c, SelectionListener objToInform) { enableMeshPicking(objToInform); myOnLongClickCommand = c; } @Override public void setOnMapClickCommand(Command c) { myOnMapClickCommand = c; } @Override public MeshComponent clone() throws CloneNotSupportedException { Log.e("", "MeshComponent.clone() subclass missed, add it there"); return null; } /** * @param child */ public void addChild(RenderableEntity child) { addChildToTargetsChildGroup(this, child, false); } public static void addChildToTargetsChildGroup(MeshComponent target, RenderableEntity a, boolean insertAtBeginnung) { if (a == null) { Log.e(LOG_TAG, "Request to add NULL object as a child to " + target + " was denied!"); return; } if (target.myChildren == null) { target.myChildren = a; return; } if (!(target.myChildren instanceof RenderList)) { RenderList childrenGroup = new RenderList(); // keep the old animation: if (target.myChildren != null) { childrenGroup.add(target.myChildren); } // and change myChildren to the created group: target.myChildren = childrenGroup; } if (insertAtBeginnung) { ((RenderList) target.myChildren).insert(0, a); } else { ((RenderList) target.myChildren).add(a); } } /** * An animation will be inserted at the BEGINNING of the children list. So * the last animation added will be executed first by the renderer! * * @param animation */ public void addAnimation(GLAnimation animation) { addChildToTargetsChildGroup(this, animation, true); } /** * use {@link MeshComponent#removeAllChildren()} instead */ @Deprecated public void clearChildren() { myChildren = null; } /** * Removes all children from this {@link MeshComponent}, also all * {@link GLAnimation}s which might have been added via * {@link MeshComponent#addAnimation(GLAnimation)} */ // @Override TODO public void removeAllChildren() { myChildren = null; } // @Override TODO /** * Removes a child (this migth also be an {@link GLAnimation}) from the * {@link MeshComponent} * * @param meshToRemove * @return */ public boolean remove(RenderableEntity meshToRemove) { return find(meshToRemove, true); } // @Override TODO public boolean contains(RenderableEntity meshToFind) { return find(meshToFind, false); } // @Override TODO private boolean find(RenderableEntity entity, boolean andRemove) { if (myChildren == entity) { if (andRemove) clearChildren(); return true; } if (myChildren instanceof Container) { if (andRemove) return ((Container) myChildren).remove(entity); else return ((Container) myChildren).getAllItems().contains(entity) >= 0; } return false; } public void removeAllAnimations() { if (myChildren instanceof GLAnimation) this.clearChildren(); if (myChildren instanceof Container) removeAllElementsOfType(((Container) myChildren), GLAnimation.class); } /** * @param c * the collection to run through. Can be * {@link MeshComponent#getChildren()} for example if the child * is a collection (check via instanceof!) * @param classTypeToRemove * The class-type (like {@link GLAnimation}.class e.g.) If an * object of the specified class-type is found in the passed * {@link Container} it will be removes */ public static void removeAllElementsOfType(Container c, Class classTypeToRemove) { EfficientList list = c.getAllItems(); for (int i = 0; i < list.myLength; i++) { if (classTypeToRemove.isAssignableFrom(list.get(i).getClass())) { c.remove(list.get(i)); } } } public RenderableEntity getChildren() { return myChildren; } }