/*********************************************************************** * mt4j Copyright (c) 2008 - 2009, C.Ruff, Fraunhofer-Gesellschaft All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * ***********************************************************************/ package org.mt4j.components.visibleComponents.shapes; import java.util.ArrayList; import javax.media.opengl.GL; import org.apache.log4j.ConsoleAppender; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.SimpleLayout; import org.mt4j.components.MTComponent; import org.mt4j.components.TransformSpace; import org.mt4j.components.bounds.IBoundingShape; import org.mt4j.components.bounds.OrientedBoundingBox; import org.mt4j.components.visibleComponents.AbstractVisibleComponent; import org.mt4j.components.visibleComponents.GeometryInfo; import org.mt4j.input.gestureAction.DefaultDragAction; import org.mt4j.input.gestureAction.DefaultRotateAction; import org.mt4j.input.gestureAction.DefaultScaleAction; import org.mt4j.input.inputProcessors.IGestureEventListener; import org.mt4j.input.inputProcessors.componentProcessors.dragProcessor.DragProcessor; import org.mt4j.input.inputProcessors.componentProcessors.rotateProcessor.RotateProcessor; import org.mt4j.input.inputProcessors.componentProcessors.scaleProcessor.ScaleProcessor; import org.mt4j.util.MT4jSettings; import org.mt4j.util.MTColor; import org.mt4j.util.animation.Animation; import org.mt4j.util.animation.AnimationEvent; import org.mt4j.util.animation.AnimationManager; import org.mt4j.util.animation.IAnimationListener; import org.mt4j.util.animation.MultiPurposeInterpolator; import org.mt4j.util.math.ConvexQuickHull2D; import org.mt4j.util.math.Matrix; import org.mt4j.util.math.Ray; import org.mt4j.util.math.Tools3D; import org.mt4j.util.math.Vector3D; import org.mt4j.util.math.Vertex; import org.mt4j.util.opengl.GLTexture; import org.mt4j.util.opengl.GLTextureSettings; import org.mt4j.util.opengl.GLTexture.EXPANSION_FILTER; import org.mt4j.util.opengl.GLTexture.SHRINKAGE_FILTER; import org.mt4j.util.opengl.GLTexture.TEXTURE_TARGET; import org.mt4j.util.opengl.GLTexture.WRAP_MODE; import processing.core.PApplet; import processing.core.PConstants; import processing.core.PImage; /** * Abstract superclass for all kinds of shapes defined by vertices. * * @author Christopher Ruff */ public abstract class AbstractShape extends AbstractVisibleComponent { private static final Logger logger = Logger.getLogger(AbstractShape.class.getName()); static{ logger.setLevel(Level.ERROR); SimpleLayout l = new SimpleLayout(); ConsoleAppender ca = new ConsoleAppender(l); logger.addAppender(ca); } /** Default gesture actions. */ private static final IGestureEventListener defaultScaleAction = new DefaultScaleAction(); /** Default gesture actions*. */ private static final IGestureEventListener defaultRotateAction = new DefaultRotateAction(); /** Default gesture actions*. */ private static final IGestureEventListener defaultDragAction = new DefaultDragAction(); //Texture Stuff /** The texture enabled. */ private boolean textureEnabled; /** The texture mode. */ private int textureMode; // set defaults! /** The texture image. */ private PImage textureImage; /** The draw direct gl. */ private boolean drawDirectGL; /** The use vb os. */ private boolean useVBOs; /** The use display list. */ private boolean useDisplayList; /** The geometry of this shape. */ private GeometryInfo geometryInfo; // /** The bounds global vertices dirty. */ // private boolean boundsGlobalVerticesDirty; /** The vertices global. */ private Vertex[] verticesGlobal; /** global vertices dirty. */ private boolean globalVerticesDirty; /** * Creates a new shape with the vertices provided. * * @param vertices the vertices * @param pApplet the applet */ public AbstractShape(Vertex[] vertices, PApplet pApplet) { this(new GeometryInfo(pApplet, vertices), pApplet); } /** * Creates a new geometry with the geometryInfo provided. * * @param geometryInfo the geometry info * @param pApplet the applet */ public AbstractShape(GeometryInfo geometryInfo, PApplet pApplet) { super(pApplet,"unnamed AbstractShape", /*null,*/ null); //Initialize fields this.drawDirectGL = MT4jSettings.getInstance().isOpenGlMode()? true : false; this.useVBOs = false; this.useDisplayList = false; this.textureMode = PConstants.NORMALIZED; this.setFillDrawMode(GL.GL_TRIANGLE_FAN); // this.boundsGlobalVerticesDirty = true; this.boundsAutoCompute = true; this.setGeometryInfo(geometryInfo); //Default this.boundsBehaviour = BOUNDS_CHECK_THEN_GEOMETRY_CHECK; this.globalVerticesDirty = true;// this.setDefaultGestureActions(); } /* //FIXME TODO switch drawBounds! put draw() into IBoundingShape! @Override public void postDraw(PGraphics g) { super.postDraw(g); if (this.getBounds() instanceof OrientedBoundingBox){ OrientedBoundingBox b = (OrientedBoundingBox)this.getBounds(); b.drawBounds(g); } else if (this.getBounds() instanceof BoundsZPlaneRectangle){ BoundsZPlaneRectangle b = (BoundsZPlaneRectangle)this.getBounds(); b.drawBounds(g); } else if (this.getBounds() instanceof BoundingSphere){ BoundingSphere b = (BoundingSphere)this.getBounds(); b.drawBounds(g); } } */ /* //Test for drawing bounding shape aligned to coordinate axis, like getWidth/getHeightRelativeToParent would return @Override public void postDrawChildren(PGraphics g) { super.postDrawChildren(g); if (this.getBounds() instanceof BoundsZPlaneRectangle){ BoundsZPlaneRectangle b = (BoundsZPlaneRectangle)this.getBounds(); // b.drawBounds(g); g.pushMatrix(); g.pushStyle(); g.fill(250,150,150,180); Vector3D[] v = b.getVectorsGlobal(); float[] minMax = ToolsGeometry.getMinXYMaxXY(v); g.beginShape(); g.vertex(minMax[0], minMax[1], 0); g.vertex(minMax[2], minMax[1], 0); g.vertex(minMax[2], minMax[3], 0); g.vertex(minMax[0], minMax[3], 0); g.endShape(); // g.popStyle(); g.popMatrix(); } } */ //FIXME TEST public static boolean createDefaultGestures = true; /** * Assigns the default gesture to this component, drag, rotate, scale. * <br>Gets called in the constructor. * Can be overridden in subclasses to allow other/more default gestures. */ protected void setDefaultGestureActions(){ // /* if (createDefaultGestures){ this.registerInputProcessor(new RotateProcessor(this.getRenderer())); this.setGestureAllowance(RotateProcessor.class, true); // this.addGestureListener(RotateProcessor.class, defaultRotateAction); this.addGestureListener(RotateProcessor.class, new DefaultRotateAction()); this.registerInputProcessor(new ScaleProcessor(this.getRenderer())); this.setGestureAllowance(ScaleProcessor.class, true); // this.addGestureListener(ScaleProcessor.class, defaultScaleAction); this.addGestureListener(ScaleProcessor.class, new DefaultScaleAction()); this.registerInputProcessor(new DragProcessor(this.getRenderer())); this.setGestureAllowance(DragProcessor.class, true); // this.addGestureListener(DragProcessor.class, defaultDragAction); this.addGestureListener(DragProcessor.class, new DefaultDragAction()); } // */ } //////////////// BOUNDING STUFF /////////////////////////////// // /** The bounding shape. */ // private IBoundingShape boundingShape; /** The Constant BOUNDS_ONLY_CHECK. */ public static final int BOUNDS_ONLY_CHECK = 1; /** The Constant BOUNDS_CHECK_THEN_GEOMETRY_CHECK. */ public static final int BOUNDS_CHECK_THEN_GEOMETRY_CHECK = 2; /** The Constant BOUNDS_DONT_USE. */ public static final int BOUNDS_DONT_USE = 3; //FIXME RENAME, KEEP OLD ONES BUT AS DPERECATED // BOUNDSBEHAVIOUR_USE_GEOMETRY // BOUNDSBEHAVIOUR_USE_BOUNDS_AND_GEOMETRY // BOUNDSBEHAVIOUR_USE_BOUNDS // // /** The Constant BOUNDS_ONLY_CHECK. */ // public static final int BOUNDS_ONLY_CHECK = 1; // // /** The Constant BOUNDS_CHECK_THEN_GEOMETRY_CHECK. */ // public static final int BOUNDS_CHECK_THEN_GEOMETRY_CHECK = 2; // // /** The Constant BOUNDS_DONT_USE. */ // public static final int BOUNDS_DONT_USE = 3; /** The bounds picking behaviour. */ private int boundsBehaviour; /** The bounds auto compute. */ private boolean boundsAutoCompute; //TODO!! // @Override //INterface geometryNode mit getCenter getBounds etc? f�r Abstractshape und ShapeContainer? // public void addChild(AbstractShape b){ // // } /** * Sets the bounds behaviour. The behaviour influences * calculations in methods like <code>getIntersectionLocal</code> (used in picking) and * <code>getComponentContainsPointLocal</code>. * Allowed values are: * <ul> * <li><code>AbstractShape.BOUNDS_ONLY_CHECK</code> <br> * -> Uses the shape's bounding shape for the calculations <br> * => faster, more inaccurate * <li><code>AbstractShape.BOUNDS_DONT_USE</code> <br> * -> Uses the shape's geometry for the calculations <br> * => slower, more accurate * <li><code>AbstractShape.BOUNDS_CHECK_THEN_GEOMETRY_CHECK</code> <br> * -> Uses the shape's bounding shape first, and then also checks the geometry at picking. (Default)<br> * => compromise between the other two * </ul> * * @param boundsBehaviour the new bounds behaviour */ public void setBoundsBehaviour(int boundsBehaviour){ this.boundsBehaviour = boundsBehaviour; } /** * Gets the bounds behaviour. * * @return the bounds behaviour constant */ private int getBoundsBehaviour(){ return this.boundsBehaviour; } //FIXME REMOVE THESE METHODS IN THE NEXT VERSION! /** * Sets the bounds picking behaviour. * @param boundsPickingBehaviour the new bounds picking behaviour * * @deprecated * Method was renamed! Use setBoundsBehaviour()! */ public void setBoundsPickingBehaviour(int boundsPickingBehaviour){ this.boundsBehaviour = boundsPickingBehaviour; } //FIXME REMOVE THESE METHODS IN THE NEXT VERSION! /** * Gets the bounds picking behaviour. * * @return the bounds picking behaviour * @deprecated * Method was renamed! Use getBoundsBehaviour()! */ private int getBoundsPickingBehaviour(){ return this.boundsBehaviour; } // /** // * Sets the bounding shape. // * // * @param boundingShape the new bounding shape // */ // public void setBoundingShape(IBoundingShape boundingShape){ // this.boundingShape = boundingShape; // this.setBoundsGlobalDirty(true); // } // // /** // * Gets the bounding shape. // * // * @return the bounding shape // */ // public IBoundingShape getBounds(){ // return this.boundingShape; // } // // /** // * Checks if is bounding shape set. // * @return true, if is bounding shape set // */ // public boolean hasBounds(){ // return this.boundingShape != null; // } // // /** // * Sets the bounding shape. // * // * @param boundingShape the new bounding shape // */ // public void setBoundingShape(IBoundingShape boundingShape){ // super.setBoundingShape(boundingShape); // this.setBoundsGlobalDirty(true); // } // // //TODO REMOVE? // /** // * Sets the bounds global vertices dirty. // * // * @param boundsWorldVerticesDirty the new bounds world vertices dirty // */ // private void setBoundsGlobalDirty(boolean boundsWorldVerticesDirty) { // this.boundsGlobalVerticesDirty = boundsWorldVerticesDirty; // IBoundingShape bounds = this.getBounds(); // if (bounds != null){ // bounds.setGlobalBoundsChanged(); // } // } // @Override public void setMatricesDirty(boolean baseMatrixDirty) { /* * Overridden, so the component is also informed of the need to update * the bounds vertices */ if (baseMatrixDirty){ // this.setBoundsGlobalDirty(true); this.globalVerticesDirty = true; } // System.out.println("Set pickmat dirty on obj: " + this.getName()); super.setMatricesDirty(baseMatrixDirty); } /** * Sets the bounds auto compute. * * @param autoCompute the new bounds auto compute */ public void setBoundsAutoCompute(boolean autoCompute){ this.boundsAutoCompute = autoCompute; } /** * Checks if is bounds auto compute. * * @return true, if is bounds auto compute */ public boolean isBoundsAutoCompute(){ return this.boundsAutoCompute; } /** * Computes a default bounding box for the shape. * This gets called after setting creating a shape and its setGeometryInfo method is called. */ protected IBoundingShape computeDefaultBounds(){ return new OrientedBoundingBox(this); } ////////////////BOUNDING STUFF /////////////////////////////// /** * Sets a new geometryInfo with new vertices for this shape. * <br>If running in OpenGL mode, this also creates new vertex buffers * for openGL use and eventually new Vertex Buffer Objects or * Displaylists depending on the objects settings! * So DONT create them (buffers or vbos) on the geometryinfo yourself manually, * prior to setting it here! * <br>Also calls computeDefaultBounds() if setAutoComputeBounds() is true (default) * to recreate the bounding shape. * <br><strong>NOTE:</strong> Be aware, that an old geometryinfo of this shape may have * created VBOs or displaylists on the gfx card which we should delete if not needed * anywhere else! * * @param geometryInfo the geometry info */ public void setGeometryInfo(GeometryInfo geometryInfo){ if (this.isUseDirectGL()){ if (geometryInfo.getVertBuff() == null || geometryInfo.getStrokeColBuff() == null){ //new geometryinfo has no drawbuffers created yet -> create them! geometryInfo.generateOrUpdateBuffersLocal(this.getStyleInfo()); }else if (this.geometryInfo != null && geometryInfo.equals(this.geometryInfo)){ // old geometryinfo is the same than the new one -> assumimg change -> create new buffers! geometryInfo.generateOrUpdateBuffersLocal(this.getStyleInfo()); }else{ //the new geometryinfo already has opengl draw buffers and //the old geometryinfo is null or not the same as the new one //-> just use the new geometry's buffers without recreating! //TODO do the same check with displaylists and vbos!??! // } if (this.isUseVBOs()){ geometryInfo.generateOrUpdateAllVBOs(); } if (this.isUseDisplayList()){ geometryInfo.generateDisplayLists( this.isTextureEnabled(), this.getTexture(), this.getFillDrawMode(), this.isDrawSmooth(), this.getStrokeWeight()); } } this.geometryInfo = geometryInfo; if (this.isBoundsAutoCompute()){ if (geometryInfo.getVertices().length >= 3){ this.setBounds(this.computeDefaultBounds()); }else{ // logger.error("Warning: could not compute bounds because too few vertices were supplied: " + this.getName() + " in " + this + " -> Setting boundingShape to null."); this.setBounds(null); } }else{ this.setBounds(null); } //Sets the base matrix dirty, so that when inquiring info about //vertices, they get updated first // this.setLocalBaseMatrixDirty(true); this.globalVerticesDirty = true; } /** * Gets the geometry info. The geometryinfo contains the * geometric information of this shape by managing the shapes * vertices, OpenGL vertex buffer objects and OpenGL display list. * * @return the geometry info * * the geometry information object of that shape */ public GeometryInfo getGeometryInfo() { return this.geometryInfo; } /** * Sets new vertices for that shape. * and generates new vertex arrays for opengl mode. * <li>Re-computes and sets the shapes default bounding shape. * * @param vertices the vertices */ public void setVertices(Vertex[] vertices){ this.getGeometryInfo().reconstruct( vertices, this.getGeometryInfo().getNormals(), this.getGeometryInfo().getIndices(), this.isUseDirectGL(), this.isUseVBOs(), this.getStyleInfo()); if (this.isBoundsAutoCompute()){ if (geometryInfo.getVertices().length >= 3){ this.setBounds(this.computeDefaultBounds()); }else{ // logger.error("Warning: could not compute bounds because too few vertices were supplied: " + this.getName() + " in " + this + " -> Setting boundingShape to null."); this.setBounds(null); } }else{ this.setBounds(null); } //Sets the base matrix dirty, so that when inquiring info about //vertices, they get updated first // this.setLocalBaseMatrixDirty(true); this.globalVerticesDirty = true; } /** * Returns the vertices of this shape without any transformations applied * <br> <b>Caution:</b> If you alter them in anyway, changes will only * be consistent by calling the <code>setVertices(Vertex[])</code> method with the changes vertices * as an argument!. * * @return the untransformed vertices */ public Vertex[] getVerticesLocal(){ return this.getGeometryInfo().getVertices(); } /** * Returns the vertices of this shape in real world (global) coordinates * <br> <b>Caution:</b> If you alter them in anyway, changes will only * be consistent if you call the setVertices() method of the shape. * <br><b>Caution:</b>This operation is not cheap since all vertices are * first copied and then transformed! * <br><b>Note:</b> if a shape as a lot of vertices this will increase memory usage considerably * because a complete copy of the shapes vertices is made and kept! * @return the vertices in global coordinate space */ public Vertex[] getVerticesGlobal(){ this.updateVerticesGlobal(); return this.verticesGlobal; } /** * Updates the verticesglobal array of the shape by * multipying them with the current shape's global matrix.<br> * <br>This calculates the real world space coordinates and saves it * in the verticesglobal array. These vertices can be used to test at picking * or just to know the real world global coordinates of the vertices. */ private void updateVerticesGlobal(){ if (this.globalVerticesDirty){ Vertex[] unTransformedCopy = Vertex.getDeepVertexArrayCopy(this.getGeometryInfo().getVertices()); //transform the copied vertices and save them in the vertices array this.verticesGlobal = Vertex.transFormArray(this.getGlobalMatrix(), unTransformedCopy); this.globalVerticesDirty = false; } } /** * Gets the vertex count. * * @return the vertex count * * the number of vertices for that shape */ public int getVertexCount(){ return this.getGeometryInfo().getVertexCount(); } /** * Checks if is use direct gl. * * @return true, if checks if is use direct gl * * true, if the shape tries to draw itself with OpenGL commands * rather than processing commands */ public boolean isUseDirectGL() { return this.drawDirectGL; } /** * If set to true - which is the default if using the OpenGL render mode - * this shape will bypass processings rendering pipeline * and use the OpenGL context directly for performance increases.<br> * Setting this to false forces the use of the processing renderer. * <p> * If this is set to true, and additionally, setUseVBOs() is set to true, * the shape is drawn by using vertex buffer objects (VBO). <br> * By calling setUseDisplayList(true) it is drawn using display lists. * * @param drawPureGL the draw pure gl */ public void setUseDirectGL(boolean drawPureGL){ if (MT4jSettings.getInstance().isOpenGlMode()){ if ( !this.isUseDirectGL() && drawPureGL //FIXME WHY WAS THIS MISSING? is there a reason not to put this condition? && this.getGeometryInfo().getVertices() != null && this.getGeometryInfo().getVertexCount() > 0){ //Generate buffers for opengl array use this.getGeometryInfo().generateOrUpdateBuffersLocal(this.getStyleInfo()); } this.drawDirectGL = drawPureGL; //Wrap the current texture into a gl texture object for openGl use if (this.drawDirectGL && this.getTexture() != null // && !(this.getTexture() instanceof MTTexture) //dont do so that tex coords get updated if previously set GLTexture was set with no directGL mode ){ this.setTexture(this.getTexture()); } }else{ logger.error(this.getName() + " - Cant use direct GL mode if not in opengl mode! Object: " + this.getName()); this.drawDirectGL = false; } } /** * Checks if this shape is drawn using VBOs. * * @return true, if checks if is use vbos * * true, if the shape tries to draw itself with OpenGL Vertex Buffer Objects */ public boolean isUseVBOs() { return this.useVBOs; } /** * <br>Tries to use Vertex Buffer Objects for displaying this shape.<br> * You have to be in OpenGL mode and set <code>setDrawDirectGL(true)</code>first. * * @param useVBOs the use vb os */ public void setUseVBOs(boolean useVBOs) { if (MT4jSettings.getInstance().isOpenGlMode() && this.isUseDirectGL()){ if (!this.isUseVBOs()){ this.getGeometryInfo().generateOrUpdateAllVBOs(); } this.useVBOs = useVBOs; }else{ logger.error(this.getName() + " - Cant use VBOs if not in opengl mode and setDrawDirectGL has to be set to true! Object: " + this.getName()); this.useVBOs = false; } } /** * Checks if is use display list. * * @return true, if checks if is use display list * * true, if the shape tries to draw itself with OpenGL display lists */ public boolean isUseDisplayList() { return this.useDisplayList; } /** * Tries to use a opengl display list for rendering this shape.<br> * You have to be in OpenGL mode and <code>setDrawDirectGL()</code> has to * be set to "true" first! * <br><strong>NOTE: </strong> the display list has to be created first * to use it! This can be done by calling <code>generateDisplayLists</code>. * Instead of these 2 steps we can also just call <code>generateAndUseDisplayLists()</code> * <br><strong>NOTE: </strong> if the shape was using a display list before we should delete it before setting * a new one! * * @param useDisplayList the use display list */ public void setUseDisplayList(boolean useDisplayList) { if (MT4jSettings.getInstance().isOpenGlMode() && this.isUseDirectGL()){ this.useDisplayList = useDisplayList; if (this.getGeometryInfo().getDisplayListIDs()[0] == -1 && this.getGeometryInfo().getDisplayListIDs()[1] == -1 ){ logger.warn(this.getName() + " - Warning, no displaylists created yet on component: " + this.getName()); } }else{ if (useDisplayList) logger.warn(this.getName() + " - Cant set display lists if not in opengl mode and setDrawDirectGL has to be set to true! Object: " + this.getName()); this.useDisplayList = false; } } /** * Generates 2 openGL display lists for drawing this shape. * <br>One for the interior (with textures etc.) and * one for drawing the outline. * <br><code>setUseDirectGL</code> has to be set to true first! * <br>To use the display lists for drawing, call <code>setUseDisplayList()</code> * This method only generates them! * <br><strong>NOTE: </strong> if the shape was using a display list before we should delete it before setting * a new one! */ public void generateDisplayLists(){ if (MT4jSettings.getInstance().isOpenGlMode() && this.isUseDirectGL()){ this.getGeometryInfo().generateDisplayLists( this.isTextureEnabled(), this.getTexture(), this.getFillDrawMode(), this.isDrawSmooth(), this.getStrokeWeight()); }else{ logger.error(this.getName() + " - Cannot create displaylist if not in openGL mode or if setUseDirectGL() hasnt been set to true!"); } } /** * Generates and uses openGL display lists for drawing this * shape. */ public void generateAndUseDisplayLists(){ this.generateDisplayLists(); this.setUseDisplayList(true); } /** * Deletes the displaylists of the object and sets * setUseDisplayList() to false. */ public void disableAndDeleteDisplayLists(){ this.getGeometryInfo().deleteDisplayLists(); this.setUseDisplayList(false); } //FIXME move to TOols3D!? /** * Calculates the 2D XY convex hull for this shape. * * @return the convex hull xy global */ public Vector3D[] getConvexHullXYGlobal(){ ArrayList<Vector3D> vers = new ArrayList<Vector3D>(); Vertex[] transVerts = this.getVerticesGlobal(); for (int i = 0; i < transVerts.length; i++) { Vertex vertex = transVerts[i]; vers.add(vertex); } ArrayList<Vector3D> edgeList = ConvexQuickHull2D.getConvexHull2D(vers); return (edgeList.toArray(new Vertex[edgeList.size()])); } @Override public void setFillColor(MTColor color) { super.setFillColor(color); this.getGeometryInfo().setVerticesColorAll(color.getR(), color.getG(), color.getB(), color.getAlpha()); } @Override public void setStrokeColor(MTColor strokeColor) { super.setStrokeColor(strokeColor); if (MT4jSettings.getInstance().isOpenGlMode() && this.isUseDirectGL()) this.getGeometryInfo().setStrokeColorAll(strokeColor.getR(), strokeColor.getG(), strokeColor.getB(), strokeColor.getAlpha()); } /** * Tells the shape to use its texture. * A texture has to be set previously! * * @param texture the texture */ public void setTextureEnabled(boolean texture){ this.textureEnabled = texture; } /** * Checks if is texture enabled. * * @return true, if checks if is texture enabled * * true, if the shape is to use a texture */ public boolean isTextureEnabled(){ return this.textureEnabled; } /** * Sets a texture for this shape. * <br>Uses the texture coordinates in the provided vertices for drawing. * <br>If openGL mode is used, it also creates a GLTexture object. * <br>For best compatibility, power of two texture dimensions should be provided. * If the provided texture is non power of two and you are in opengl mode, we try * to use the RECTANGULAR texture extension. * <br>If textures were disabled for this component, they are being enabled again. * * @param newTexImage the new tex image */ public void setTexture(PImage newTexImage){ if (newTexImage == null){ this.textureImage = null; this.setTextureEnabled(false); // System.out.println("Set texture to null"); return; } //TODO AbstractShape.updateGLTextureCoordinates(); ? //-> updateTextureBuffer still needed if custom tex coords wanted //-> use before setTexture()! //TODO delete old GLTexture object??? may cause memory leak if not! //But how to know if its not needed elsewhere? register in GLTexture object when its used and not? //TODO make sure that NORMALIZED texture coords are supplied and BEFORE setting the texture! //TODO Note that if we want to change the tex coords mannually, do it normalized, then for precaution update the buffer and then set the texture //if the tex coords have to be un/normalized the updating is done twice but else we might miss updating it when we update from POT to POT.. //maybe make method updateTextureCoords() - maybe also call setTexture to un/normalize() if (!this.isTextureEnabled()) this.setTextureEnabled(true); if (lastTextureDimension.equalsVector(Vector3D.ZERO_VECTOR)){ lastTextureDimension.setXYZ(newTexImage.width, newTexImage.height, 0); } // if (MT4jSettings.getInstance().isOpenGlMode()){ //FIXME USE WHICH ONE? if (this.isUseDirectGL()){ if (newTexImage instanceof GLTexture) { GLTexture glTex = (GLTexture) newTexImage; if (glTex.getTextureTargetEnum() == TEXTURE_TARGET.RECTANGULAR){ this.setTextureMode(PConstants.IMAGE); if (this.getGeometryInfo().isTextureCoordsNormalized()){ //0..1 -> 0..width this.unNormalizeFromPOTtoRectMode(newTexImage, this.getVerticesLocal()); this.getGeometryInfo().setTextureCoordsNormalized(false); }else{ //0..oldWidth -> 0..newWidth GLTexture is NPOT but this component's texture coords have seemingly already been un-normalized //FIXME dont do it if it has the same dimensions! this.fromRectModeToRectMode(newTexImage, this.getVerticesLocal(), this.lastTextureDimension.x, this.lastTextureDimension.y); } }else{ //GLTexture is POT -> normalize tex coords if neccessary this.setTextureMode(PConstants.NORMALIZED); if (this.getGeometryInfo().isTextureCoordsNormalized()){ //0..1 -> 0..1 }else{ //0..width -> 0..1 this.normalizeFromRectMode(newTexImage, this.getVerticesLocal(), this.lastTextureDimension.x, this.lastTextureDimension.y); this.getGeometryInfo().setTextureCoordsNormalized(true); } } this.textureImage = newTexImage; //FIXME always save last tex dimensions? also if POT? this.lastTextureDimension.setXYZ(newTexImage.width, newTexImage.height, 0); }else{ //We are in OpenGL mode but the new texture is not a GLTexture -> create new GLTexture from PImage boolean isPOT = Tools3D.isPowerOfTwoDimension(newTexImage); GLTextureSettings ts = new GLTextureSettings(); if (!isPOT){ ts.target = TEXTURE_TARGET.RECTANGULAR; this.setTextureMode(PConstants.IMAGE); if (this.getGeometryInfo().isTextureCoordsNormalized()){ //0..1 -> 0..newWidth this.unNormalizeFromPOTtoRectMode(newTexImage, this.getVerticesLocal()); this.getGeometryInfo().setTextureCoordsNormalized(false); }else{ //0..oldWidth -> 0..newWidth //FIXME dont do it if it has the same dimensions! this.fromRectModeToRectMode(newTexImage, this.getVerticesLocal(), this.lastTextureDimension.x, this.lastTextureDimension.y); } this.textureImage = newTexImage; this.lastTextureDimension.setXYZ(newTexImage.width, newTexImage.height, 0); }else{ ts.target = TEXTURE_TARGET.TEXTURE_2D; this.setTextureMode(PConstants.NORMALIZED); //We are in OpenGL mode, new texture is a PImage, is POT -> create POT GLTexture and un-normalize tex coords if neccessary if (this.getGeometryInfo().isTextureCoordsNormalized()){ //0..1 -> 0..1 }else{ //normalize 0..width -> 0..1 this.normalizeFromRectMode(newTexImage, this.getVerticesLocal(), this.lastTextureDimension.x, this.lastTextureDimension.y); this.getGeometryInfo().setTextureCoordsNormalized(true); } } //Create new GLTexture from PImage ts.shrinkFilter = SHRINKAGE_FILTER.BilinearNoMipMaps; ts.expansionFilter = EXPANSION_FILTER.Bilinear; ts.wrappingHorizontal = WRAP_MODE.CLAMP_TO_EDGE; ts.wrappingVertical = WRAP_MODE.CLAMP_TO_EDGE; GLTexture newGLTexture = new GLTexture(this.getRenderer(), newTexImage, ts); // newGLTexture.format = newTexImage.format; //FIXME REMOVE? this.textureImage = newGLTexture; this.lastTextureDimension.setXYZ(newTexImage.width, newTexImage.height, 0); } }else{ //We dont use OpenGL -> just set the PImage texture this.textureImage = newTexImage; this.lastTextureDimension.setXYZ(newTexImage.width, newTexImage.height, 0); } } //TODO save the set texture dimensions so we can always scale from one NPOT texture to another NPOT texture coords //(even if texture was set to null in between) private Vector3D lastTextureDimension = new Vector3D(); private void unNormalizeFromPOTtoRectMode(PImage newTexture, Vertex[] verts){ for (int i = 0; i < verts.length; i++) { Vertex vertex = verts[i]; vertex.setTexCoordU(vertex.getTexCoordU() * (float)newTexture.width); vertex.setTexCoordV(vertex.getTexCoordV() * (float)newTexture.height); // System.out.println("TexU:" + vertex.getTexCoordU() + " TexV:" + vertex.getTexCoordV()); //FIXME REMOVE } this.getGeometryInfo().updateTextureBuffer(this.isUseVBOs()); } private void normalizeFromRectMode(PImage newTexture, Vertex[] verts, float oldTexWidth, float oldTexHeight){ for (int i = 0; i < verts.length; i++) { Vertex vertex = verts[i]; // vertex.setTexCoordU(ToolsMath.map(vertex.getTexCoordU(), 0, oldTexWidth, 0, 1)); // vertex.setTexCoordV(ToolsMath.map(vertex.getTexCoordV(), 0, oldTexWidth, 0, 1)); vertex.setTexCoordU(vertex.getTexCoordU() / oldTexWidth); vertex.setTexCoordV(vertex.getTexCoordV() / oldTexHeight); } this.getGeometryInfo().updateTextureBuffer(this.isUseVBOs()); } private void fromRectModeToRectMode(PImage newTexture, Vertex[] verts, float oldTexWidth, float oldTexHeight){ for (int i = 0; i < verts.length; i++) { Vertex vertex = verts[i]; vertex.setTexCoordU( (vertex.getTexCoordU() / oldTexWidth) * (float)newTexture.width); vertex.setTexCoordV( (vertex.getTexCoordV() / oldTexHeight) * (float)newTexture.height); } this.getGeometryInfo().updateTextureBuffer(this.isUseVBOs()); } /** * Gets the texture. * * @return the texture * * the texture object associated with this shape (either a PImage or GLTexture obj) */ public PImage getTexture() { return this.textureImage; } /** * Sets the way texture coordinates are handled in processing. This setting * is not considered if using OpenGL mode! * Allowed values are: <code>PApplet.NORMALIZED</code> and <code>PApplet.IMAGE</code> * <br>Default is <code>PApplet.NORMALIZED</code>. * Which indicates that the texture coordinates should be in normalized * range from 0.0 to 1.0! * In image mode they have to range from 0..imageDimensions. * * @param textureMode the texture mode */ public void setTextureMode(int textureMode){ this.textureMode = textureMode; } /** * Gets the processing texture mode. * * @return the texture mode */ public int getTextureMode(){ return this.textureMode; } /** * Sets the global position of the component. (In global coordinates) * * @param pos the pos */ public void setPositionGlobal(Vector3D pos){ this.translateGlobal(pos.getSubtracted(this.getCenterPointGlobal())); } /** * Sets the position of the component, relative to its parent coordinate frame. * * @param pos the pos */ public void setPositionRelativeToParent(Vector3D pos){ this.translate(pos.getSubtracted(this.getCenterPointRelativeToParent()), TransformSpace.RELATIVE_TO_PARENT); } /** * Sets the position of this component, relative to the other specified component. * * @param otherComp the other comp * @param pos the pos */ public void setPositionRelativeToOther(MTComponent otherComp, Vector3D pos){ Matrix m0 = MTComponent.getTransformToDestinationLocalSpace(otherComp, this); pos.transform(m0); Vector3D centerpointGlobal = this.getCenterPointGlobal(); centerpointGlobal.transform(this.getGlobalInverseMatrix()); //to localobj space centerpointGlobal.transform(this.getLocalMatrix()); //to parent relative space Vector3D diff = pos.getSubtracted(centerpointGlobal); this.translate(diff, TransformSpace.RELATIVE_TO_PARENT); // Vector3D globalCenter = this.getCenterPointGlobal(); // // Vector3D localObjCenter = this.getCenterPointGlobal(); // localObjCenter.transform(this.getAbsoluteWorldToLocalMatrix()); //to localobj space //// localObjCenter.transform(this.getLocalBasisMatrix()); //to parent relative space // // //// pos = MTBaseComponent.getWorldVecToParentRelativeSpace(otherComp, pos); //// pos.transform(otherComp.getLocalBasisMatrix()); //// pos = MTBaseComponent.getWorldVecToLocalRelativeSpace(otherComp, pos); // //// pos.transform(otherComp.getAbsoluteLocalToWorldMatrix()); // // Matrix m = MTBaseComponent.getTransformToDestinationLocalSpace(this, otherComp); //// m.multLocal(otherComp.getLocalBasisMatrix()); // Matrix m2 = MTBaseComponent.getTransformToDestinationParentSpace(this, otherComp); //// pos.transform(m); // // /* // //// localObjCenter.transform(m2); //// System.out.println("Localobjcenter transformed to other: " + localObjCenter); // // Vector3D diff = pos.minus(localObjCenter); // diff.transformDirectionVector(m2); // this.translateGlobal(diff); // */ // // //// pos.transform(otherComp.getAbsoluteLocalToWorldMatrix()); //// localObjCenter.transform(this.getAbsoluteLocalToWorldMatrix()); //// localObjCenter.transform(otherComp.getAbsoluteWorldToLocalMatrix()); //// Vector3D diff = pos.minus(localObjCenter); // // Vector3D centerPoint = this.getBounds().getCenterPointLocal(); //// centerPoint.transform(this.getLocalBasisMatrix()); // // //Muss punkt nach svgComp space transformieren, da diese comp gescaled wird // centerPoint.transform(m); // pos.transform(otherComp.getAbsoluteLocalToWorldMatrix()); // Vector3D diff = pos.minus(centerPoint); // // System.out.println("Point from where to start: " + localObjCenter); // System.out.println("Destination pos: " + pos); // System.out.println("Translation vector: " + diff); //// Vector3D diff = pos.minus(localObjCenter); //// diff.transformDirectionVector(m); // // this.translate(diff, TransformSpace.RELATIVE_TO_PARENT); } /* (non-Javadoc) * @see org.mt4j.components.visibleComponents.AbstractVisibleComponent#getIntersectionLocal(org.mt4j.util.math.Ray) */ @Override public Vector3D getIntersectionLocal(Ray ray) { //TODO be aware that we dont call the super implementation here.. switch (this.getBoundsBehaviour()) { case AbstractShape.BOUNDS_DONT_USE: // System.out.println("\"" + this.getName() + "\": -> GEOMETRY only check"); return this.getGeometryIntersectionLocal(ray); case AbstractShape.BOUNDS_ONLY_CHECK: if (this.hasBounds()){ // System.out.println("\"" + this.getName() + "\": -> BOUNDS only check"); return this.getBounds().getIntersectionLocal(ray); }else{ // System.out.println("\"" + this.getName() + "\": -> GEOMETRY only check"); return this.getGeometryIntersectionLocal(ray); } case AbstractShape.BOUNDS_CHECK_THEN_GEOMETRY_CHECK: if (this.hasBounds()){ // System.out.println("\"" + this.getName() + "\": -> BOUNDS check then GEOMETRY check"); Vector3D boundsIntersection = this.getBounds().getIntersectionLocal(ray); if (boundsIntersection != null){ return this.getGeometryIntersectionLocal(ray); }else{ return null; } }else{ return this.getGeometryIntersectionLocal(ray); } default: break; } return null; } @Override protected boolean componentContainsPointLocal(Vector3D testPoint) { switch (this.getBoundsBehaviour()) { case AbstractShape.BOUNDS_DONT_USE: // System.out.println("\"" + this.getName() + "\": -> GEOMETRY only check"); return this.isGeometryContainsPointLocal(testPoint); case AbstractShape.BOUNDS_ONLY_CHECK: if (this.hasBounds()){ // System.out.println("\"" + this.getName() + "\": -> BOUNDS only check"); return this.getBounds().containsPointLocal(testPoint); }else{ // System.out.println("\"" + this.getName() + "\": -> GEOMETRY only check"); return this.isGeometryContainsPointLocal(testPoint); } case AbstractShape.BOUNDS_CHECK_THEN_GEOMETRY_CHECK: if (this.hasBounds()){ // System.out.println("\"" + this.getName() + "\": -> BOUNDS check then GEOMETRY check"); if (this.getBounds().containsPointLocal(testPoint)){ return this.isGeometryContainsPointLocal(testPoint); }else{ return false; } }else{ return this.isGeometryContainsPointLocal(testPoint); } default: break; } return false; } /** * Tests if the ray intersects the shape and where. * The ray is assumed to be transformed to local space already! * * @param ray the ray * * @return the geometry intersection * * the intersection point or null if no intersection occured */ abstract public Vector3D getGeometryIntersectionLocal(Ray ray); /** * Tests is the geometry of the shape contains the given point. * The testpoint is assumed to be transformed to local space already! * * @param testPoint the test point * * @return true, if checks if is geometry contains point */ abstract public boolean isGeometryContainsPointLocal(Vector3D testPoint); /** * Gets the center point global. * First it gets the local center and then transforms it to the global frame. * * @return the center point global * * the center of this shape in global coordinates */ public final Vector3D getCenterPointGlobal(){ Vector3D center = this.getCenterPointLocal(); center.transform(this.getGlobalMatrix()); return center; } /** * Gets the center point relative to parent. * First it gets the local center and then transforms it to the parent frame. * @return the center of this shape in coordinates relative to the shapes parent coordiante frame. */ public final Vector3D getCenterPointRelativeToParent(){ Vector3D center = this.getCenterPointLocal(); center.transform(this.getLocalMatrix()); return center; } /** * Gets the center point in local object space. * This should always return a COPY of the centerpoint of the implementing shape * since the point may get transformed afterwards. * @return the center point of this shape in untransformed local object coordinates. */ abstract public Vector3D getCenterPointLocal(); // // /** // * Get the width of the shape in the XY-Plane. Uses the x and y coordinate // * values for calculation. Usually the calculation is delegated to the shapes // * bounding shape. // * // * @param transformSpace the space the width is calculated in, can be global space, parent relative- or object space // * // * @return the width xy // * // * the width // */ // abstract public float getWidthXY(TransformSpace transformSpace); // // /** // * Get the height of the shape in the XY-Plane. Uses the x and y coordinate // * values for calculation. Usually the calculation is delegated to the shapes // * bounding shape. // * // * @param transformSpace the space the width is calculated in, can be world space, parent relative- or object space // * // * @return the height xy // * // * the height // */ // abstract public float getHeightXY(TransformSpace transformSpace); // /** * Get the height of the shape in the XY-Plane. Uses the x and y coordinate * values for calculation. Usually the calculation is delegated to the shapes * bounding shape. * * @param transformSpace the space the width is calculated in, can be world space, parent relative- or object space * * @return the height xy * * the height */ public float getHeightXY(TransformSpace transformSpace) { switch (transformSpace) { case LOCAL: return this.getHeightXYLocal(); case RELATIVE_TO_PARENT: return this.getHeightXYRelativeToParent(); case GLOBAL: return this.getHeightXYGlobal(); default: return -1; } } /** * Gets the height xy obj space. * @return the height xy obj space */ private float getHeightXYLocal() { return this.getHeightXYVectLocal().length(); } /** * Gets the "height vector" and transforms it to parent relative space, then calculates * its length. * * @return the height xy relative to parent * * the height relative to its parent space frame */ protected float getHeightXYRelativeToParent() { if (this.hasBounds()){ return this.getBounds().getHeightXY(TransformSpace.RELATIVE_TO_PARENT); }else{ OrientedBoundingBox tempBounds = new OrientedBoundingBox(this); return tempBounds.getHeightXY(TransformSpace.RELATIVE_TO_PARENT); } } /** * Gets the "height vector" and transforms it to world space, then calculates * its length. * * @return the height xy global * * the height relative to the world space */ protected float getHeightXYGlobal() { if (this.hasBounds()){ return this.getBounds().getHeightXY(TransformSpace.GLOBAL); }else{ OrientedBoundingBox tempBounds = new OrientedBoundingBox(this); return tempBounds.getHeightXY(TransformSpace.GLOBAL); } } /** * Gets the "height vector" from its boundingshape. If no boundingshape is set, * a temporary bounding rectangle in the xy-plane is calculated and its height * is calculated as a vector with the height as its length in object space. * * @return the height xy vect obj space * * vector representing the height of the boundingshape of the shape * @deprecated this method should actually be private. Use getHeightXY(Transformspace.LOCAL) instead! */ public Vector3D getHeightXYVectLocal() { if (this.hasBounds()){ return this.getBounds().getHeightXYVectLocal(); }else{ OrientedBoundingBox tempBounds = new OrientedBoundingBox(this); return tempBounds.getHeightXYVectLocal(); } } /** * Get the width of the shape in the XY-Plane. Uses the x and y coordinate * values for calculation. Usually the calculation is delegated to the shapes * bounding shape. * * @param transformSpace the space the width is calculated in, can be global space, parent relative- or object space * * @return the width xy * * the width */ public float getWidthXY(TransformSpace transformSpace) { switch (transformSpace) { case LOCAL: return this.getWidthXYLocal(); case RELATIVE_TO_PARENT: return this.getWidthXYRelativeToParent(); case GLOBAL: return this.getWidthXYGlobal(); default: return -1; } } /** * Gets the width xy obj space. * * @return the width xy obj space */ private float getWidthXYLocal() { return this.getWidthXYVectLocal().length(); } /** * Calculates the width of this shape, by using its * bounding shape. * Uses the objects local transform. So the width will be * relative to the parent only - not the whole world * * @return the width xy relative to parent * * the width */ protected float getWidthXYRelativeToParent() { if (this.hasBounds()){ return this.getBounds().getWidthXY(TransformSpace.RELATIVE_TO_PARENT); }else{ OrientedBoundingBox tempBounds = new OrientedBoundingBox(this); return tempBounds.getWidthXY(TransformSpace.RELATIVE_TO_PARENT); } } /** * Gets the "Width vector" and transforms it to world space, then calculates * its length. * * @return the width xy global * * the Width relative to the world space */ protected float getWidthXYGlobal() { if (this.hasBounds()){ return this.getBounds().getWidthXY(TransformSpace.GLOBAL); }else{ OrientedBoundingBox tempBounds = new OrientedBoundingBox(this); return tempBounds.getWidthXY(TransformSpace.GLOBAL); } } /** * Gets the "width vector" from its boundingshape. If no boundingshape is set, * a temporary bounding rectangle in the xy-plane is calculated and its width * is calculated as a vector with the width as its length in object space. * * @return the width xy vect obj space * * vector representing the width of the boundingshape of the shape * @deprecated this method should actually be private. Use getWidthXY(Transformspace.LOCAL) instead! */ public Vector3D getWidthXYVectLocal() { if (this.hasBounds()){ return this.getBounds().getWidthXYVectLocal(); }else{ OrientedBoundingBox tempBounds = new OrientedBoundingBox(this); return tempBounds.getWidthXYVectLocal(); } } /** * <li>Removes this component from its parent. * <li>Calls <code>destroyComponent</code> on this component which * can be used to free resources that the component used. * <li>Recursively calls destroy() on its children * <br> * <p> * By default, the openGl texture object and the VBOs associated with this shape will be deleted. * Be careful when you share textures or VBOs across more than one object! * Destroying of displaylists isnt done atm! Use disableAndDeleteDisplaylists() instead. */ @Override public void destroy(){ // System.out.println(this + " -> DESTROY() -> (AbstractShape)"); //FIXME if vbos or display lists are shared, they shouldnt be deleted! //right now, destroying of displaylists isnt done. Use disableAndDeleteDisplaylists() instead. if (this.geometryInfo != null){ //Delete VBOs this.getGeometryInfo().deleteAllVBOs(); } this.destroyDisplayLists(); /* //Delete displaylist this.disableAndDeleteDisplayLists(); */ // this.geometryInfo = null; //FIXME TEST // this.boundingShape = null; this.setBounds(null); //Delete openGL texture object if (this.getTexture() instanceof GLTexture){ GLTexture tex = (GLTexture) this.getTexture(); //Delete texture tex.destroy(); this.setTexture(null); this.setTextureEnabled(false); } super.destroy(); } /** * This is called during the shape's destroy() method. * Override this and leave it empty if you dont want the * display list destroyed in your component * (makes sense with shared geometry infos/display lists) */ protected void destroyDisplayLists(){ // /* //Delete displaylist this.disableAndDeleteDisplayLists(); // */ } @Override abstract protected void destroyComponent(); /** * Moves this shape to the specified global position using an animation specified * by the last three parameters. * * @param x the x * @param y the y * @param z the z * @param interpolationDuration the interpolation duration * @param accelerationEndTime the acceleration end time - normalized value 0..1 * @param decelerationStartTime the deceleration start time - normalized value 0..1 * @return the animation */ public Animation tweenTranslateTo(float x, float y, float z, float interpolationDuration, float accelerationEndTime, float decelerationStartTime){ Vector3D from = this.getCenterPointGlobal(); Vector3D targetPoint = new Vector3D(x, y, z); Vector3D directionVect = targetPoint.getSubtracted(from); //GO through all animations for this shape Animation[] animations = AnimationManager.getInstance().getAnimationsForTarget(this); for (int i = 0; i < animations.length; i++) { Animation animation = animations[i]; //Go through all listeners of these animations IAnimationListener[] animationListeners = animation.getAnimationListeners(); for (int j = 0; j < animationListeners.length; j++) { IAnimationListener listener = animationListeners[j]; //IF a listener is a TranslationAnimationListener the animations is a translationTween //and should be stopped before doing this new animation if (listener instanceof TranslationAnimationListener) animation.stop(); } } return this.tweenTranslate(directionVect, interpolationDuration, accelerationEndTime, decelerationStartTime); } /** * Moves this shape in the specified direction with an animation specified by the other parameters. * * @param directionVect the direction vect * @param interpolationDuration the interpolation duration * @param accelerationEndTime the acceleration end time - normalized value 0..1 * @param decelerationStartTime the deceleration start time - normalized value 0..1 * @return the animation */ public Animation tweenTranslate(Vector3D directionVect, float interpolationDuration, float accelerationEndTime, float decelerationStartTime){ return this.tweenTranslate(directionVect, interpolationDuration, accelerationEndTime, decelerationStartTime, 0); } /** * Tween translate. * * @param directionVect the direction vect * @param interpolationDuration the interpolation duration * @param accelerationEndTime the acceleration end time - normalized value 0..1 * @param decelerationStartTime the deceleration start time - normalized value 0..1 * @param triggerDelay the trigger delay * @return the animation */ public Animation tweenTranslate(Vector3D directionVect, float interpolationDuration, float accelerationEndTime, float decelerationStartTime, int triggerDelay){ float distance = directionVect.length(); MultiPurposeInterpolator interpolator = new MultiPurposeInterpolator(0, distance, interpolationDuration , accelerationEndTime, decelerationStartTime , 1); Animation animation = new Animation("Tween translate of " + this.getName(), interpolator, this, triggerDelay); animation.addAnimationListener(new TranslationAnimationListener(this, directionVect)); animation.setResetOnFinish(false); animation.start(); return animation; } /** * This private class acts as an AnimationListener for translation animations. * * @author C.Ruff */ private class TranslationAnimationListener implements IAnimationListener{ /** The direction vector. */ private Vector3D directionVector; /** The normalized dir vect. */ private Vector3D normalizedDirVect; /** The shape. */ private AbstractShape shape; /** * Instantiates a new translation animation listener. * * @param shape the shape * @param directionVector the direction vector */ public TranslationAnimationListener(AbstractShape shape, Vector3D directionVector){ this.directionVector = directionVector; this.normalizedDirVect = this.directionVector.getCopy(); this.normalizedDirVect.normalizeLocal(); this.shape = shape; } /* (non-Javadoc) * @see util.animation.IAnimationListener#processAnimationEvent(util.animation.AnimationEvent) */ public void processAnimationEvent(AnimationEvent ae) { Object target = ae.getTargetObject(); if (target != null && target.equals(this.shape)){ AbstractShape shape = (AbstractShape)target; float amount = ae.getAnimation().getInterpolator().getCurrentStepDelta(); Vector3D newTranslationVect = this.normalizedDirVect.getCopy(); newTranslationVect.scaleLocal(amount); //Move shape // shape.translateGlobal(newTranslationVect); shape.translate(newTranslationVect); } } } }