View Javadoc

1   /**
2    * 
3    */
4   package org.ximtec.igesture.util;
5   
6   import java.awt.Color;
7   import java.awt.Font;
8   import java.awt.Graphics;
9   import java.awt.Graphics2D;
10  import java.awt.Point;
11  import java.awt.Rectangle;
12  import java.awt.RenderingHints;
13  import java.util.ArrayList;
14  import java.util.Iterator;
15  import java.util.List;
16  import java.util.Vector;
17  
18  import org.sigtec.ink.Note;
19  import org.sigtec.ink.Trace;
20  import org.ximtec.igesture.util.additions3d.AccelerationSample;
21  import org.ximtec.igesture.util.additions3d.Accelerations;
22  import org.ximtec.igesture.util.additions3d.RecordedGesture3D;
23  import org.ximtec.igesture.core.Gesture;
24  import org.ximtec.igesture.core.GestureSample;
25  import org.ximtec.igesture.util.additions3d.Point3D;
26  
27  /**
28   * @author Bjorn Puype, bpuype@gmail.com
29   *
30   */
31  public class RecordedGesture3DTool {
32  
33  	public static void paintGesture(RecordedGesture3D gs, Graphics g, int width, int height, boolean drawFieldTitles)
34  	{		
35  	   List<Rectangle> fields = paintStructure(g,width,height, drawFieldTitles);
36  
37  	   ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
38  	            RenderingHints.VALUE_ANTIALIAS_ON);
39  	   
40  		Accelerations acc = gs.getAccelerations();
41  	
42  		// Split the gesture to three planes of type Gesture<Note>
43  		List<Gesture<Note>> notes = splitToPlanes(gs);
44  	
45  		// Get traces out of the note
46  		List<Trace> traces = new Vector<Trace>();
47  		for (int i = 0; i < notes.size(); i++) {
48  			traces.add(notes.get(i).getGesture().get(0));
49  		}
50  		traces = scaleTraces(traces, 10, fields.subList(0, 3));
51  		notes.clear();
52  		for (int i = 0; i < traces.size(); i++) {
53  			Note note = new Note();
54  			note.add(traces.get(i));
55  			Gesture<Note> gesture = new GestureSample("", note);
56  			notes.add(gesture);
57  		}
58  	
59  		// Get the planes from the list for convenience
60  		Gesture<Note> gest = notes.get(0);
61  		Note noteXY = gest.getGesture();
62  		gest = notes.get(1);
63  		Note noteYZ = gest.getGesture();
64  		gest = notes.get(2);
65  		Note noteZX = gest.getGesture();
66  		// Rectangle accelerationsField = fields.get(3);
67  		// Draw planes on g
68  		drawPlane(noteXY, fields.get(0), g);
69  		drawPlane(noteYZ, fields.get(1), g);
70  		drawPlane(noteZX, fields.get(2), g);
71  		// Draw acceleration graphs
72  		drawAccelerationsGraph(acc, fields.get(3), 0.02, g);
73  		
74  		if(drawFieldTitles)
75  			g.drawString(""+gs.size(), width/2-5, height/2+5);
76  		
77  		((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
78  	            RenderingHints.VALUE_ANTIALIAS_OFF);
79    }
80    
81    /**
82     * Draw the fields for the different planes and the accelerations.
83     */
84    public static List<Rectangle> paintStructure(Graphics g, int width, int height, boolean drawFieldTitles)
85    {
86  		g.setColor(Color.WHITE);
87  		g.fillRect(0, 0, width, height);
88  		g.setColor(Color.BLACK);
89  		
90  		// Calculate the drawing fields for the planes
91  		List<Rectangle> fields = calculateFieldSizes(0.02,width,height);
92  		Rectangle XYfield = fields.get(0);
93  		Rectangle YZfield = fields.get(1);
94  		Rectangle ZXfield = fields.get(2);
95  		Rectangle graphField = fields.get(3);
96  		//Draw rectangles around the fields
97  		g.drawRect((int) XYfield.getX(), (int) XYfield.getY(), (int) XYfield.getWidth(), (int) XYfield.getHeight());
98  		g.drawRect((int) YZfield.getX(), (int) YZfield.getY(), (int) YZfield.getWidth(), (int) YZfield.getHeight());
99  		g.drawRect((int) ZXfield.getX(), (int) ZXfield.getY(), (int) ZXfield.getWidth(), (int) ZXfield.getHeight());
100 		g.drawRect((int) graphField.getX(), (int) graphField.getY(), (int) graphField.getWidth(), (int) graphField.getHeight());
101 		if(drawFieldTitles)
102 		{
103 			((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
104 		            RenderingHints.VALUE_ANTIALIAS_ON);
105 			
106 			//Draw titles in the fields
107 			g.setColor(Color.RED);
108 			g.drawString("XY-Plane", (int) (XYfield.getX() + 10),(int) (XYfield.getY() + 15));
109 			g.drawString("YZ-Plane", (int) (YZfield.getX() + 10),(int) (YZfield.getY() + 15));
110 			g.drawString("ZX-Plane", (int) (ZXfield.getX() + 10),(int) (ZXfield.getY() + 15));
111 			g.drawString("Accelerations", (int) (graphField.getX() + 10),(int) (graphField.getY() + 15));
112 			
113 			
114 			((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
115 		            RenderingHints.VALUE_ANTIALIAS_OFF);
116 		}
117 		return fields;
118   }
119   
120   /**
121 	 * Calculates the sizes and positions of the four fields on this panel,
122 	 * using the size of the panel
123 	 * 
124 	 * @param spacePercentage
125 	 *            The margin percentage for the fields from the sides of the
126 	 *            panel
127 	 * @return A list of 4 Rectangles, the fields for this panel
128 	 */
129 	private static List<Rectangle> calculateFieldSizes(double spacePercentage, int width, int height) {
130 		// Keep a distance from the sides of the panel
131 		int spacerHorizontal = (int) (spacePercentage * width);
132 		int spacerVertical = (int) (spacePercentage * height);
133 		// Calculate width and height, they are the same for all four fields
134 		int fieldWidth = (int) ((width - (4 * spacerHorizontal)) * 0.5);
135 		int fieldHeight = (int) ((height - (4 * spacerVertical)) * 0.5);
136 		// Create field rectangles
137 		Rectangle field1 = new Rectangle(spacerHorizontal, spacerVertical,
138 				fieldWidth, fieldHeight);
139 		Rectangle field2 = new Rectangle((spacerHorizontal * 3) + fieldWidth,
140 				spacerVertical, fieldWidth, fieldHeight);
141 		Rectangle field3 = new Rectangle(spacerHorizontal, (spacerVertical * 3)
142 				+ fieldHeight, fieldWidth, fieldHeight);
143 		Rectangle field4 = new Rectangle((spacerHorizontal * 3) + fieldWidth,
144 				(spacerVertical * 3) + fieldHeight, fieldWidth, fieldHeight);
145 		// Add fields to return variable
146 		List<Rectangle> fields = new Vector<Rectangle>();
147 		fields.add(field1);
148 		fields.add(field2);
149 		fields.add(field3);
150 		fields.add(field4);
151 		// Return
152 		return fields;
153 	}
154   
155 	/**
156 	 * Draws a graph with wiimote accelerations on g
157 	 * 
158 	 * @param acc
159 	 *            The WiiAccelerations object containing the data for the graphs
160 	 * @param field
161 	 *            The rectangle in which the graph should be drawn
162 	 * @param spacePercentage
163 	 *            The space marging percentage at the sides
164 	 * @param title
165 	 *            The title of the graph
166 	 * @param g
167 	 *            The Graphics object on which the graph should be drawn
168 	 */
169 	private static void drawAccelerationsGraph(Accelerations acc, Rectangle field,
170 			double spacePercentage, Graphics g) {
171 		g.setColor(Color.BLACK);
172 
173 		if (acc != null) {
174 
175 			// Calculate margin in pixels from spacePercentage
176 			int margin = (int) (spacePercentage * field.getWidth());
177 
178 			// Define fields for X, Y and Z acceleration graphs
179 			Rectangle fieldX = new Rectangle((int) field.getX() + margin,
180 					(int) field.getY() + margin, (int) field.getWidth()
181 							- (2 * margin), (int) (field.getHeight() / 3)
182 							- (2 * margin));
183 			Rectangle fieldY = new Rectangle((int) field.getX() + margin,
184 					(int) (field.getY() + field.getHeight() / 3) + margin,
185 					(int) field.getWidth() - (2 * margin), (int) (field
186 							.getHeight() / 3)
187 							- (2 * margin));
188 			Rectangle fieldZ = new Rectangle((int) field.getX() + margin,
189 					(int) (field.getY() + field.getHeight() * 0.67) + margin,
190 					(int) field.getWidth() - (2 * margin), (int) (field
191 							.getHeight() / 3)
192 							- (2 * margin));
193 
194 			// Create buffers of points to draw for X, Y and Z acceleration
195 			List<List<Point>> buffers = scaleAccelerations(acc, fieldX);
196 
197 			// Draw the graphs
198 			drawGraph(buffers.get(0), fieldX, Color.GRAY, Color.RED, "X",//-Axis
199 					g);
200 			drawGraph(buffers.get(1), fieldY, Color.GRAY, Color.GREEN,
201 					"Y", g);//-Axis
202 			drawGraph(buffers.get(2), fieldZ, Color.GRAY, Color.BLUE, "Z",//-Axis
203 					g);
204 
205 		}
206 	}
207 
208 	/**
209 	 * Scales acceleration data from acc to fit into a rectangle of the size of
210 	 * fieldSize in order to be drawn. Returns a lists of 3 lists of points.
211 	 * 
212 	 * @param acc
213 	 *            the WiiAccelerations object containing the input data
214 	 * @param fieldSize
215 	 *            A rectangle from which the size is used to scale into
216 	 * @return A List of Lists of Points, cotaining the scaled data
217 	 */
218 	private static List<List<Point>> scaleAccelerations(Accelerations acc,
219 			Rectangle fieldSize) {
220 		// Retrieve timestamp of first and last sample
221 		long timeFirst = acc.getFirstSampleTime();
222 		long timeLast = acc.getLastSampleTime();
223 		// Retrieve maximum absolute acceleration value
224 		double maxAbsAcc = acc.getMaxAbsoluteAccelerationValue();
225 		// Find out how high and wide a graph can be
226 		double graphWidth = fieldSize.getWidth();
227 		double graphHeight = fieldSize.getHeight();
228 		// Calculate the vertical scaling factor
229 		double verticalScalingFactor = (0.5 * graphHeight) / maxAbsAcc;
230 		// Calculate the horizontal scaling factor
231 		long gestureLength = timeLast - timeFirst;
232 		double horizontalScalingFactor = graphWidth / gestureLength;
233 		// Calculate the positions to draw
234 		List<Point> bufferX = new Vector<Point>();
235 		List<Point> bufferY = new Vector<Point>();
236 		List<Point> bufferZ = new Vector<Point>();
237 		Iterator<AccelerationSample> i = acc.getSamples().iterator();
238 		while (i.hasNext()) {
239 			AccelerationSample s = i.next();
240 			Point point = new Point();
241 			// X position
242 			double xPos = horizontalScalingFactor
243 					* (s.getTimeStamp() - timeFirst);
244 			double yPos = (graphHeight / 2)
245 					- (verticalScalingFactor * (s.getXAcceleration()));
246 			point.setLocation(xPos, yPos);
247 			bufferX.add(point);
248 			// Y position
249 			point = new Point();
250 			yPos = (graphHeight / 2)
251 					- (verticalScalingFactor * (s.getYAcceleration()));
252 			point.setLocation(xPos, yPos);
253 			bufferY.add(point);
254 			// Z position
255 			point = new Point();
256 			yPos = (graphHeight / 2)
257 					- (verticalScalingFactor * (s.getZAcceleration()));
258 			point.setLocation(xPos, yPos);
259 			bufferZ.add(point);
260 		}
261 		List<List<Point>> buffers = new Vector<List<Point>>();
262 		buffers.add(bufferX);
263 		buffers.add(bufferY);
264 		buffers.add(bufferZ);
265 		return buffers;
266 	}
267 
268 	/**
269 	 * Draws Note plane in Rectangle field on Graphics g
270 	 * 
271 	 * @param plane
272 	 *            the Note that should be drawn
273 	 * @param field
274 	 *            The rectangle in which the Note should be drawn
275 	 * @param title
276 	 *            The title of the plane
277 	 * @param g
278 	 *            The Graphics object onto which the Note should be drawn
279 	 */
280 	private static void drawPlane(Note plane, Rectangle field, Graphics g) {
281 		if (plane != null) {
282 			g.setColor(Color.BLACK);
283 			// Draw all traces from plane on g
284 			for (Trace trace : plane.getTraces()) {
285 				drawTrace(trace, 10, field, g);
286 			}
287 		}
288 	}
289 
290 	/**
291 	 * Draws Trace trace on Graphics g in Rectangle field, while maintaining
292 	 * margin from the sides of the rectangle
293 	 * 
294 	 * @param trace
295 	 *            The Trace that should be drawn
296 	 * @param margin
297 	 *            The margin to the sides of the field in pixels
298 	 * @param field
299 	 *            The Rectangle into which the trace should be drawn
300 	 * @param g
301 	 *            The Graphics object onto which the Trace should be drawn
302 	 */
303 	private static void drawTrace(Trace trace, int margin, Rectangle field, Graphics g) {
304 		// Scale trace to match the field size, taking margin at the sides into
305 		// account
306 		// Create a buffer containing all the points of the trace
307 		List<Point> buffer = new ArrayList<Point>();
308 		for (org.sigtec.ink.Point point : trace.getPoints()) {
309 			buffer.add(new Point((int) point.getX(), (int) point.getY()));
310 		}
311 		// Draw lines on g from point to point within the field
312 		for (int i = 0; i < buffer.size() - 1; i++) {
313 			g.drawLine((int) (buffer.get(i).getX() + field.getX()),
314 					(int) (buffer.get(i).getY() + field.getY()), (int) (buffer
315 							.get(i + 1).getX() + field.getX()), (int) (buffer
316 							.get(i + 1).getY() + field.getY()));
317 		}
318 		// Draw a small circle to mark the startpoint of the gesture
319 		if (buffer.size() > 0)
320 			g.fillOval((int) (buffer.get(0).getX() + +field.getX()),
321 					(int) (buffer.get(0).getY() + field.getY()), 5, 5);
322 	}
323 
324 	/**
325 	 * Scales Traces to fit into the given Rectangles
326 	 * 
327 	 * @param traces
328 	 *            List of traces that should be scaled
329 	 * @param margin
330 	 *            The margin to the sides of the fields in pixels
331 	 * @param fields
332 	 *            List of Rectangles into which the traces should be scaled
333 	 * @return A list of scaled traces
334 	 */
335 	private static List<Trace> scaleTraces(List<Trace> traces, int margin,
336 			List<Rectangle> fields) {
337 		List<Trace> tracesNew = new Vector<Trace>();
338 		double scalingFactor = 0;
339 		// find the scaling factor to use for all traces
340 		double maxXUsed = 0;
341 		double maxYUsed = 0;
342 		double minXAvailable = 100000;
343 		double minYAvailable = 100000;
344 		for (int i = 0; i < traces.size(); i++) {
345 			// Take trace and corresponding rectangle from lists
346 			Trace trace = traces.get(i);
347 			Rectangle field = fields.get(i);
348 			// Find extremes
349 			double maxFoundX = 0;
350 			double minFoundX = 0;
351 			double maxFoundY = 0;
352 			double minFoundY = 0;
353 			for (int j = 0; j < trace.getPoints().size(); j++) {
354 				org.sigtec.ink.Point point = trace.getPoints().get(j);
355 				if (point.getX() > maxFoundX)
356 					maxFoundX = point.getX();
357 				if (point.getY() > maxFoundY)
358 					maxFoundY = point.getY();
359 				if (point.getX() < minFoundX)
360 					minFoundX = point.getX();
361 				if (point.getY() < minFoundY)
362 					minFoundY = point.getY();
363 			}
364 			double xSizeUsed = maxFoundX - minFoundX;
365 			double ySizeUsed = maxFoundY - minFoundY;
366 
367 			if (xSizeUsed > maxXUsed)
368 				maxXUsed = xSizeUsed;
369 			if (ySizeUsed > maxYUsed)
370 				maxYUsed = ySizeUsed;
371 
372 			// Define the playing field
373 			double minAvailableX = field.getX() + margin;
374 			double minAvailableY = field.getY() + margin;
375 			double maxAvailableX = (field.getWidth() + field.getX()) - margin;
376 			double maxAvailableY = (field.getHeight() + field.getY()) - margin;
377 			double xSizeAvailable = maxAvailableX - minAvailableX;
378 			double ySizeAvailable = maxAvailableY - minAvailableY;
379 
380 			if (xSizeAvailable < minXAvailable)
381 				minXAvailable = xSizeAvailable;
382 			if (ySizeAvailable < minYAvailable)
383 				minYAvailable = ySizeAvailable;
384 
385 		}
386 		// Calculate scaling factor
387 		scalingFactor = findScalingFactor(minXAvailable, maxXUsed,
388 				minYAvailable, maxYUsed);
389 		// Scale every Trace using the found scalingfactor
390 		for (int i = 0; i < traces.size(); i++) {
391 			// Take trace and corresponding rectangle from lists
392 			Trace trace = traces.get(i);
393 
394 			// Find extremes
395 			double maxFoundX = 0;
396 			double minFoundX = 0;
397 			double maxFoundY = 0;
398 			double minFoundY = 0;
399 			for (int j = 0; j < trace.getPoints().size(); j++) {
400 				org.sigtec.ink.Point point = trace.getPoints().get(j);
401 				if (point.getX() > maxFoundX)
402 					maxFoundX = point.getX();
403 				if (point.getY() > maxFoundY)
404 					maxFoundY = point.getY();
405 				if (point.getX() < minFoundX)
406 					minFoundX = point.getX();
407 				if (point.getY() < minFoundY)
408 					minFoundY = point.getY();
409 			}
410 
411 			// Move every point inside the playing field
412 			Trace traceNew = new Trace();
413 			for (int k = 0; k < trace.getPoints().size(); k++) {
414 				double xNew = scalingFactor
415 						* (trace.getPoints().get(k).getX() - minFoundX)
416 						+ margin;
417 				double yNew = scalingFactor
418 						* (trace.getPoints().get(k).getY() - minFoundY)
419 						+ margin;
420 				traceNew.add(new org.sigtec.ink.Point(xNew, yNew));
421 			}
422 			tracesNew.add(traceNew);
423 		}
424 		return tracesNew;
425 	}
426 
427 	/**
428 	 * Calculates scaling factor
429 	 * 
430 	 * @param xSizeAvailable
431 	 *            Available size in x direction in pixels
432 	 * @param xSizeUsed
433 	 *            Used size in x direction in source data
434 	 * @param ySizeAvailable
435 	 *            Available size in y direction in pixels
436 	 * @param ySizeUsed
437 	 *            Used size in y direction in source data
438 	 * @return The scaling factor
439 	 */
440 	private static double findScalingFactor(double xSizeAvailable, double xSizeUsed,
441 			double ySizeAvailable, double ySizeUsed) {
442 		double scalingFactor = xSizeAvailable / xSizeUsed;
443 		if ((ySizeAvailable / ySizeUsed) < scalingFactor) // take the smallest
444 			// factor so it will
445 			// definately fit
446 			scalingFactor = ySizeAvailable / ySizeUsed;
447 		return scalingFactor;
448 	}
449 	
450 	/**
451 	 * Draws a graph in color from data into rectangle on g
452 	 * 
453 	 * @param data
454 	 *            The source data for the graph
455 	 * @param field
456 	 *            The Rectangle into which the graph should be drawn
457 	 * @param axisColor
458 	 *            The color of the axis
459 	 * @param dataColor
460 	 *            The color of the data series in the graph
461 	 * @param title
462 	 *            The title of the graph
463 	 * @param g
464 	 *            The Graphics object onto which the graph should be drawn
465 	 */
466 	private static void drawGraph(List<Point> data, Rectangle field, Color axisColor,
467 			Color dataColor, String title, Graphics g) {
468 		//System.err.println("Field: " + field.getX() + "," + field.getY() + " dimensions " + field.getWidth() + "," + field.getHeight());
469 		// Save original color
470 		Color originalColor = g.getColor();
471 		// Draw axes
472 		g.setColor(axisColor);
473 		g.drawLine((int) field.getX(), (int) (field.getY() + (0.5 * field
474 				.getHeight())), (int) (field.getX() + field.getWidth()),
475 				(int) (field.getY() + (0.5 * field.getHeight())));
476 		g.drawLine((int) field.getX(), (int) field.getY(), (int) field.getX(),
477 				(int) (field.getY() + field.getHeight()));
478 		// g.drawRect((int)field.getX(), (int)field.getY(),
479 		// (int)field.getWidth(), (int)field.getHeight());
480 		// Set data color
481 		g.setColor(dataColor);
482 		// Draw title
483 		Font originalFont = g.getFont();
484 		Font font = new Font("Arial", Font.PLAIN, 10);
485 		g.setFont(font);
486 		g.drawString(title, (int) (field.getX() + (0.5 * field.getWidth())),
487 				(int) (field.getY() + 15));
488 		g.setFont(originalFont);
489 		// Draw data
490 		Point lastPoint = new Point();
491 		if (data.size() > 0)
492 			lastPoint.setLocation((int) data.get(0).getX(), (int) data.get(0)
493 					.getY()); // Startpoint
494 		Iterator<Point> it = data.iterator();
495 		while (it.hasNext()) {
496 			Point p = it.next();
497 			int oldX = (int) (field.getX() + lastPoint.getX());
498 			int oldY = (int) (field.getY() + lastPoint.getY());
499 			int newX = (int) (field.getX() + p.getX());
500 			int newY = (int) (field.getY() + p.getY());
501 			g.drawLine(oldX, oldY, newX, newY);
502 			lastPoint = p;
503 			//System.err.println("old: (" + oldX + "," + oldY + ") , new: " + newX + "," + newY + ")");
504 		}
505 		// Set color back to original
506 		g.setColor(originalColor);
507 	}
508   
509 	/**
510 	 * Splits RecordedGesture3D gesture into three 2D planes. The XY-Plane is
511 	 * defined as the plane in 3D space where z=0. The YZ-Plane is defined as
512 	 * the plane in 3D space where x=0. The ZX-Plane is defined as the plane in
513 	 * 3D space where y=0.
514 	 * @param gs 
515 	 * 
516 	 * @return The list of 2D gestures that are the planes. First XY, then YZ
517 	 *         and then ZX.
518 	 */
519 	public static List<Gesture<Note>> splitToPlanes(RecordedGesture3D gs) {//comes from GestureSample3D
520 		Iterator<Point3D> iterator = gs.iterator(); // Iterator on the
521 		// list of
522 		// Point3D in
523 		// gesture
524 		Trace traceXY = new Trace(); // X plane Trace
525 		Trace traceYZ = new Trace(); // Y plane Trace
526 		Trace traceZX = new Trace(); // Z plane Trace
527 		Point3D point3d; // Working variable
528 		// Project all 3d points in gesture on planes
529 		while (iterator.hasNext()) {
530 			point3d = iterator.next();
531 			// Add points to 2d traces
532 			traceXY.add(new org.sigtec.ink.Point(point3d.getX(), point3d.getY(), point3d.getTimeStamp()));
533 			traceYZ.add(new org.sigtec.ink.Point(point3d.getY(), point3d.getZ(), point3d.getTimeStamp()));
534 			traceZX.add(new org.sigtec.ink.Point(point3d.getZ(), point3d.getX(), point3d.getTimeStamp()));
535 		}
536 		// Put traces into Notes
537 		Note noteXY = new Note(); // X plane Note
538 		Note noteYZ = new Note(); // Y plane Note
539 		Note noteZX = new Note(); // Z plane Note
540 		// Add planes to returnlist
541 		noteXY.add(traceXY);
542 		noteYZ.add(traceYZ);
543 		noteZX.add(traceZX);
544 		List<Gesture<Note>> returnList = new ArrayList<Gesture<Note>>(); // Return
545 		// variable
546 		returnList.add(new GestureSample("XY-plane", noteXY));
547 		returnList.add(new GestureSample("YZ-plane", noteYZ));
548 		returnList.add(new GestureSample("ZX-plane", noteZX));
549 		// Return list with three planes
550 		return returnList;
551 	}
552 }