/***********************************************************************
* 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.camera.IFrustum;
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.GLConstants;
import org.mt4j.util.opengl.GLTexture;
import org.mt4j.util.opengl.GLTextureParameters;
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
if (MT4jSettings.getInstance().isOpenGlMode()){
this.drawDirectGL = true;
}else{
this.drawDirectGL = 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.bounds = new Vector3D[0];
this.setDefaultGestureActions();
globalVerticesDirty = true;//
}
/*
//FIXME TODO switch drawBounds! put draw() into IBoundingShape!
@Override
public void postDraw(PGraphics g) {
super.postDraw(g);
if (this.getBoundingShape() instanceof OrientedBoundingBox){
OrientedBoundingBox b = (OrientedBoundingBox)this.getBoundingShape();
b.drawBounds(g);
}
else if (this.getBoundingShape() instanceof BoundsZPlaneRectangle){
BoundsZPlaneRectangle b = (BoundsZPlaneRectangle)this.getBoundingShape();
b.drawBounds(g);
}
else if (this.getBoundingShape() instanceof BoundingSphere){
BoundingSphere b = (BoundingSphere)this.getBoundingShape();
b.drawBounds(g);
}
}
*/
/*
//Test for drawing bounding shape aligned to coordinate axis
@Override
public void postDrawChildren(PGraphics g) {
super.postDrawChildren(g);
if (this.getBoundingShape() instanceof BoundsZPlaneRectangle){
BoundsZPlaneRectangle b = (BoundsZPlaneRectangle)this.getBoundingShape();
// b.drawBounds(g);
g.pushMatrix();
g.pushStyle();
g.fill(250,150,150,180);
Vector3D[] v = b.getVectorsGlobal();
float[] minMax = Tools3D.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();
}
}
*/
/**
* 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(){
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 getBoundingShape(){
return this.boundingShape;
}
/**
* Checks if is bounding shape set.
* @return true, if is bounding shape set
*/
public boolean isBoundingShapeSet(){
return this.boundingShape != null;
}
/**
* 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);
}
/**
* 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;
}
////////////////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.setBoundingShape(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.setBoundingShape(null);
}
}else{
this.setBoundingShape(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.setBoundingShape(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.setBoundingShape(null);
}
}else{
this.setBoundingShape(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;
}
}
//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.getBoundingShape();
if (bounds != null){
bounds.setGlobalBoundsChanged();
}
}
/* (non-Javadoc)
* @see com.jMT.components.MTBaseComponent#setMatricesDirty(boolean)
*/
@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);
}
/**
* 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 GLTexture)
){
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{
logger.error(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;
}
//TODO alte GL-Texture l�schen wenn vorhanden!!?
//TODO there is a problem when we set a pimage texture and have > 1.0 texcoords for tiling
/**
* 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;
// System.out.println("Set texture to null");
return;
}
//Enable textures
if (!this.isTextureEnabled())
this.setTextureEnabled(true);
boolean isPowerOfTwo = Tools3D.isPowerOfTwoDimension(newTexImage);
if (this.textureImage != null){ //Shape already has a texture
boolean hasSameDimensions = (this.textureImage.width == newTexImage.width
&& this.textureImage.height == newTexImage.height);
if (this.isUseDirectGL()){
if (this.textureImage instanceof GLTexture){
//Old texture is instance of GLTexture object
GLTexture oldGLTex = (GLTexture)this.textureImage;
// if (hasSameDimensions){
// //Old GLTexture obj has same dimension -> just put new pimage and pixels into texture obj
// oldGLTex.putImage(newTexImage);
// //TODO delete old texture?
// }else{
//Old gl texture doesnt have same dimensions -> make new Texture
/*
GLTextureParameters p = new GLTextureParameters();
p.format = oldGLTex.getTextureParams().format;
p.magFilter = oldGLTex.getTextureParams().magFilter;
p.minFilter = oldGLTex.getTextureParams().minFilter;
p.target = oldGLTex.getTextureParams().target;
GLTexture newTex = new GLTexture(this.getRenderer(), newTexImage.width, newTexImage.height, p);
newTex.putImage(textureImage);
this.textureImage = newTex;
*/
//TODO delete old glTexture object?
//generateDefault a GLTexture object for use with pure openGL
GLTextureParameters tParams = new GLTextureParameters();
if (!isPowerOfTwo){
tParams.target = GLConstants.RECTANGULAR;
//We have to scale the texture coordinates from 0..1 to 0..width
//
//If old gltexture was rectangular we assume that the textcoords were scaled to the images dimensions (instead of normalzied)
//so we rescale accordingly
if (oldGLTex.getTextureParams().target == GLTexture.RECTANGULAR){
this.scaleTextureCoordsForRectModeFromRectMode(this.textureImage, newTexImage, this.getGeometryInfo().getVertices());
}else{
Tools3D.scaleTextureCoordsForRectModeFromNormalized(newTexImage, this.getGeometryInfo().getVertices());
}
this.setTextureMode(PConstants.IMAGE);
//Update the texture buffer!
this.getGeometryInfo().updateTextureBuffer(this.isUseVBOs());
// System.out.println("Non power of two texture detected in object: " + this.getName());
}else{
// this.setTextureMode(PConstants.NORMALIZED);
//System.out.println("Power of two texture in: " + this.getName());
}
//Dont use mipmaps by default
tParams.minFilter = GLTextureParameters.LINEAR;
tParams.magFilter = GLTextureParameters.LINEAR;
//Initialize emtpy texture
GLTexture newGLTexture = new GLTexture(this.getRenderer(), newTexImage.width, newTexImage.height, tParams);
//Fill the texture with pixels
newGLTexture.putImage(newTexImage);
newGLTexture.setFlippedY(true); //?
newGLTexture.format = newTexImage.format;
this.textureImage = newGLTexture;
// }
}else{
//Old texture isnt gltexture but this obj is in directGL mode ->create new gltexture
GLTextureParameters tParams = new GLTextureParameters();
if (!isPowerOfTwo){
tParams.target = GLConstants.RECTANGULAR;
//We have to scale the texture coordinates from 0..1 to 0..width
Tools3D.scaleTextureCoordsForRectModeFromNormalized(newTexImage, this.getGeometryInfo().getVertices());
this.setTextureMode(PConstants.IMAGE);
//Update the texture buffer!
this.getGeometryInfo().updateTextureBuffer(this.isUseVBOs());
// System.out.println("Non power of two texture detected in object: " + this.getName());
}else{
// this.setTextureMode(PConstants.NORMALIZED);
//System.out.println("Power of two texture in: " + this.getName());
}
//Dont use mipmaps by default
tParams.minFilter = GLTextureParameters.LINEAR;
tParams.magFilter = GLTextureParameters.LINEAR;
//Initialize emtpy texture
GLTexture newGLTexture = new GLTexture(this.getRenderer(), newTexImage.width, newTexImage.height, tParams);
//Fill the texture with pixels
newGLTexture.putImage(newTexImage);
newGLTexture.setFlippedY(true); //?
newGLTexture.format = newTexImage.format;
this.textureImage = newGLTexture;
}
}else{
//Had old texture but this shape isnt set to use direct gl mode ->just set new pimage
this.textureImage = newTexImage;
}
}else{
//Didnt have previously set texture -> check if were in direct gl mode
// System.out.println(this.getName() + "didnt have previous texture");
if (this.isUseDirectGL()){
// System.out.println(this.getName() + "hast direct gl mode set");
//Shape is set to use direct gl mode -> check if the new texture is already a gltexture object instance
if (newTexImage instanceof GLTexture){
//New texture is already gltexture -> just set it
this.textureImage = newTexImage;
}else{
//New texture isnt of GLTexture but we should use direct gl mode -> create one
GLTextureParameters tParams = new GLTextureParameters();
if (!isPowerOfTwo){
tParams.target = GLConstants.RECTANGULAR;
//We have to scale the texture coordinates from 0..1 to 0..width
Tools3D.scaleTextureCoordsForRectModeFromNormalized(newTexImage, this.getGeometryInfo().getVertices());
this.setTextureMode(PConstants.IMAGE);
//Update the texture buffer!
this.getGeometryInfo().updateTextureBuffer(this.isUseVBOs());
// System.out.println("Non power of two texture detected in object: " + this.getName());
}else{
// this.setTextureMode(PConstants.NORMALIZED);
//System.out.println("Power of two texture in: " + this.getName());
}
//Dont use mipmaps by default
tParams.minFilter = GLTextureParameters.LINEAR;
tParams.magFilter = GLTextureParameters.LINEAR;
//Initialize emtpy texture
GLTexture newGLTexture = new GLTexture(this.getRenderer(), newTexImage.width, newTexImage.height, tParams);
//Fill the texture with pixels
newGLTexture.putImage(newTexImage);
newGLTexture.setFlippedY(true); //?
newGLTexture.format = newTexImage.format;
this.textureImage = newGLTexture;
}
}else{
//Didnt have old texture and this shape isnt set to use direct gl mode -> just set new pimage
this.textureImage = newTexImage;
}
}
}
/**
* Rescale tex coords form old texture in rectangular mode to new.
*
* @param oldTexture the old texture
* @param newTexture the new texture
* @param verts the verts
*/
private void scaleTextureCoordsForRectModeFromRectMode(PImage oldTexture, PImage newTexture, Vertex[] verts){
for (int i = 0; i < verts.length; i++) {
Vertex vertex = verts[i];
vertex.setTexCoordU( (vertex.getTexCoordU()/oldTexture.width) * newTexture.width );
vertex.setTexCoordV( (vertex.getTexCoordV()/oldTexture.height) * newTexture.height);
}
}
/*//rectangular works even if dimensions is power of two so..not neccesary
private void scaleTextureCoordsForNormalizedModeFromRect(PImage texture, Vertex[] verts){
//TODO
}
*/
/**
* 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.getBoundingShape().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 com.jMT.components.visibleComponents.AbstractVisibleComponent#getComponentIntersectionPoint(util.math.Ray)
*/
@Override
public Vector3D getIntersectionLocal(Ray ray) {
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.isBoundingShapeSet()){
// System.out.println("\"" + this.getName() + "\": -> BOUNDS only check");
return this.getBoundingShape().getIntersectionLocal(ray);
}else{
// System.out.println("\"" + this.getName() + "\": -> GEOMETRY only check");
return this.getGeometryIntersectionLocal(ray);
}
case AbstractShape.BOUNDS_CHECK_THEN_GEOMETRY_CHECK:
if (this.isBoundingShapeSet()){
// System.out.println("\"" + this.getName() + "\": -> BOUNDS check then GEOMETRY check");
Vector3D boundsIntersection = this.getBoundingShape().getIntersectionLocal(ray);
if (boundsIntersection != null){
return this.getGeometryIntersectionLocal(ray);
}else{
return null;
}
}else{
return this.getGeometryIntersectionLocal(ray);
}
default:
break;
}
return null;
}
/* (non-Javadoc)
* @see com.jMT.components.visibleComponents.AbstractVisibleComponent#componentContainsPoint(util.math.Vector3D)
*/
@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.isBoundingShapeSet()){
// System.out.println("\"" + this.getName() + "\": -> BOUNDS only check");
return this.getBoundingShape().containsPointLocal(testPoint);
}else{
// System.out.println("\"" + this.getName() + "\": -> GEOMETRY only check");
return this.isGeometryContainsPointLocal(testPoint);
}
case AbstractShape.BOUNDS_CHECK_THEN_GEOMETRY_CHECK:
if (this.isBoundingShapeSet()){
// System.out.println("\"" + this.getName() + "\": -> BOUNDS check then GEOMETRY check");
if (this.getBoundingShape().containsPointLocal(testPoint)){
return this.isGeometryContainsPointLocal(testPoint);
}else{
return false;
}
}else{
return this.isGeometryContainsPointLocal(testPoint);
}
default:
break;
}
return false;
}
/* (non-Javadoc)
* @see org.mt4j.components.MTComponent#isContainedIn(util.camera.IFrustum)
*/
@Override
public boolean isContainedIn(IFrustum frustum){
//Check if bounds are contained in the frustum
//if shape has no boundingshape return true by default
if (this.getBoundingShape() != null){
return this.getBoundingShape().isContainedInFrustum(frustum);
}else{
return true;
}
}
/**
* 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();
/**
* <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.setBoundingShape(null);
//Delete openGL texture object
if (this.getTexture() instanceof GLTexture){
GLTexture tex = (GLTexture) this.getTexture();
//Delete texture
tex.deleteTextureGL();
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();
// */
}
/* (non-Javadoc)
* @see com.jMT.components.MTBaseComponent#destroyComponent()
*/
@Override
abstract protected void destroyComponent();
/**
* 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);
/**
* 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
* @param decelerationStartTime the deceleration start time
*/
public void 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();
}
}
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
* @param decelerationStartTime the deceleration start time
*/
public void tweenTranslate(Vector3D directionVect, float interpolationDuration, float accelerationEndTime, float decelerationStartTime){
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
* @param decelerationStartTime the deceleration start time
* @param triggerDelay the trigger delay
*/
public void 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();
}
/**
* 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);
}
}
}
}