View Javadoc

1   package org.jcurl.demo.tactics;
2   
3   import java.awt.BorderLayout;
4   import java.awt.Color;
5   import java.awt.geom.AffineTransform;
6   import java.awt.geom.Point2D;
7   import java.awt.geom.Rectangle2D;
8   import java.awt.geom.RectangularShape;
9   import java.util.IdentityHashMap;
10  import java.util.Iterator;
11  import java.util.Map;
12  import java.util.Map.Entry;
13  
14  import javax.swing.event.ChangeEvent;
15  
16  import org.apache.commons.logging.Log;
17  import org.jcurl.core.api.ComputedTrajectorySet;
18  import org.jcurl.core.api.Rock;
19  import org.jcurl.core.api.RockSet;
20  import org.jcurl.core.api.RockSetUtils;
21  import org.jcurl.core.api.RockType.Pos;
22  import org.jcurl.core.log.JCLoggerFactory;
23  import org.jcurl.core.ui.BroomPromptModel;
24  import org.jcurl.core.ui.ChangeManager;
25  import org.jcurl.core.ui.PosMemento;
26  import org.jcurl.math.R1RNFunction;
27  import org.jcurl.zui.piccolo.BroomPromptSimple;
28  import org.jcurl.zui.piccolo.PIceFactory;
29  import org.jcurl.zui.piccolo.PRockFactory;
30  import org.jcurl.zui.piccolo.PTrajectoryFactory;
31  
32  import edu.umd.cs.piccolo.PCamera;
33  import edu.umd.cs.piccolo.PCanvas;
34  import edu.umd.cs.piccolo.PLayer;
35  import edu.umd.cs.piccolo.PNode;
36  import edu.umd.cs.piccolo.event.PBasicInputEventHandler;
37  import edu.umd.cs.piccolo.event.PInputEvent;
38  import edu.umd.cs.piccolo.event.PInputEventListener;
39  import edu.umd.cs.piccolo.util.PAffineTransform;
40  import edu.umd.cs.piccolo.util.PPaintContext;
41  
42  public class TrajectoryPiccoloBean extends TrajectoryBean<PNode, PNode> {
43  
44  	/**
45  	 * V -&gt; M Controller. Push rock movements via {@link ChangeManager} to
46  	 * the model.
47  	 */
48  	private class MoveHandler extends PBasicInputEventHandler {
49  		private PosMemento first = null;
50  
51  		private PNode climb(PNode node) {
52  			for (;;) {
53  				if (node.getPickable() && node.getAttribute(ATTR_IDX16) != null)
54  					return node;
55  				node = node.getParent();
56  				if (node == null)
57  					return null;
58  			}
59  		}
60  
61  		@SuppressWarnings("unchecked")
62  		private PosMemento create(final PInputEvent e, final PNode node) {
63  			final RockSet<Pos> rs = (RockSet<Pos>) node
64  					.getAttribute(ATTR_ROCKSET);
65  			final Point2D p = world.globalToLocal(e.getPosition());
66  			if (log.isDebugEnabled())
67  				log.debug(p);
68  			return new PosMemento(rs, (Integer) node.getAttribute(ATTR_IDX16),
69  					p);
70  		}
71  
72  		@Override
73  		public void mouseDragged(final PInputEvent e) {
74  			final PNode node = climb(e.getPickedNode());
75  			if (log.isDebugEnabled())
76  				log.debug(node.getAttribute(ATTR_IDX16));
77  			getChanger().temporary(create(e, node));
78  			e.setHandled(true);
79  		}
80  
81  		@Override
82  		public void mouseEntered(final PInputEvent arg0) {
83  			super.mouseEntered(arg0);
84  			arg0.pushCursor(CURSOR);
85  		}
86  
87  		@Override
88  		public void mouseExited(final PInputEvent arg0) {
89  			super.mouseExited(arg0);
90  			arg0.popCursor();
91  		}
92  
93  		@Override
94  		public void mousePressed(final PInputEvent e) {
95  			final PNode node = climb(e.getPickedNode());
96  			if (log.isDebugEnabled())
97  				log.debug(node.getAttribute(ATTR_IDX16));
98  			final int i16 = (Integer) node.getAttribute(ATTR_IDX16);
99  			final RockSet<Pos> ipos = getCurves().getInitialPos();
100 			first = new PosMemento(ipos, i16, ipos.getRock(i16).p());
101 			e.setHandled(true);
102 		}
103 
104 		@Override
105 		public void mouseReleased(final PInputEvent e) {
106 			final PNode node = climb(e.getPickedNode());
107 			if (log.isDebugEnabled())
108 				log.debug(node.getAttribute(ATTR_IDX16));
109 			getChanger().undoable(first, create(e, node));
110 			e.setHandled(true);
111 		}
112 	}
113 
114 	private static final Log log = JCLoggerFactory
115 			.getLogger(TrajectoryPiccoloBean.class);
116 	private static final int major = 255;
117 	private static final int minor = 64;
118 	private static final long serialVersionUID = -4648771240323713217L;
119 	private static final int WHEEL_DT = 0;
120 	private static final double WHEEL_SCALE = 0.10;
121 
122 	/** update one curve's path */
123 	private static void syncM2V(final ComputedTrajectorySet src, final int i16,
124 			final PNode[] dst, final PTrajectoryFactory tf) {
125 		syncM2V(src.getCurveStore().iterator(i16), i16, dst[i16], tf);
126 	}
127 
128 	/** update one curves' path */
129 	private static void syncM2V(
130 			final Iterator<Entry<Double, R1RNFunction>> src, final int i16,
131 			final PNode dst, final PTrajectoryFactory tf) {
132 		if (log.isDebugEnabled())
133 			log.debug("path " + i16);
134 		tf.refresh(src, RockSet.isDark(i16), 0, 30, dst);
135 	}
136 
137 	/** update one rock */
138 	private static void syncM2V(final Rock<Pos> src, final PNode dst,
139 			final long dt) {
140 		if (src == null || dst == null) {
141 			if (log.isDebugEnabled())
142 				log.debug(src + " " + dst);
143 			return;
144 		}
145 		if (log.isDebugEnabled())
146 			log.debug("rock " + dst.getAttribute(ATTR_IDX16));
147 		if (dt >= 0)
148 			dst.animateToTransform(src.getAffineTransform(), dt);
149 		else
150 			dst.setTransform(src.getAffineTransform());
151 	}
152 
153 	private final long AnimationMillis = 0;
154 	private final BroomPromptSimple broom = new BroomPromptSimple();
155 	private final PNode[] current = new PNode[RockSet.ROCKS_PER_SET];
156 	private final PNode[] initial = new PNode[RockSet.ROCKS_PER_SET];
157 	private final PInputEventListener mouse = new MoveHandler();
158 	private final PCanvas panel;
159 	private final PNode[] path = new PNode[RockSet.ROCKS_PER_SET];
160 	/** Rock<Pos> -> PNode lookup */
161 	private final Map<Rock<Pos>, PNode> r2n = new IdentityHashMap<Rock<Pos>, PNode>();
162 	private final PNode rocks;
163 	private final PTrajectoryFactory tf = new PTrajectoryFactory.Fancy();
164 	private final PNode world;
165 
166 	public TrajectoryPiccoloBean() {
167 		setVisible(false);
168 		broom.setModel(tt);
169 		panel = new PCanvas();
170 		panel.addInputEventListener(new PInputEventListener() {
171 			public void processEvent(final PInputEvent evt, final int i) {
172 				if (evt.isMouseWheelEvent()) {
173 					// http://groups.google.com/group/piccolo2d-users/browse_thread/thread/8bb79024a41a3ae3?hl=en#
174 					final int c = evt.getWheelRotation();
175 					final double s = Math.pow(
176 							1D - WHEEL_SCALE * Math.signum(c), Math.abs(c));
177 					final Point2D p = evt.getPosition();
178 					final PCamera cam = evt.getCamera();
179 					if (WHEEL_DT <= 0)
180 						cam.scaleViewAboutPoint(s, p.getX(), p.getY());
181 					else {
182 						final PAffineTransform t = cam.getViewTransform();
183 						t.scaleAboutPoint(s, p.getX(), p.getY());
184 						cam.animateViewToTransform(t, WHEEL_DT);
185 					}
186 					tmpViewPort = cam.getViewBounds();
187 				}
188 			}
189 		});
190 
191 		panel.setAnimatingRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING);
192 		panel.setInteractingRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING);
193 		setLayout(new BorderLayout());
194 		add(panel, BorderLayout.CENTER);
195 
196 		// create the WC coordinate system
197 		world = new PNode();
198 		// add the ice
199 		world.addChild(new PIceFactory.Fancy().newInstance());
200 		// add initial, current rock and paths
201 		final PNode init = new PNode();
202 		final PNode curr = new PNode();
203 		final PNode paths = new PNode();
204 		rocks = new PNode();
205 		final RockSet<Pos> home = RockSetUtils.allHome();
206 		final RockSet<Pos> out = RockSetUtils.allOut();
207 		final PRockFactory iniRf = new PRockFactory.Fancy(minor);
208 		final PRockFactory curRf = new PRockFactory.Fancy(major);
209 		for (int i16 = RockSet.ROCKS_PER_SET - 1; i16 >= 0; i16--) {
210 			// initial (read/write) rocks
211 			PNode n;
212 			init.addChild(n = initial[i16] = iniRf.newInstance(i16));
213 			n.setPickable(true);
214 			n.addInputEventListener(mouse);
215 
216 			// current (read-only) rocks
217 			curr.addChild(n = current[i16] = curRf.newInstance(i16));
218 			n.addAttribute(ATTR_TRIGGER_CURVE_UPDATE, true);
219 			n.setChildrenPickable(false);
220 
221 			// trajectories (read-only)
222 			paths.addChild(path[i16] = new PNode());
223 		}
224 		rocks.addChild(paths);
225 		rocks.addChild(init);
226 		rocks.addChild(curr);
227 		rocks.addChild(broom);
228 		rocks.setVisible(false);
229 		world.addChild(rocks);
230 
231 		// make world right-handed:
232 		world.setTransform(AffineTransform.getScaleInstance(1, -1));
233 
234 		panel.getLayer().addChild(world);
235 		panel.setBackground(super.getBackground());
236 		setVisible(true);
237 	}
238 
239 	@Override
240 	public BroomPromptModel getBroom() {
241 		return broom.getModel();
242 	}
243 
244 	PLayer getIceLayer() {
245 		return panel.getLayer();
246 	}
247 
248 	@Override
249 	public RectangularShape getZoom() {
250 		if (super.getZoom() == null)
251 			return panel.getCamera().getViewBounds();
252 		return super.getZoom();
253 	}
254 
255 	@Override
256 	public void setBackground(final Color bg) {
257 		panel.setBackground(bg);
258 		super.setBackground(bg);
259 	}
260 
261 	@Override
262 	public void setChanger(final ChangeManager changer) {
263 		super.setChanger(changer);
264 		broom.setChanger(changer);
265 	}
266 
267 	@Override
268 	public void setCurves(final ComputedTrajectorySet curves) {
269 		// rocks.setVisible(model != null);
270 		if (this.curves != null) {
271 			removeCL(this.curves.getInitialPos());
272 			removeCL(this.curves.getCurrentPos());
273 		}
274 		this.curves = curves;
275 		r2n.clear();
276 		if (this.curves != null) {
277 			final RockSet<Pos> ip = this.curves.getInitialPos();
278 			final RockSet<Pos> cp = this.curves.getCurrentPos();
279 			addCL(ip);
280 			addCL(cp);
281 			for (int i16 = RockSet.ROCKS_PER_SET - 1; i16 >= 0; i16--) {
282 				syncM2V(ip, i16, initial[i16]);
283 				syncM2V(cp, i16, current[i16]);
284 				syncM2V(getCurves(), i16, path, tf);
285 			}
286 		}
287 		tt.init(this.curves);
288 		rocks.setVisible(this.curves != null);
289 	}
290 
291 	public void setZoom(final RectangularShape viewport, int transitionMillis) {
292 		if (transitionMillis < 0)
293 			transitionMillis = 333;
294 		final Rectangle2D r;
295 		if (viewport instanceof Rectangle2D)
296 			r = (Rectangle2D) viewport;
297 		else
298 			r = new Rectangle2D.Double(viewport.getX(), viewport.getY(),
299 					viewport.getWidth(), viewport.getHeight());
300 		tmpViewPort = (Rectangle2D) r.clone();
301 		panel.getCamera().animateViewToCenterBounds(r, true, transitionMillis);
302 	}
303 
304 	/** M -&gt; V Controller, update the view. */
305 	public void stateChanged(final ChangeEvent e) {
306 		if (e.getSource() instanceof Rock) {
307 			// update the rock position, be it initial or current
308 			final Rock<Pos> r = (Rock<Pos>) e.getSource();
309 			final PNode n = r2n.get(r);
310 			syncM2V(r, n, AnimationMillis);
311 
312 			// update the trajectory path, current only.
313 			if (!Boolean.TRUE.equals(n.getAttribute(ATTR_TRIGGER_CURVE_UPDATE)))
314 				return;
315 			final int i16 = (Integer) n.getAttribute(ATTR_IDX16);
316 			syncM2V(getCurves(), i16, path, tf);
317 		} else if (log.isDebugEnabled())
318 			log.debug("Unconsumed event from " + e.getSource());
319 	}
320 
321 	/**
322 	 * rather an "init" than sync. Don't use with high frequency.
323 	 * 
324 	 * @param src
325 	 * @param i16
326 	 * @param dst
327 	 * @return
328 	 */
329 	private PNode syncM2V(final RockSet<Pos> src, final int i16, final PNode dst) {
330 		final Rock<Pos> r = src.getRock(i16);
331 		syncM2V(r, dst, 0);
332 		dst.addAttribute(ATTR_ROCKSET, src);
333 		dst.addAttribute(ATTR_ROCK, r);
334 		dst.addAttribute(ATTR_IDX16, i16);
335 		r2n.put(r, dst);
336 		return dst;
337 	}
338 }