View Javadoc

1   package cz.cuni.amis.pogamut.base3d.worldview.object;
2   
3   import java.beans.PropertyEditorManager;
4   import java.beans.PropertyEditorSupport;
5   import java.io.Serializable;
6   import java.util.Locale;
7   import java.util.regex.Matcher;
8   import java.util.regex.Pattern;
9   
10  import javax.vecmath.Matrix3d;
11  import javax.vecmath.Point3d;
12  
13  /**
14   * Rotation within the world.
15   * 
16   * Rotation is represented as yaw, roll and pitch.
17   * 
18   * FIXME[js]: Add working methods and consider imports from Tuple3d.
19   * 
20   * @author Juraj 'Loque' Simlovic
21   * @author Radek 'Black_Hand' Pibil
22   */
23  public class Rotation implements IRotable, Serializable, Cloneable {
24  
25  	/**
26  	 * Rotation representing NONE.
27  	 */
28  	public static final Rotation NONE = new Rotation(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE);
29  	
30  	/**
31  	 * Rotation(0,0,0);
32  	 */
33  	public static final Rotation ZERO = new Rotation();
34  
35  	/**
36  	 * This here is for StoryFactory compatibility reasons. Can be removed in
37  	 * 2012.
38  	 */
39  	static final long serialVersionUID = -1964427510333336912L;
40  
41  	static {
42  		// register property editor, otherwise this class won't be
43  		// introspectable
44  		PropertyEditorManager.registerEditor(Rotation.class, Rotation.PropertyEditor.class);
45  	}
46  
47  	/**
48  	 * Property editor for Rotation. Accepts same format as Location.
49  	 */
50  	public static class PropertyEditor extends PropertyEditorSupport {
51  
52  		@Override
53  		public String getAsText() {
54  			if (getValue() != null) {
55  				return getValue().toString();
56  			} else {
57  				return "null";
58  			}
59  		}
60  
61  		@Override
62  		public void setAsText(String s) {
63  			if ("null".equals(s.trim())) {
64  				setValue(null);
65  				return;
66  			} else {
67  				double[] d = Location.PropertyEditor.parseNumberArray(s);
68  				if (d.length != 3) {
69  					throw new IllegalArgumentException();
70  				}
71  				setValue(new Rotation(d[0], d[1], d[2]));
72  			}
73  		}
74  	}
75  
76  	@Override
77  	public Rotation clone() {
78  		return new Rotation(this);
79  	}
80  
81  	/**
82  	 * Rotation yaw. Yaw is rotation to the left or right. E.g. turn left. The
83  	 * value ranges from -32768..32767.
84  	 */
85  	public final double yaw;
86  	/**
87  	 * Rotation roll. Roll is twist of head. E.g. Tilt the head to shoulder. The
88  	 * value ranges from -32768..32767.
89  	 */
90  	public final double roll;
91  	/**
92  	 * Rotation pitch. Pitch is rotation up and down. E.g. look down. The value
93  	 * ranges from -32768..32767.
94  	 */
95  	public final double pitch;
96  
97  	private Integer hashCode;
98  
99  	/* ********************************************************************** */
100 
101 	/**
102 	 * Rotation yaw. Yaw is rotation to the left or right. E.g. turn left.
103 	 * 
104 	 * @return Rotation yaw. The value ranges from -32768..32767.
105 	 */
106 	public double getYaw() {
107 		return yaw;
108 	}
109 
110 	/**
111 	 * Rotation pitch. Pitch is rotation up and down. E.g. look down.
112 	 * 
113 	 * @return Rotation pitch. The value ranges from -32768..32767.
114 	 */
115 	public double getPitch() {
116 		return pitch;
117 	}
118 
119 	/**
120 	 * Rotation roll. Roll is twist of head. E.g. Tilt the head to shoulder.
121 	 * 
122 	 * @return Rotation roll. The value ranges from -32768..32767.
123 	 */
124 	public double getRoll() {
125 		return roll;
126 	}
127 
128 	/* ********************************************************************** */
129 
130 	/**
131 	 * Retreives the rotation itself to implement {@link IRotable}.
132 	 * 
133 	 * @return The rotation itself (note: does not create a copy).
134 	 */
135 	@Override
136 	public Rotation getRotation() {
137 		return this;
138 	}
139 
140 	/**
141 	 * Retreives javax.vecmath.Point3d representation of the rotation.
142 	 * 
143 	 * @return javax.vecmath.Point3d representation with x, y and z values set.
144 	 */
145 	public Point3d getPoint3d() {
146 		return new Point3d(pitch, yaw, roll);
147 	}
148 
149 	/* ********************************************************************** */
150 
151 	/**
152 	 * Creates rotation with all values set to zeroes.
153 	 */
154 	private Rotation() {
155 		this(0,0,0);
156 	}
157 
158 	/**
159 	 * Creates rotation with specified values.
160 	 * 
161 	 * @param yaw
162 	 *            Rotation yaw. Yaw is rotation to the left or right.
163 	 * @param roll
164 	 *            Rotation roll. Roll is twist of head. E.g. Tilt the head to
165 	 *            shoulder.
166 	 * @param pitch
167 	 *            Rotation pitch. Pitch is rotation up and down.
168 	 */
169 	public Rotation(double pitch, double yaw, double roll) {
170 		this.pitch = pitch;
171 		this.yaw = yaw;
172 		this.roll = roll;
173 	}
174 
175 	/**
176 	 * Copy constructor.
177 	 * 
178 	 * @param rotation
179 	 *            Rotation.
180 	 */
181 	public Rotation(Rotation rotation) {
182 		this(rotation.getPitch(), rotation.getYaw(), rotation.getRoll());
183 	}
184 	
185 	private int computeHashCode() {
186 		final int prime = 31;
187 		int result = 1;
188 		long temp;
189 		temp = Double.doubleToLongBits(pitch);
190 		result = prime * result + (int) (temp ^ (temp >>> 32));
191 		temp = Double.doubleToLongBits(roll);
192 		result = prime * result + (int) (temp ^ (temp >>> 32));
193 		temp = Double.doubleToLongBits(yaw);
194 		result = prime * result + (int) (temp ^ (temp >>> 32));
195 		return result;
196 	}
197 
198 	/**
199 	 * Pattern used to parse {@link Rotation#toString()} in
200 	 * {@link Rotation#Rotation(String)}.
201 	 */
202 	public static final Pattern rotationPattern = Pattern
203 			.compile("\\[([-+]?[0-9]+(\\.[0-9]+){0,1})\\; ([-+]?[0-9]+(\\.[0-9]+){0,1})\\; ([-+]?[0-9]+(\\.[0-9]+){0,1})\\]");
204 
205 	/**
206 	 * Parses the location from the "string" generated by
207 	 * {@link Rotation#toString()}. If it fails, it throws RuntimeException.
208 	 * 
209 	 * @param string
210 	 */
211 	public Rotation(String string) {
212 		Matcher m = rotationPattern.matcher(string);
213 		if (m.find()) {
214 			String strPitch = m.group(1);
215 			String strYaw = m.group(3);
216 			String strRoll = m.group(5);
217 			try {
218 				this.pitch = Double.parseDouble(strPitch);
219 			} catch (Exception e) {
220 				throw new RuntimeException("String '" + string
221 						+ "', was not matched as Rotation, because pitch-value '" + strPitch + "' is not a number.");
222 			}
223 			try {
224 				this.yaw = Double.parseDouble(strYaw);
225 			} catch (Exception e) {
226 				throw new RuntimeException("String '" + string + "', was not matched as Rotation, because yaw-value '"
227 						+ strYaw + "' is not a number.");
228 			}
229 			try {
230 				this.roll = Double.parseDouble(strRoll);
231 			} catch (Exception e) {
232 				throw new RuntimeException("String '" + string + "', was not matched as Rotation, because roll-value '"
233 						+ strRoll + "' is not a number.");
234 			}
235 		} else {
236 			throw new RuntimeException("String '" + string + "' was not matched as Rotation.");
237 		}
238 	}
239 
240 	/**
241 	 * Linearly interpolates between 2 doubles with alpha as strength. That is
242 	 * how close from a to be the interpolation proceeds.
243 	 * 
244 	 * @param a
245 	 *            start
246 	 * @param b
247 	 *            target
248 	 * @param alpha
249 	 *            strength
250 	 * @return interpolated value
251 	 */
252 	public static final double LinearInterp(double a, double b, double alpha) {
253 		return a + (b - a) * alpha;
254 	}
255 
256 	/**
257 	 * Logarithmically interpolates between 2 doubles with alpha as strength.
258 	 * That is how close from a to be the interpolation proceeds.
259 	 * 
260 	 * @param a
261 	 *            start
262 	 * @param b
263 	 *            target
264 	 * @param alpha
265 	 *            strength
266 	 * @return interpolated value
267 	 */
268 	public static final double LogInterp(double a, double b, double alpha) {
269 		return a + (b - a) * Math.log(1 + alpha * (Math.E - 1));
270 	}
271 
272 	/**
273 	 * Exponentially interpolates between 2 doubles with alpha as strength. That
274 	 * is how close from a to be the interpolation proceeds.
275 	 * 
276 	 * @param a
277 	 *            start
278 	 * @param b
279 	 *            target
280 	 * @param alpha
281 	 *            strength
282 	 * @return interpolated value
283 	 */
284 	public static final double ExpInterp(double a, double b, double alpha) {
285 		// (0,1);(1,e)
286 		return a + (b - a) * (Math.exp(alpha) - 1) / (Math.E - 1);
287 	}
288 
289 	/**
290 	 * Linearly interpolates between 2 rotations with alpha as strength. That is
291 	 * how close from a to be the interpolation proceeds. Static version.
292 	 * 
293 	 * @param a
294 	 *            start
295 	 * @param b
296 	 *            target
297 	 * @param alpha
298 	 *            strength
299 	 * @return interpolated rotation
300 	 */
301 	public static final Rotation RotationLinearInterp(Rotation a, Rotation b, double alpha) {
302 		return new Rotation(LinearInterp(a.pitch, b.pitch, alpha), LinearInterp(a.yaw, b.yaw, alpha), LinearInterp(
303 				a.roll, b.roll, alpha));
304 	}
305 
306 	/**
307 	 * Linearly interpolates between 2 rotations with alpha as strength. That is
308 	 * how close from a to be the interpolation proceeds. Dynamic version.
309 	 * 
310 	 * @param a
311 	 *            start
312 	 * @param b
313 	 *            target
314 	 * @param alpha
315 	 *            strength
316 	 * @return interpolated rotation
317 	 */
318 	public final Rotation RotationLinearInterp(Rotation b, double alpha) {
319 		return RotationLinearInterp(this, b, alpha);
320 	}
321 
322 	/**
323 	 * Logarithmically interpolates between 2 rotations with alpha as strength.
324 	 * That is how close from a to be the interpolation proceeds. Static
325 	 * version.
326 	 * 
327 	 * @param a
328 	 *            start
329 	 * @param b
330 	 *            target
331 	 * @param alpha
332 	 *            strength
333 	 * @return interpolated rotation
334 	 */
335 	public static final Rotation RotationLogInterp(Rotation a, Rotation b, double alpha) {
336 		return new Rotation(LogInterp(a.pitch, b.pitch, alpha), LogInterp(a.yaw, b.yaw, alpha), LogInterp(a.roll,
337 				b.roll, alpha));
338 	}
339 
340 	/**
341 	 * Logarithmically interpolates between 2 rotations with alpha as strength.
342 	 * That is how close from a to be the interpolation proceeds. Dynamic
343 	 * version.
344 	 * 
345 	 * @param a
346 	 *            start
347 	 * @param b
348 	 *            target
349 	 * @param alpha
350 	 *            strength
351 	 * @return interpolated rotation
352 	 */
353 	public final Rotation RotationLogInterp(Rotation b, double alpha) {
354 		return RotationLogInterp(this, b, alpha);
355 	}
356 
357 	/**
358 	 * Exponentially interpolates between 2 rotations with alpha as strength.
359 	 * That is how close from a to be the interpolation proceeds. Static
360 	 * version.
361 	 * 
362 	 * @param a
363 	 *            start
364 	 * @param b
365 	 *            target
366 	 * @param alpha
367 	 *            strength
368 	 * @return interpolated rotation
369 	 */
370 	public static final Rotation RotationExpInterp(Rotation a, Rotation b, double alpha) {
371 		return new Rotation(ExpInterp(a.pitch, b.pitch, alpha), ExpInterp(a.yaw, b.yaw, alpha), ExpInterp(a.roll,
372 				b.roll, alpha));
373 	}
374 
375 	/**
376 	 * Exponentially interpolates between 2 rotations with alpha as strength.
377 	 * That is how close from a to be the interpolation proceeds. Dynamic
378 	 * version.
379 	 * 
380 	 * @param a
381 	 *            start
382 	 * @param b
383 	 *            target
384 	 * @param alpha
385 	 *            strength
386 	 * @return interpolated rotation
387 	 */
388 	public final Rotation RotationExpInterp(Rotation b, double alpha) {
389 		return RotationExpInterp(this, b, alpha);
390 	}
391 
392 	/**
393 	 * Used for conversions in from Location into Rotation and vice versa
394 	 */
395 	public enum Order {
396 		YAW_PITCH_ROLL, ROLL_PITCH_YAW, PITCH_YAW_ROLL, PITCH_ROLL_YAW, YAW_ROLL_PITCH, ROLL_YAW_PITCH;
397 	}
398 
399 	/* ********************************************************************** */
400 
401 	/**
402 	 * Generates a hashcode for this Rotation.
403 	 * 
404 	 */
405 	@Override
406 	public int hashCode() {
407 		if (hashCode == null) hashCode = computeHashCode();
408 		return hashCode;
409 	}
410 
411 	/**
412 	 * Tells, whether this objects equals to given rotation.
413 	 * 
414 	 * @param obj
415 	 *            Object to be compared with.
416 	 * @return True, if the object is an instance of rotation has the same values of all three
417 	 *         corresponding values.
418 	 */
419 	@Override
420 	public boolean equals(Object obj) {
421 		if (this == obj)
422 			return true;
423 		if (obj == null)
424 			return false;
425 		if (!(obj instanceof Rotation))
426 			return false;
427 		Rotation other = (Rotation) obj;
428 		if (hashCode != other.hashCode) return false;
429 		if (Double.doubleToLongBits(pitch) != Double.doubleToLongBits(other.pitch))
430 			return false;
431 		if (Double.doubleToLongBits(roll) != Double.doubleToLongBits(other.roll))
432 			return false;
433 		if (Double.doubleToLongBits(yaw) != Double.doubleToLongBits(other.yaw))
434 			return false;
435 		return true;
436 	}
437 
438 	/**
439 	 * Tells, whether two given rotations equal.
440 	 * 
441 	 * @param r1
442 	 *            First rotation to comapre.
443 	 * @param r2
444 	 *            Second rotation to comapre.
445 	 * @return True, if the locations have the same values of all three
446 	 *         corresponding coordinates.
447 	 */
448 	public static boolean equal(Rotation r1, Rotation r2) {
449 		if (r1 == null && r2 == null)
450 			return true;
451 		if (r1 == null || r2 == null)
452 			return false;
453 
454 		return r1.equals(r2);
455 	}
456 
457 	@Override
458 	public String toString() {
459 		return String.format(Locale.ENGLISH, "[%.2f; %.2f; %.2f]", pitch, yaw, roll);
460 	}
461 
462 	/**
463 	 * Converts this Rotation into Location. Using default order.
464 	 * 
465 	 * @return converted Rotation into Location using yaw roll pitch order
466 	 */
467 	public Location toLocation() {
468 		return toLocation(Order.PITCH_YAW_ROLL);
469 	}
470 
471 	/**
472 	 * Converts this Rotation into Location.
473 	 * 
474 	 * @param order
475 	 *            order of rotations should the method use
476 	 * @return converted Rotation into Location
477 	 */
478 	public Location toLocation(Order order) {
479 		Matrix3d yaw = constructXYRot(getYaw() / 32767 * Math.PI);
480 		Matrix3d roll = constructYZRot(getRoll() / 32767 * Math.PI);
481 		Matrix3d pitch = constructXZRot(getPitch() / 32767 * Math.PI);
482 
483 		Location res = new Location(1, 0, 0);
484 
485 		switch (order) {
486 		case YAW_PITCH_ROLL:
487 			return res.mul(yaw).mul(pitch).mul(roll);
488 		case ROLL_PITCH_YAW:
489 			return res.mul(roll).mul(pitch).mul(yaw);
490 		case PITCH_YAW_ROLL:
491 			return res.mul(pitch).mul(yaw).mul(roll);
492 		case PITCH_ROLL_YAW:
493 			return res.mul(pitch).mul(roll).mul(yaw);
494 		case YAW_ROLL_PITCH:
495 			return res.mul(yaw).mul(roll).mul(pitch);
496 		case ROLL_YAW_PITCH:
497 			return res.mul(roll).mul(yaw).mul(pitch);
498 		}
499 
500 		return null;
501 	}
502 
503 	/**
504 	 * Useful methods from Rotation->Location conversions. Constructs rotation
505 	 * in YZ plane (~ ROLL).
506 	 * 
507 	 * @param angle
508 	 * @return projection Matrix3d
509 	 */
510 	public static Matrix3d constructYZRot(double angle) {
511 		Matrix3d res = new Matrix3d();
512 		double cos = Math.cos(angle);
513 		double sin = Math.sin(angle);
514 		res.setM00(1);
515 		res.setM11(cos);
516 		res.setM21(-sin);
517 		res.setM12(sin);
518 		res.setM22(cos);
519 
520 		return res;
521 	}
522 
523 	/**
524 	 * Useful methods from Rotation->Location conversions. Constructs rotation
525 	 * in XZ plane (~ PITCH).
526 	 * 
527 	 * @param angle
528 	 * @return projection Matrix3d
529 	 */
530 	public static Matrix3d constructXZRot(double angle) {
531 		Matrix3d res = new Matrix3d();
532 		double cos = Math.cos(angle);
533 		double sin = Math.sin(angle);
534 		res.setM00(cos);
535 		res.setM20(sin);
536 		res.setM11(1);
537 		res.setM02(-sin);
538 		res.setM22(cos);
539 
540 		return res;
541 	}
542 
543 	/**
544 	 * Useful methods from Rotation->Location conversions. Constructs rotation
545 	 * in XY plane (~ YAW).
546 	 *  
547 	 * @param angle
548 	 * @return projection Matrix3d
549 	 */
550 	public static Matrix3d constructXYRot(double angle) {
551 		Matrix3d res = new Matrix3d();
552 		double cos = Math.cos(angle);
553 		double sin = Math.sin(angle);
554 		res.setM00(cos);
555 		res.setM10(-sin);
556 		res.setM01(sin);
557 		res.setM11(cos);
558 		res.setM22(1);
559 
560 		return res;
561 	}
562 	
563 	/**
564 	 * Sets the yaw.
565 	 * 
566 	 * @return new rotation object
567 	 */
568 	public Rotation setYaw(double yaw) {
569 		return new Rotation(this.pitch, yaw, this.roll);
570 	}
571 		
572 	/**
573 	 * Sets the pitch.
574 	 * 
575 	 * @return new rotation object
576 	 */
577 	public Rotation setPitch(double pitch) {
578 		return new Rotation(pitch, this.yaw, this.roll);
579 	}
580 	
581 	/**
582 	 * Sets the roll.
583 	 * 
584 	 * @return new rotation object
585 	 */
586 	public Rotation setRoll(double roll) {
587 		return new Rotation(this.pitch, this.yaw, roll);
588 	}
589 
590 //	/**
591 //	 * Set this rotation to values from r.
592 //	 * 
593 //	 * @param r
594 //	 *            rotation from which we copy data
595 //	 * @return this rotation after data has been set to r
596 //	 */
597 //	public Rotation setTo(Rotation r) {
598 //		this.yaw = r.yaw;
599 //		this.roll = r.roll;
600 //		this.pitch = r.pitch;
601 //
602 //		return this;
603 //	}
604 //
605 //	/**
606 //	 * Set this rotation to passed values.
607 //	 * 
608 //	 * @param pitch
609 //	 *            new pitch
610 //	 * @param yaw
611 //	 *            new yaw
612 //	 * @param roll
613 //	 *            new roll
614 //	 * @return this rotation after data has been set
615 //	 */
616 //	public Rotation setTo(double pitch, double yaw, double roll) {
617 //		this.yaw = yaw;
618 //		this.roll = roll;
619 //		this.pitch = pitch;
620 //
621 //		return this;
622 //	}
623 
624 }