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