View Javadoc

1   /*
2    * jcurl curling simulation framework https://JCurl.mro.name Copyright (C)
3    * 2005-2009 M. Rohrmoser
4    * 
5    * This program is free software; you can redistribute it and/or modify it under
6    * the terms of the GNU General Public License as published by the Free Software
7    * Foundation; either version 2 of the License, or (at your option) any later
8    * version.
9    * 
10   * This program is distributed in the hope that it will be useful, but WITHOUT
11   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12   * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13   * details.
14   * 
15   * You should have received a copy of the GNU General Public License along with
16   * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17   * Place, Suite 330, Boston, MA 02111-1307 USA
18   */
19  
20  package org.jcurl.demo.tactics;
21  
22  import java.awt.BorderLayout;
23  import java.awt.RenderingHints;
24  import java.awt.event.MouseEvent;
25  import java.awt.geom.AffineTransform;
26  import java.awt.geom.Point2D;
27  import java.awt.geom.RectangularShape;
28  import java.util.IdentityHashMap;
29  import java.util.Iterator;
30  import java.util.Map;
31  import java.util.Map.Entry;
32  
33  import javax.swing.event.ChangeEvent;
34  import javax.swing.event.ChangeListener;
35  
36  import org.apache.commons.logging.Log;
37  import org.jcurl.core.api.ComputedTrajectorySet;
38  import org.jcurl.core.api.Rock;
39  import org.jcurl.core.api.RockSet;
40  import org.jcurl.core.api.RockSetUtils;
41  import org.jcurl.core.api.RockType.Pos;
42  import org.jcurl.core.log.JCLoggerFactory;
43  import org.jcurl.core.ui.BroomPromptModel;
44  import org.jcurl.core.ui.ChangeManager;
45  import org.jcurl.core.ui.PosMemento;
46  import org.jcurl.demo.tactics.sg.AnimateAffine;
47  import org.jcurl.demo.tactics.sg.BroomPromptScenario;
48  import org.jcurl.demo.tactics.sg.SGIceFactory;
49  import org.jcurl.demo.tactics.sg.SGRockFactory;
50  import org.jcurl.demo.tactics.sg.SGTrajectoryFactory;
51  import org.jcurl.math.R1RNFunction;
52  
53  import com.sun.scenario.scenegraph.JSGPanel;
54  import com.sun.scenario.scenegraph.SGComposite;
55  import com.sun.scenario.scenegraph.SGGroup;
56  import com.sun.scenario.scenegraph.SGNode;
57  import com.sun.scenario.scenegraph.SGRenderCache;
58  import com.sun.scenario.scenegraph.SGTransform;
59  import com.sun.scenario.scenegraph.SGComposite.OverlapBehavior;
60  import com.sun.scenario.scenegraph.SGTransform.Affine;
61  import com.sun.scenario.scenegraph.event.SGMouseAdapter;
62  
63  /**
64   * @author <a href="mailto:JCurl@mro.name">M. Rohrmoser </a>
65   * @version $Id$
66   */
67  public class TrajectoryScenarioBean extends TrajectoryBean<Affine, SGGroup>
68  		implements ChangeListener {
69  	private class MoveHandler extends SGMouseAdapter {
70  		private PosMemento first = null;
71  		private final Point2D tmp = new Point2D.Double(0, 0);
72  
73  		@SuppressWarnings("unchecked")
74  		private PosMemento create(final MouseEvent e, final SGNode node) {
75  			final RockSet<Pos> rs = (RockSet<Pos>) node
76  					.getAttribute(ATTR_ROCKSET);
77  			final Point2D p = dc2wc.globalToLocal(e.getPoint(), tmp);
78  			if (log.isDebugEnabled())
79  				log.debug(p);
80  			return new PosMemento(rs, (Integer) node.getAttribute(ATTR_IDX16),
81  					p);
82  		}
83  
84  		@Override
85  		public void mouseDragged(final MouseEvent e, final SGNode node) {
86  			if (log.isDebugEnabled())
87  				log.debug(node.getAttribute(ATTR_IDX16));
88  			getChanger().temporary(create(e, node));
89  		}
90  
91  		@Override
92  		public void mousePressed(final MouseEvent e, final SGNode node) {
93  			if (log.isDebugEnabled())
94  				log.debug(node.getAttribute(ATTR_IDX16));
95  			final int i16 = (Integer) node.getAttribute(ATTR_IDX16);
96  			final RockSet<Pos> ipos = getCurves().getInitialPos();
97  			first = new PosMemento(ipos, i16, ipos.getRock(i16).p());
98  		}
99  
100 		@Override
101 		public void mouseReleased(final MouseEvent e, final SGNode node) {
102 			if (log.isDebugEnabled())
103 				log.debug(node.getAttribute(ATTR_IDX16));
104 			getChanger().undoable(first, create(e, node));
105 		}
106 	}
107 
108 	private static final boolean DoCacheRocks = true;
109 	private static final Log log = JCLoggerFactory
110 			.getLogger(TrajectoryScenarioBean.class);
111 	private static final long serialVersionUID = 6661957210899967106L;
112 
113 	private static Affine createSceneRock(final int i16, final int opacity) {
114 		SGNode rock = new SGRockFactory.Fancy().newInstance(i16);
115 		if (DoCacheRocks) {
116 			final SGRenderCache rc = new SGRenderCache();
117 			rc.setChild(rock);
118 			rc
119 					.setInterpolationHint(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
120 			rock = rc;
121 		}
122 		return SGTransform.createAffine(new AffineTransform(), rock);
123 	}
124 
125 	/** update one curve's path */
126 	private static void syncM2V(final ComputedTrajectorySet src, final int i16,
127 			final SGGroup[] dst, final SGTrajectoryFactory tf) {
128 		syncM2V(src.getCurveStore().iterator(i16), i16, dst[i16], tf);
129 
130 	}
131 
132 	/** update one curves' path */
133 	private static void syncM2V(
134 			final Iterator<Entry<Double, R1RNFunction>> src, final int i16,
135 			final SGGroup dst, final SGTrajectoryFactory tf) {
136 		if (log.isDebugEnabled())
137 			log.debug("path " + i16);
138 		tf.refresh(src, RockSet.isDark(i16), 0, 30, dst);
139 	}
140 
141 	/** update one rock */
142 	private static void syncM2V(final Rock<Pos> src, final Affine dst) {
143 		if (src == null || dst == null) {
144 			if (log.isDebugEnabled())
145 				log.debug(src + " " + dst);
146 			return;
147 		}
148 		if (log.isDebugEnabled())
149 			log.debug("rock " + dst.getAttribute(ATTR_IDX16));
150 		dst.setAffine(src.getAffineTransform());
151 	}
152 
153 	private final BroomPromptScenario broom = new BroomPromptScenario();
154 	private final Affine[] current = new Affine[RockSet.ROCKS_PER_SET];
155 	private final Affine dc2wc;
156 	private final Affine[] initial = new Affine[RockSet.ROCKS_PER_SET];
157 	private final MoveHandler mouse = new MoveHandler();
158 	/** all rocks, trajectories and broomprompt */
159 	private final SGComposite opa_r0 = new SGComposite();
160 	private final SGComposite opa_r1 = new SGComposite();
161 	private final SGComposite opa_t0 = new SGComposite();
162 	private final JSGPanel panel;
163 	private final SGGroup[] path = new SGGroup[RockSet.ROCKS_PER_SET];
164 	/** Rock<Pos> -> SGNode lookup */
165 	private final Map<Rock<Pos>, Affine> r2n = new IdentityHashMap<Rock<Pos>, Affine>();
166 	private final SGGroup rocks = new SGGroup();
167 	private final transient SGTrajectoryFactory tf = new SGTrajectoryFactory.Fancy();
168 	private final Affine zoom;
169 
170 	public TrajectoryScenarioBean() {
171 		setVisible(false);
172 		broom.setModel(tt);
173 		panel = new JSGPanel();
174 		setLayout(new BorderLayout());
175 		add(panel, BorderLayout.CENTER);
176 
177 		final SGGroup world = new SGGroup();
178 		world.add(new SGIceFactory.Fancy().newInstance());
179 
180 		// rocks.setVisible(false);
181 		final SGGroup r0 = new SGGroup();
182 		final SGGroup r1 = new SGGroup();
183 		final SGGroup pa = new SGGroup();
184 		// rocks.add(traj);
185 		final RockSet<Pos> home = RockSetUtils.allHome();
186 		final RockSet<Pos> out = RockSetUtils.allOut();
187 		for (int i16 = RockSet.ROCKS_PER_SET - 1; i16 >= 0; i16--) {
188 			Affine n = createSceneRock(home, i16, 255);
189 			n.setMouseBlocker(true);
190 			n.addMouseListener(mouse);
191 			n.setCursor(CURSOR);
192 			r0.add(initial[i16] = n);
193 			r1.add(current[i16] = n = createSceneRock(out, i16, 255));
194 			n.putAttribute(ATTR_TRIGGER_CURVE_UPDATE, true);
195 			pa.add(path[i16] = new SGGroup());
196 		}
197 		if (false) {
198 			rocks.add(pa);
199 			rocks.add(r0);
200 			rocks.add(r1);
201 		} else {
202 			opa_r0.setChild(r0);
203 			opa_r0.setOpacity(64.0F / 255.0F);
204 			opa_r0.setOverlapBehavior(OverlapBehavior.LAYER);
205 
206 			opa_r1.setChild(r1);
207 			opa_r1.setOverlapBehavior(OverlapBehavior.LAYER);
208 
209 			opa_t0.setChild(pa);
210 			opa_t0.setMouseBlocker(true);
211 			opa_t0.setOverlapBehavior(OverlapBehavior.LAYER);
212 			opa_t0.setOpacity(100.0F / 255.0F);
213 
214 			rocks.add(opa_t0);
215 			rocks.add(opa_r0);
216 			rocks.add(opa_r1);
217 		}
218 		rocks.add(broom.getScene());
219 		rocks.setVisible(false);
220 		world.add(rocks);
221 
222 		// make world right-handed:
223 		final AffineTransform rightHand = AffineTransform.getScaleInstance(1,
224 				-1);
225 		zoom = SGTransform.createAffine(new AffineTransform(),
226 				dc2wc = SGTransform.createAffine(rightHand, world));
227 		broom.setDc2wc(dc2wc);
228 		panel.setScene(zoom);
229 		setVisible(true);
230 	}
231 
232 	private Affine createSceneRock(final RockSet<Pos> pos, final int i16,
233 			final int opacity) {
234 		return syncM2V(pos, i16, createSceneRock(i16, opacity));
235 	}
236 
237 	/**
238 	 * keep the recent viewport visible
239 	 * 
240 	 * @Override public void doLayout() { super.doLayout(); if (tmpViewPort !=
241 	 *           null) zoom.setAffine(AnimateAffine.map(tmpViewPort,
242 	 *           this.getBounds(), zoom.getAffine())); }
243 	 */
244 	@Override
245 	public BroomPromptModel getBroom() {
246 		return broom.getModel();
247 	}
248 
249 	Affine getDc2Wc() {
250 		return dc2wc;
251 	}
252 
253 	@Override
254 	public void setChanger(final ChangeManager changer) {
255 		super.setChanger(changer);
256 		broom.setChanger(changer);
257 	}
258 
259 	@Override
260 	public void setCurves(final ComputedTrajectorySet curves) {
261 		// rocks.setVisible(model != null);
262 		if (this.curves != null) {
263 			removeCL(this.curves.getInitialPos());
264 			removeCL(this.curves.getCurrentPos());
265 		}
266 		this.curves = curves;
267 		r2n.clear();
268 		if (this.curves != null) {
269 			final RockSet<Pos> ip = this.curves.getInitialPos();
270 			final RockSet<Pos> cp = this.curves.getCurrentPos();
271 			addCL(ip);
272 			addCL(cp);
273 			for (int i16 = RockSet.ROCKS_PER_SET - 1; i16 >= 0; i16--) {
274 				syncM2V(ip, i16, initial[i16]);
275 				syncM2V(cp, i16, current[i16]);
276 				syncM2V(getCurves(), i16, path, tf);
277 			}
278 		}
279 		tt.init(this.curves);
280 		rocks.setVisible(this.curves != null);
281 	}
282 
283 	public void setZoom(final RectangularShape viewport,
284 			final int transitionMillis) {
285 		AnimateAffine.animateToCenterBounds(zoom, this.getBounds(),
286 				tmpViewPort = viewport, transitionMillis);
287 	}
288 
289 	public void stateChanged(final ChangeEvent e) {
290 		if (e.getSource() instanceof Rock) {
291 			// update the rock position, be it initial or current
292 			final Rock<Pos> r = (Rock<Pos>) e.getSource();
293 			final Affine n = r2n.get(r);
294 			syncM2V(r, n);
295 
296 			// update the trajectory path, current only.
297 			if (!Boolean.TRUE.equals(n.getAttribute(ATTR_TRIGGER_CURVE_UPDATE)))
298 				return;
299 			final int i16 = (Integer) n.getAttribute(ATTR_IDX16);
300 			syncM2V(getCurves(), i16, path, tf);
301 		} else if (log.isDebugEnabled())
302 			log.debug("Unconsumed event from " + e.getSource());
303 	}
304 
305 	/**
306 	 * rather an "init" than sync. Don't use with high frequency.
307 	 * 
308 	 * @param src
309 	 * @param i16
310 	 * @param dst
311 	 * @return
312 	 */
313 	private Affine syncM2V(final RockSet<Pos> src, final int i16,
314 			final Affine dst) {
315 		final Rock<Pos> r = src.getRock(i16);
316 		syncM2V(r, dst);
317 		dst.putAttribute(ATTR_ROCKSET, src);
318 		dst.putAttribute(ATTR_ROCK, r);
319 		dst.putAttribute(ATTR_IDX16, i16);
320 		r2n.put(r, dst);
321 		return dst;
322 	}
323 }