PageRenderTime 39ms CodeModel.GetById 19ms app.highlight 15ms RepoModel.GetById 1ms app.codeStats 0ms

/src/org/mt4j/components/MTCanvas.java

http://mt4j.googlecode.com/
Java | 604 lines | 245 code | 95 blank | 264 comment | 44 complexity | f4996918667ecc00356017ec826ff2c6 MD5 | raw file
  1/***********************************************************************
  2 * mt4j Copyright (c) 2008 - 2009, C.Ruff, Fraunhofer-Gesellschaft All rights reserved.
  3 *  
  4 *   This program is free software: you can redistribute it and/or modify
  5 *   it under the terms of the GNU General Public License as published by
  6 *   the Free Software Foundation, either version 3 of the License, or
  7 *   (at your option) any later version.
  8 *
  9 *   This program is distributed in the hope that it will be useful,
 10 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 11 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 12 *   GNU General Public License for more details.
 13 *
 14 *   You should have received a copy of the GNU General Public License
 15 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 16 *
 17 ***********************************************************************/
 18package org.mt4j.components;
 19
 20import java.awt.event.ActionEvent;
 21import java.awt.event.ActionListener;
 22import java.util.HashMap;
 23import java.util.List;
 24
 25import javax.swing.Timer;
 26
 27import org.mt4j.components.clusters.Cluster;
 28import org.mt4j.components.clusters.ClusterManager;
 29import org.mt4j.components.interfaces.IMTComponent3D;
 30import org.mt4j.input.IHitTestInfoProvider;
 31import org.mt4j.input.inputData.MTInputEvent;
 32import org.mt4j.input.inputProcessors.componentProcessors.dragProcessor.DragProcessor;
 33import org.mt4j.input.inputProcessors.componentProcessors.rotateProcessor.RotateProcessor;
 34import org.mt4j.input.inputProcessors.componentProcessors.scaleProcessor.ScaleProcessor;
 35import org.mt4j.input.inputProcessors.componentProcessors.tapProcessor.TapProcessor;
 36import org.mt4j.util.camera.Icamera;
 37import org.mt4j.util.math.Matrix;
 38
 39import processing.core.PApplet;
 40import processing.core.PGraphics;
 41import processing.core.PGraphics3D;
 42
 43/**
 44 * MTCanvas is the root node of the component hierarchy of a MT4j scene.
 45 * To make a mt4j component visible and interactable you have to add it to
 46 * the scene's canvas or to a component which is already attached to the canvas.
 47 * The canvas then recursivly updates and draws all attached components each frame.
 48 * 
 49 * @author Christopher Ruff
 50 */
 51public class MTCanvas extends MTComponent implements IHitTestInfoProvider{
 52	
 53	/** The cluster manager. */
 54	private ClusterManager clusterManager;
 55	
 56	/** The last time hit test. */
 57	private long lastTimeHitTest;
 58	
 59	/** The cache time delta. */
 60	private long cacheTimeDelta;
 61	
 62	/** The cache clear time. */
 63	private int cacheClearTime;
 64	
 65	/** The position to component. */
 66	private HashMap<Position, IMTComponent3D> positionToComponent;
 67	
 68	/** The timer. */
 69	private Timer timer;
 70	
 71	/** The use hit test cache. */
 72	private boolean useHitTestCache;
 73	
 74	/** The frustum culling switch. */
 75	private boolean frustumCulling;
 76	
 77	private int culledObjects = 0;
 78
 79	private long lastUpdateTime;
 80
 81	
 82	
 83	/**
 84	 * The Constructor.
 85	 *
 86	 * @param pApplet the applet
 87	 * @param attachedCamera the attached camera
 88	 */
 89	public MTCanvas(PApplet pApplet, Icamera attachedCamera) {
 90		this(pApplet, "unnamed MT Canvas", attachedCamera);
 91	}
 92
 93	/**
 94	 * The Constructor.
 95	 * 
 96	 * @param pApplet the applet
 97	 * @param name the name
 98	 * @param attachedCamera the attached camera
 99	 */
100	public MTCanvas(PApplet pApplet, String name, Icamera attachedCamera) {
101		super(pApplet, name, attachedCamera);
102		//Cache settings
103		lastTimeHitTest = 0;
104		cacheTimeDelta = 100;
105		cacheClearTime = 20000;
106		useHitTestCache = true;
107		
108		lastUpdateTime = 0;
109		
110		clusterManager = new ClusterManager(this);
111		
112		positionToComponent = new HashMap<Position, IMTComponent3D>();
113		
114		//Schedule a Timer task to clear the object cache so it wont 
115		//get filled infinitely
116		timer = new Timer(cacheClearTime, new ActionListener(){
117			public void actionPerformed(ActionEvent arg0) {
118//				System.out.println("Hit test chache entries: " + positionToComponent.size() + " ... cleared!");
119				positionToComponent.clear();
120			}
121		});
122//		timer.start(); //FIXME TEST 
123		this.useHitTestCache = false; //FIXME TEST DISABLE HIT CACHE
124		
125//		this.setCollidable(false);
126		
127		this.setGestureAllowance(RotateProcessor.class, false);
128		this.setGestureAllowance(ScaleProcessor.class, false);
129		this.setGestureAllowance(TapProcessor.class, false);
130		this.setGestureAllowance(DragProcessor.class, false);
131		
132		this.setPickable(false);
133		
134		//Frustum culling default
135		frustumCulling = false;
136	}
137	
138	@Override
139	protected void destroyComponent() {
140		super.destroyComponent();
141		
142		if (this.timer != null && timer.isRunning()){
143			timer.stop();
144		}
145		
146		if (positionToComponent != null){
147			positionToComponent.clear();
148		}
149	}
150	
151
152	/**
153	 * Method for asking the canvas whether and which object is at the specified
154	 * screen position.
155	 * <p>
156	 * IMPORTANT: this method returns the MTCanvas instance if no other object is hit.
157	 * This means that the MTCanvas instance acts like the background => Gestures that
158	 * are supposed to be performed on the background have to check if they hit the canvas.
159	 * And the gestureevents should then have the canvas as their targetComponent!
160	 * Also, you have to be careful in other gestures, as even when you dont hit an object, you will
161	 * get the mtcanvas returned as the hit component - not null! 
162	 * <p>Note: if the hit component is part of a cluster, the cluster is returned!
163	 * 
164	 * @param x the screen x coordinate
165	 * @param y the screen y coordinate
166	 * 
167	 * @return the object at that position or this MTCanvas instance if no component was hit
168	 */
169	public IMTComponent3D getComponentAt(float x, float y) { 
170		IMTComponent3D closest3DComp = null;
171		try{
172			long now = System.currentTimeMillis();
173			if (useHitTestCache){
174				if (now - lastTimeHitTest > cacheTimeDelta){ //If the time since last check surpassed => do new hit-test!
175					//Benchmark the picking
176//					long a = System.nanoTime();
177					closest3DComp = this.pick(x, y).getNearestPickResult();
178					//Benchmark the picking
179//					long b = System.nanoTime();
180//					System.out.println("Time for picking the scene: " + (b-a));
181					/*
182					for (MTBaseComponent c : pickResult.getPickList())
183						System.out.println(c.getName());
184					if (closest3DComp != null)
185						System.out.println("Using: " + closest3DComp.getName());
186					*/
187					if (closest3DComp == null){
188						closest3DComp = this;
189					}
190					positionToComponent.put(new Position(x,y), closest3DComp);
191				}else{
192					//Check whats in the cache
193					IMTComponent3D cachedComp = positionToComponent.get(new Position(x,y));
194					if (cachedComp != null){ //Use cached obj
195						closest3DComp = cachedComp;
196						positionToComponent.put(new Position(x,y), closest3DComp);
197					}else{
198						closest3DComp = this.pick(x, y).getNearestPickResult();
199						if (closest3DComp == null){
200							closest3DComp = this;
201						}
202						positionToComponent.put(new Position(x,y), closest3DComp);
203					}
204				}
205			}else{//IF no hittest cache is being used
206				closest3DComp = this.pick(x, y).getNearestPickResult();
207				if (closest3DComp == null){
208					closest3DComp = this;
209				}
210			}
211			lastTimeHitTest = now;
212			
213	//		/*//TODO anders machen..z.b. geclusterte comps einfach als kinder von
214			//?bergeordnetem clusterpoly machen? aber mit clusterPoly.setComposite(TRUE);
215			//Clusterpoly pickable machen damit das hier nicht gebraucht wird?
216			Cluster sel = this.getClusterManager().getCluster(closest3DComp);
217			  if (sel != null){
218				  closest3DComp = sel;
219			  }
220	//		 */
221			  
222//			  //FIXME TEST for stencil clipped scene windows -> we have to return the scenes canvas for some gestures!
223//			  if (closest3DComp != null && closest3DComp instanceof mtClipSceneWindow)
224		  
225		}catch(Exception e){
226			System.err.println("Error while trying to pick an object: ");
227			e.printStackTrace();
228		}
229		/*
230		if (closest3DComp != null)
231			System.out.println("Picked: '" + closest3DComp.getName() + "' at pos (" + x + "," + y + ")");
232		else
233			System.out.println("Picked: '" + closest3DComp + "' at pos (" + x + "," + y + ")");
234		*/
235		return closest3DComp;
236	}
237	
238	
239	/* (non-Javadoc)
240	 * @see com.jMT.input.IHitTestInfoProvider#isBackGroundAt(float, float)
241	 */
242	public boolean isBackGroundAt(float x, float y) {
243		return this.getComponentAt(x, y).equals(this);
244	}
245	
246	
247	//FIXME TEST	
248	@Override
249	public void updateComponent(long timeDelta) {
250		super.updateComponent(timeDelta);
251		this.lastUpdateTime = timeDelta;
252	}
253	//FIXME TEST
254	private boolean calledFromDrawComponent = false;
255	//FIXME TEST
256//	/* 
257//	 * Actually this canvases drawComponent method should be called
258//	 * ever because the canvas should not be added as a child to any component.
259//	 * Anyway - this code will still make it possible to use it as a child of other components
260//	 * (non-Javadoc)
261//	 * @see org.mt4j.components.MTComponent#drawComponent(processing.core.PGraphics)
262//	 */
263//	@Override
264//	public void drawComponent(PGraphics g) { //FIXME this would draw the canvas 2 times..
265//		super.drawComponent(g);
266//		
267//		//Call the canvases scenes draw method to also draw
268//		//stuff defined in an overrriden scenes draw method
269//		if (this.getRenderer() instanceof MTApplication){
270//			MTApplication app = (MTApplication)this.getRenderer();
271//			Iscene[] scenes = app.getScenes();
272//			for (int i = 0; i < scenes.length; i++) {
273//				Iscene iscene = scenes[i];
274//				if (iscene instanceof AbstractScene){
275//					AbstractScene as = (AbstractScene)iscene;
276//					if (as.getCanvas().equals(this)){
277//						this.calledFromDrawComponent = true;
278////						this.drawAndUpdateCanvas(g, this.lastUpdateTime);
279//						as.drawAndUpdate(g, this.lastUpdateTime);
280//						this.calledFromDrawComponent = false;
281//					}
282//				}
283//			}
284//		}
285//		
286////		this.calledFromDrawComponent = true;
287////		this.drawAndUpdateCanvas(g, this.lastUpdateTime);
288////		this.calledFromDrawComponent = false;
289//	}
290	
291	
292	/**
293	 * Updates and then draws every visible object in the canvas.
294	 * First calls the <code>updateComponent(long timeDelta)</code> method. Then
295	 * the <code>drawComponent()</code> method of each object in the scene graph.
296	 * Also handles the setting of cameras attached to the objects.
297	 * @param graphics 
298	 * 
299	 * @param updateTime the time passed since the last update (in ms)
300	 */
301	public void drawAndUpdateCanvas(PGraphics graphics, long updateTime){
302		this.culledObjects = 0;
303		
304		//FIXME THIS IS A HACK! WE SHOULD REPLACE CLUSTERS WITH NORMAL COMPONENTS INSTEAD!
305		//Update cluster components 
306		Cluster[] clusters = getClusterManager().getClusters();
307        for (Cluster cluster : clusters) {
308            cluster.updateComponent(updateTime);
309        }
310		
311		this.drawUpdateRecursive(this, updateTime, graphics);
312//		System.out.println("Culled objects: " + culledObjects);
313	}
314
315	
316	/**
317	 * Draw the whole canvas update recursive.
318	 * 
319	 * @param currentcomp the currentcomp
320	 * @param updateTime the update time
321	 * @param graphics the renderer
322	 */
323	private void drawUpdateRecursive(MTComponent currentcomp, long updateTime, PGraphics graphics){
324		if (currentcomp.isVisible()){
325			//Update current component
326			currentcomp.updateComponent(updateTime);
327			
328			if (currentcomp.getAttachedCamera() != null){
329				//Saves transformations up to this object
330				graphics.pushMatrix();
331				
332				//Resets the modelview completely with a new camera matrix
333				currentcomp.getAttachedCamera().update();
334				
335				if (currentcomp.getParent() != null){
336					//Applies all transforms up to this components parent
337					//because the new camera wiped out all previous transforms
338					Matrix m = currentcomp.getParent().getGlobalMatrix();
339					PGraphics3D pgraphics3D = (PGraphics3D)graphics;
340					pgraphics3D.modelview.apply(
341							m.m00, m.m01, m.m02,  m.m03,
342							m.m10, m.m11, m.m12,  m.m13,
343							m.m20, m.m21, m.m22,  m.m23,
344							m.m30, m.m31, m.m32,  m.m33
345					);
346				}
347				
348				//Apply local transform etc
349				currentcomp.preDraw(graphics);
350				
351				//Check visibility with camera frustum
352				if (frustumCulling){
353					if (currentcomp.isContainedIn(currentcomp.getViewingCamera().getFrustum())){
354						if (!this.calledFromDrawComponent){ //FIXME TEST
355						// DRAW THE COMPONENT  \\
356						currentcomp.drawComponent(graphics);
357						}
358					}else{
359						culledObjects++;
360						//System.out.println("Not visible: " + currentcomp.getName());
361					}
362				}else{
363					if (!this.calledFromDrawComponent){ //FIXME TEST
364					// DRAW THE COMPONENT  \\
365					currentcomp.drawComponent(graphics);
366					}
367				}
368				
369				currentcomp.postDraw(graphics);
370
371				//Draw Children  //FIXME for each loop sometimes throws concurrentmodification error because of fail-fast iterator
372//				for (MTComponent child : currentcomp.getChildList())
373//					drawUpdateRecursive(child, updateTime, graphics);
374				
375				List<MTComponent> childs = currentcomp.getChildList();
376				int childCount = childs.size();
377				for (int i = 0; i < childCount; i++) {
378					drawUpdateRecursive(childs.get(i), updateTime, graphics);
379				}
380
381				currentcomp.postDrawChildren(graphics);
382				
383				//Restores the transforms of the previous camera etc
384				graphics.popMatrix(); 
385			}else{//If no custom camera was set
386				//TODO in abstactvisiblecomp wird outine ?ber gradients und clips
387				//gezeichnet obwohl hier invisble war! FIXME!
388				//evtl applymatrix unapply in eigene methode? dann nur das ausf?hren, kein pre/post draw!
389				
390				//TODO vater an kinder listener -> resize - new geometry -> resize own 
391				
392				currentcomp.preDraw(graphics);
393				
394				if (frustumCulling){
395					//Check visibility with camera frustum
396					if (currentcomp.isContainedIn(currentcomp.getViewingCamera().getFrustum())){
397						// DRAW THE COMPONENT  \\
398						currentcomp.drawComponent(graphics);
399					}else{
400						culledObjects++;
401						//System.out.println("Not visible: " + currentcomp.getName());
402					}
403				}else{
404					// DRAW THE COMPONENT  \\
405					currentcomp.drawComponent(graphics);
406				}
407				
408				currentcomp.postDraw(graphics);
409					
410//				for (MTComponent child : currentcomp.getChildList()) //FIXME for each loop sometimes throws concurrentmodification error because of fail-fast iterator
411//					drawUpdateRecursive(child, updateTime, graphics);
412				
413				List<MTComponent> childs = currentcomp.getChildList();
414				int childCount = childs.size();
415				for (int i = 0; i < childCount; i++) {
416					drawUpdateRecursive(childs.get(i), updateTime, graphics);
417				}
418				
419				currentcomp.postDrawChildren(graphics);
420			}
421		}//if visible end
422	}
423
424	
425	/* (non-Javadoc)
426	 * @see org.mt4j.components.MTComponent#processInputEvent(org.mt4j.input.inputData.MTInputEvent)
427	 */
428	@Override
429	public boolean processInputEvent(MTInputEvent inEvt) {
430		//TODO not very elegant - better approach??
431		if (inEvt.hasTarget() && inEvt.getEventPhase() != MTInputEvent.BUBBLING_PHASE){
432			if (!inEvt.getTarget().equals(this)){ //Avoid recursion
433				//Send directed event to the target component
434				return inEvt.getTarget().processInputEvent(inEvt);
435			}
436		}
437		
438//		return true;  //this.handleEvent
439		//handle in superclass
440		
441		//The MTCanvas get events targeted at him AND events that have no target!
442		return super.processInputEvent(inEvt);
443	}
444	
445		
446	
447	/**
448	 * Gets the cluster manager.
449	 * 
450	 * @return the cluster manager
451	 */
452	public ClusterManager getClusterManager() {
453		return clusterManager;
454	}
455
456
457	/**
458	 * Sets the cluster manager.
459	 * 
460	 * @param selectionManager the new cluster manager
461	 */
462	public void setClusterManager(ClusterManager selectionManager) {
463		this.clusterManager = selectionManager;
464	}
465
466
467	/**
468	 * Gets the cache time delta.
469	 * 
470	 * @return the cache time delta
471	 */
472	public long getCacheTimeDelta() {
473		return cacheTimeDelta;
474	}
475
476	/**
477	 * If repeated calls to getObjectAt(float x, float y) in MTCanvas class
478	 * are called during the provided cacheTimeDelta, the Canvas looks into his
479	 * cache instead of querying all objects again
480	 * Default value is: 80.
481	 * 
482	 * @param cacheTimeDelta the cache time delta
483	 */
484	public void setCacheTimeDelta(long cacheTimeDelta) {
485		this.cacheTimeDelta = cacheTimeDelta;
486	}
487
488	/**
489	 * Checks if is use hit test cache.
490	 * 
491	 * @return true, if is use hit test cache
492	 */
493	public boolean isUseHitTestCache() {
494		return useHitTestCache;
495	}
496
497	
498	/**
499	 * The canvas can be set to look into a hit test cache if
500	 * repeated calls to getComponentAt() with the same coordinates
501	 * during a short period of time are made.
502	 * This period of time can be set with
503	 * <code>setCacheTimeDelta(long cacheTimeDelta)</code>
504	 * <p>
505	 * This is useful for example when a click is made many gestureanalyzers
506	 * call getObjectAt() almost concurrently.
507	 * 
508	 * @param useHitTestCache the use hit test cache
509	 */
510	public void setUseHitTestCache(boolean useHitTestCache) {
511		if (useHitTestCache && !timer.isRunning())
512			timer.start();
513		else if (!useHitTestCache && timer.isRunning())
514			timer.stop();
515		
516		this.useHitTestCache = useHitTestCache;
517	}
518
519
520	/**
521	 * Gets the cache clear time.
522	 * 
523	 * @return the cache clear time
524	 */
525	public int getCacheClearTime() {
526		return cacheClearTime;
527	}
528
529	/**
530	 * Sets the time intervals in ms in which the canvas clears its hit test cache
531	 * Default value is: 20000 ms
532	 * <p>
533	 * This is important to prevent the hit test cache from growing indefinitely.
534	 * 
535	 * @param cacheClearTime the cache clear time
536	 */
537	public void setCacheClearTime(int cacheClearTime) {
538		timer.setDelay(cacheClearTime);
539		this.cacheClearTime = cacheClearTime;
540	}
541
542
543	public boolean isFrustumCulling() {
544		return frustumCulling;
545	}
546
547	public void setFrustumCulling(boolean frustumCulling) {
548		this.frustumCulling = frustumCulling;
549	}
550	
551	
552	
553
554	
555	
556	/**
557	 * Class used for the pickobject cache.
558	 */
559	private class Position{
560		/** The y. */
561		float x,y;
562		
563		/**
564		 * Instantiates a new position.
565		 * 
566		 * @param x the x
567		 * @param y the y
568		 */
569		public Position(float x, float y){
570			this.x = x;
571			this.y = y;
572		}
573		
574		/**
575		 * Gets the x.
576		 * 
577		 * @return the x
578		 */
579		public float getX() {return x;}
580		
581		/**
582		 * Gets the y.
583		 * 
584		 * @return the y
585		 */
586		public float getY() {return y;}
587		
588		/* (non-Javadoc)
589		 * @see java.lang.Object#equals(java.lang.Object)
590		 */
591		@Override
592		public boolean equals(Object arg0) {
593			return (arg0 instanceof Position && ((Position)arg0).getX() == this.getX() && ((Position)arg0).getY() == this.getY());
594		}
595		
596		/* (non-Javadoc)
597		 * @see java.lang.Object#hashCode()
598		 */
599		@Override
600		public int hashCode() {
601			return ((int)x+(int)y);
602		}
603	}
604}