/src/org/mt4j/components/MTCanvas.java
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}