/***********************************************************************
* 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;
import java.util.List;
import org.mt4j.components.clusters.Cluster;
import org.mt4j.components.clusters.ClusterManager;
import org.mt4j.components.interfaces.IMTComponent3D;
import org.mt4j.input.IHitTestInfoProvider;
import org.mt4j.input.inputData.MTInputEvent;
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.input.inputProcessors.componentProcessors.tapProcessor.TapProcessor;
import org.mt4j.util.PlatformUtil;
import org.mt4j.util.camera.Icamera;
import org.mt4j.util.math.Matrix;
import processing.core.PApplet;
import processing.core.PGraphics;
import processing.core.PMatrix3D;
/**
* MTCanvas is the root node of the component hierarchy of a MT4j scene.
* To make a mt4j component visible and interactable you have to add it to
* the scene's canvas or to a component which is already attached to the canvas.
* The canvas then recursivly updates and draws all attached components each frame.
*
* @author Christopher Ruff
*/
public class MTCanvas extends MTComponent implements IHitTestInfoProvider{
/** The cluster manager. */
private ClusterManager clusterManager;
// /** The last time hit test. */
// private long lastTimeHitTest;
//
// /** The cache time delta. */
// private long cacheTimeDelta;
//
// /** The cache clear time. */
// private int cacheClearTime;
//
// /** The position to component. */
// private HashMap<Position, IMTComponent3D> positionToComponent;
//
// /** The timer. */
// private Timer timer;
//
// /** The use hit test cache. */
// private boolean useHitTestCache;
/** The frustum culling switch. */
private boolean frustumCulling;
private int culledObjects = 0;
private long lastUpdateTime;
// private PMatrix3D modelViewP5;
/**
* The Constructor.
*
* @param pApplet the applet
* @param attachedCamera the attached camera
*/
public MTCanvas(PApplet pApplet, Icamera attachedCamera) {
this(pApplet, "unnamed MT Canvas", attachedCamera);
}
/**
* The Constructor.
*
* @param pApplet the applet
* @param name the name
* @param attachedCamera the attached camera
*/
public MTCanvas(PApplet pApplet, String name, Icamera attachedCamera) {
super(pApplet, name, attachedCamera);
// //Cache settings
// lastTimeHitTest = 0;
// cacheTimeDelta = 100;
// cacheClearTime = 20000;
// useHitTestCache = true;
lastUpdateTime = 0;
clusterManager = new ClusterManager(this);
// positionToComponent = new HashMap<Position, IMTComponent3D>();
//
// //Schedule a Timer task to clear the object cache so it wont
// //get filled infinitely
// timer = new Timer(cacheClearTime, new ActionListener(){
// public void actionPerformed(ActionEvent arg0) {
//// System.out.println("Hit test chache entries: " + positionToComponent.size() + " ... cleared!");
// positionToComponent.clear();
// }
// });
//// timer.start(); //FIXME TEST
// this.useHitTestCache = false; //FIXME TEST DISABLE HIT CACHE
// this.setCollidable(false);
this.setGestureAllowance(RotateProcessor.class, false);
this.setGestureAllowance(ScaleProcessor.class, false);
this.setGestureAllowance(TapProcessor.class, false);
this.setGestureAllowance(DragProcessor.class, false);
this.setPickable(false);
//Frustum culling default
frustumCulling = false;
// this.modelViewP5 = GraphicsUtil.getModelView(); //TODO does this even work? -> better get it each time
}
@Override
protected void destroyComponent() {
super.destroyComponent();
//
// if (this.timer != null && timer.isRunning()){
// timer.stop();
// }
//
// if (positionToComponent != null){
// positionToComponent.clear();
// }
}
/**
* Method for asking the canvas whether and which object is at the specified
* screen position.
* <p>
* IMPORTANT: this method returns the MTCanvas instance if no other object is hit.
* This means that the MTCanvas instance acts like the background => Gestures that
* are supposed to be performed on the background have to check if they hit the canvas.
* And the gestureevents should then have the canvas as their targetComponent!
* Also, you have to be careful in other gestures, as even when you dont hit an object, you will
* get the mtcanvas returned as the hit component - not null!
* <p>Note: if the hit component is part of a cluster, the cluster is returned!
*
* @param x the screen x coordinate
* @param y the screen y coordinate
*
* @return the object at that position or this MTCanvas instance if no component was hit
*/
public IMTComponent3D getComponentAt(float x, float y) {
IMTComponent3D closest3DComp = null;
try{
// long now = System.currentTimeMillis();
// if (useHitTestCache){
// if (now - lastTimeHitTest > cacheTimeDelta){ //If the time since last check surpassed => do new hit-test!
// //Benchmark the picking
//// long a = System.nanoTime();
// closest3DComp = this.pick(x, y).getNearestPickResult();
// //Benchmark the picking
//// long b = System.nanoTime();
//// System.out.println("Time for picking the scene: " + (b-a));
// /*
// for (MTBaseComponent c : pickResult.getPickList())
// System.out.println(c.getName());
// if (closest3DComp != null)
// System.out.println("Using: " + closest3DComp.getName());
// */
// if (closest3DComp == null){
// closest3DComp = this;
// }
// positionToComponent.put(new Position(x,y), closest3DComp);
// }else{
// //Check whats in the cache
// IMTComponent3D cachedComp = positionToComponent.get(new Position(x,y));
// if (cachedComp != null){ //Use cached obj
// closest3DComp = cachedComp;
// positionToComponent.put(new Position(x,y), closest3DComp);
// }else{
// closest3DComp = this.pick(x, y).getNearestPickResult();
// if (closest3DComp == null){
// closest3DComp = this;
// }
// positionToComponent.put(new Position(x,y), closest3DComp);
// }
// }
// }else{//IF no hittest cache is being used
closest3DComp = this.pick(x, y).getNearestPickResult();
if (closest3DComp == null){
closest3DComp = this;
}
// }
// lastTimeHitTest = now;
// /*//TODO anders machen..z.b. geclusterte comps einfach als kinder von
//�bergeordnetem clusterpoly machen? aber mit clusterPoly.setComposite(TRUE);
//Clusterpoly pickable machen damit das hier nicht gebraucht wird?
Cluster sel = this.getClusterManager().getCluster(closest3DComp);
if (sel != null){
closest3DComp = sel;
}
// */
// //FIXME TEST for stencil clipped scene windows -> we have to return the scenes canvas for some gestures!
// if (closest3DComp != null && closest3DComp instanceof mtClipSceneWindow)
}catch(Exception e){
System.err.println("Error while trying to pick an object: ");
e.printStackTrace();
}
/*
if (closest3DComp != null)
System.out.println("Picked: '" + closest3DComp.getName() + "' at pos (" + x + "," + y + ")");
else
System.out.println("Picked: '" + closest3DComp + "' at pos (" + x + "," + y + ")");
*/
return closest3DComp;
}
public boolean isBackGroundAt(float x, float y) {
return this.getComponentAt(x, y).equals(this);
}
//FIXME TEST
@Override
public void updateComponent(long timeDelta) {
super.updateComponent(timeDelta);
this.lastUpdateTime = timeDelta;
}
//FIXME TEST
private boolean calledFromDrawComponent = false;
//FIXME TEST
// /*
// * Actually this canvases drawComponent method should be called
// * ever because the canvas should not be added as a child to any component.
// * Anyway - this code will still make it possible to use it as a child of other components
// * (non-Javadoc)
// * @see org.mt4j.components.MTComponent#drawComponent(processing.core.PGraphics)
// */
// @Override
// public void drawComponent(PGraphics g) { //FIXME this would draw the canvas 2 times..
// super.drawComponent(g);
//
// //Call the canvases scenes draw method to also draw
// //stuff defined in an overrriden scenes draw method
// if (this.getRenderer() instanceof MTApplication){
// MTApplication app = (MTApplication)this.getRenderer();
// Iscene[] scenes = app.getScenes();
// for (int i = 0; i < scenes.length; i++) {
// Iscene iscene = scenes[i];
// if (iscene instanceof AbstractScene){
// AbstractScene as = (AbstractScene)iscene;
// if (as.getCanvas().equals(this)){
// this.calledFromDrawComponent = true;
//// this.drawAndUpdateCanvas(g, this.lastUpdateTime);
// as.drawAndUpdate(g, this.lastUpdateTime);
// this.calledFromDrawComponent = false;
// }
// }
// }
// }
//
//// this.calledFromDrawComponent = true;
//// this.drawAndUpdateCanvas(g, this.lastUpdateTime);
//// this.calledFromDrawComponent = false;
// }
/**
* Updates and then draws every visible object in the canvas.
* First calls the <code>updateComponent(long timeDelta)</code> method. Then
* the <code>drawComponent()</code> method of each object in the scene graph.
* Also handles the setting of cameras attached to the objects.
* @param graphics
*
* @param updateTime the time passed since the last update (in ms)
*/
public void drawAndUpdateCanvas(PGraphics graphics, long updateTime){
this.culledObjects = 0;
//FIXME THIS IS A HACK! WE SHOULD REPLACE CLUSTERS WITH NORMAL COMPONENTS INSTEAD!
//Update cluster components
Cluster[] clusters = getClusterManager().getClusters();
for (Cluster cluster : clusters) {
cluster.updateComponent(updateTime);
}
this.drawUpdateRecursive(this, updateTime, graphics);
// System.out.println("Culled objects: " + culledObjects);
}
/**
* Draw the whole canvas update recursive.
*
* @param currentcomp the currentcomp
* @param updateTime the update time
* @param graphics the renderer
*/
private void drawUpdateRecursive(MTComponent currentcomp, long updateTime, PGraphics graphics){
if (currentcomp.isVisible()){
//Update current component
currentcomp.updateComponent(updateTime);
if (currentcomp.getAttachedCamera() != null){
//Saves transformations up to this object
graphics.pushMatrix();
//Resets the modelview completely with a new camera matrix
currentcomp.getAttachedCamera().update();
if (currentcomp.getParent() != null){
//Applies all transforms up to this components parent
//because the new camera wiped out all previous transforms
Matrix m = currentcomp.getParent().getGlobalMatrix();
// PGraphics3D pgraphics3D = (PGraphics3D)graphics;
// pgraphics3D.modelview.apply(
// modelViewP5.apply(
// GraphicsUtil.getModelView().apply(
// m.m00, m.m01, m.m02, m.m03,
// m.m10, m.m11, m.m12, m.m13,
// m.m20, m.m21, m.m22, m.m23,
// m.m30, m.m31, m.m32, m.m33
// );
if (PlatformUtil.isAndroid()){
getRenderer().g.applyMatrix(
m.m00, m.m01, m.m02, m.m03,
m.m10, m.m11, m.m12, m.m13,
m.m20, m.m21, m.m22, m.m23,
m.m30, m.m31, m.m32, m.m33);
}else{
PlatformUtil.getModelView().apply(
m.m00, m.m01, m.m02, m.m03,
m.m10, m.m11, m.m12, m.m13,
m.m20, m.m21, m.m22, m.m23,
m.m30, m.m31, m.m32, m.m33
);
}
}
//Apply local transform etc
currentcomp.preDraw(graphics);
//Check visibility with camera frustum
if (frustumCulling){
if (currentcomp.isContainedIn(currentcomp.getViewingCamera().getFrustum())){
if (!this.calledFromDrawComponent){ //FIXME TEST
// DRAW THE COMPONENT \\
currentcomp.drawComponent(graphics);
}
}else{
culledObjects++;
//System.out.println("Not visible: " + currentcomp.getName());
}
}else{
if (!this.calledFromDrawComponent){ //FIXME TEST
// DRAW THE COMPONENT \\
currentcomp.drawComponent(graphics);
}
}
currentcomp.postDraw(graphics);
//Draw Children //FIXME for each loop sometimes throws concurrentmodification error because of fail-fast iterator
// for (MTComponent child : currentcomp.getChildList())
// drawUpdateRecursive(child, updateTime, graphics);
List<MTComponent> childs = currentcomp.getChildList();
int childCount = childs.size();
for (int i = 0; i < childCount; i++) {
drawUpdateRecursive(childs.get(i), updateTime, graphics);
}
currentcomp.postDrawChildren(graphics);
//Restores the transforms of the previous camera etc
graphics.popMatrix();
}else{//If no custom camera was set
//TODO in abstactvisiblecomp wird outine �ber gradients und clips
//gezeichnet obwohl hier invisble war! FIXME!
//evtl applymatrix unapply in eigene methode? dann nur das ausf�hren, kein pre/post draw!
//TODO vater an kinder listener -> resize - new geometry -> resize own
currentcomp.preDraw(graphics);
if (frustumCulling){
//Check visibility with camera frustum
if (currentcomp.isContainedIn(currentcomp.getViewingCamera().getFrustum())){
// DRAW THE COMPONENT \\
currentcomp.drawComponent(graphics);
}else{
culledObjects++;
//System.out.println("Not visible: " + currentcomp.getName());
}
}else{
// DRAW THE COMPONENT \\
currentcomp.drawComponent(graphics);
}
currentcomp.postDraw(graphics);
// for (MTComponent child : currentcomp.getChildList()) //FIXME for each loop sometimes throws concurrentmodification error because of fail-fast iterator
// drawUpdateRecursive(child, updateTime, graphics);
List<MTComponent> childs = currentcomp.getChildList();
int childCount = childs.size();
for (int i = 0; i < childCount; i++) {
drawUpdateRecursive(childs.get(i), updateTime, graphics);
}
currentcomp.postDrawChildren(graphics);
}
}//if visible end
}
/* (non-Javadoc)
* @see org.mt4j.components.MTComponent#processInputEvent(org.mt4j.input.inputData.MTInputEvent)
*/
@Override
public boolean processInputEvent(MTInputEvent inEvt) {
//TODO not very elegant - better approach??
if (inEvt.hasTarget() && inEvt.getEventPhase() != MTInputEvent.BUBBLING_PHASE){
if (!inEvt.getTarget().equals(this)){ //Avoid recursion
//Send directed event to the target component
return inEvt.getTarget().processInputEvent(inEvt);
}
}
// return true; //this.handleEvent
//handle in superclass
//The MTCanvas get events targeted at him AND events that have no target!
return super.processInputEvent(inEvt);
}
/**
* Gets the cluster manager.
*
* @return the cluster manager
*/
public ClusterManager getClusterManager() {
return clusterManager;
}
/**
* Sets the cluster manager.
*
* @param selectionManager the new cluster manager
*/
public void setClusterManager(ClusterManager selectionManager) {
this.clusterManager = selectionManager;
}
// /**
// * Gets the cache time delta.
// *
// * @return the cache time delta
// */
// public long getCacheTimeDelta() {
// return cacheTimeDelta;
// }
//
// /**
// * If repeated calls to getObjectAt(float x, float y) in MTCanvas class
// * are called during the provided cacheTimeDelta, the Canvas looks into his
// * cache instead of querying all objects again
// * Default value is: 80.
// *
// * @param cacheTimeDelta the cache time delta
// */
// public void setCacheTimeDelta(long cacheTimeDelta) {
// this.cacheTimeDelta = cacheTimeDelta;
// }
//
// /**
// * Checks if is use hit test cache.
// *
// * @return true, if is use hit test cache
// */
// public boolean isUseHitTestCache() {
// return useHitTestCache;
// }
//
//
// /**
// * The canvas can be set to look into a hit test cache if
// * repeated calls to getComponentAt() with the same coordinates
// * during a short period of time are made.
// * This period of time can be set with
// * <code>setCacheTimeDelta(long cacheTimeDelta)</code>
// * <p>
// * This is useful for example when a click is made many gestureanalyzers
// * call getObjectAt() almost concurrently.
// *
// * @param useHitTestCache the use hit test cache
// */
// public void setUseHitTestCache(boolean useHitTestCache) {
// if (useHitTestCache && !timer.isRunning())
// timer.start();
// else if (!useHitTestCache && timer.isRunning())
// timer.stop();
//
// this.useHitTestCache = useHitTestCache;
// }
//
//
// /**
// * Gets the cache clear time.
// *
// * @return the cache clear time
// */
// public int getCacheClearTime() {
// return cacheClearTime;
// }
//
// /**
// * Sets the time intervals in ms in which the canvas clears its hit test cache
// * Default value is: 20000 ms
// * <p>
// * This is important to prevent the hit test cache from growing indefinitely.
// *
// * @param cacheClearTime the cache clear time
// */
// public void setCacheClearTime(int cacheClearTime) {
// timer.setDelay(cacheClearTime);
// this.cacheClearTime = cacheClearTime;
// }
public boolean isFrustumCulling() {
return frustumCulling;
}
public void setFrustumCulling(boolean frustumCulling) {
this.frustumCulling = frustumCulling;
}
// /**
// * Class used for the pickobject cache.
// */
// private class Position{
// /** The y. */
// float x,y;
//
// /**
// * Instantiates a new position.
// *
// * @param x the x
// * @param y the y
// */
// public Position(float x, float y){
// this.x = x;
// this.y = y;
// }
//
// /**
// * Gets the x.
// *
// * @return the x
// */
// public float getX() {return x;}
//
// /**
// * Gets the y.
// *
// * @return the y
// */
// public float getY() {return y;}
//
// /* (non-Javadoc)
// * @see java.lang.Object#equals(java.lang.Object)
// */
// @Override
// public boolean equals(Object arg0) {
// return (arg0 instanceof Position && ((Position)arg0).getX() == this.getX() && ((Position)arg0).getY() == this.getY());
// }
//
// /* (non-Javadoc)
// * @see java.lang.Object#hashCode()
// */
// @Override
// public int hashCode() {
// return ((int)x+(int)y);
// }
// }
}