720fc8d1f812f427fe2eff988a713a140a49929d
[deliverable/tracecompass.git] / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / widgets / timegraph / widgets / Utils.java
1 /*****************************************************************************
2 * Copyright (c) 2007, 2014 Intel Corporation, Ericsson
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * Intel Corporation - Initial API and implementation
10 * Ruslan A. Scherbakov, Intel - Initial API and implementation
11 * Alvaro Sanchez-Leon - Udpated for TMF
12 * Patrick Tasse - Refactoring
13 * Marc-Andre Laperle - Add time zone preference
14 *****************************************************************************/
15
16 package org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets;
17
18 import java.text.NumberFormat;
19 import java.text.SimpleDateFormat;
20 import java.util.Date;
21 import java.util.Iterator;
22 import java.util.TimeZone;
23 import java.util.concurrent.TimeUnit;
24
25 import org.eclipse.swt.graphics.Color;
26 import org.eclipse.swt.graphics.Device;
27 import org.eclipse.swt.graphics.GC;
28 import org.eclipse.swt.graphics.Point;
29 import org.eclipse.swt.graphics.Rectangle;
30 import org.eclipse.swt.widgets.Display;
31 import org.eclipse.tracecompass.internal.tmf.ui.Messages;
32 import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimePreferences;
33 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeEvent;
34 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeGraphEntry;
35
36 /**
37 * General utilities and definitions used by the time graph widget
38 *
39 * @author Alvaro Sanchez-Leon
40 * @author Patrick Tasse
41 */
42 public class Utils {
43
44 private Utils() {
45 }
46
47 /** Time format for dates and timestamp */
48 public enum TimeFormat {
49 /** Relative to the start of the trace */
50 RELATIVE,
51
52 /**
53 * Absolute timestamp (ie, relative to the Unix epoch)
54 */
55 CALENDAR,
56
57 /**
58 * Timestamp displayed as a simple number
59 */
60 NUMBER,
61
62 /**
63 * Timestamp displayed as cycles
64 */
65 CYCLES
66 }
67
68 /**
69 * Timestamp resolution
70 */
71 public static enum Resolution {
72 /** seconds */
73 SECONDS,
74
75 /** milliseconds */
76 MILLISEC,
77
78 /** microseconds */
79 MICROSEC,
80
81 /** nanoseconds */
82 NANOSEC
83 }
84
85 private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss"); //$NON-NLS-1$
86 private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); //$NON-NLS-1$
87 private static final long HOURS_PER_DAY = 24;
88 private static final long MIN_PER_HOUR = 60;
89 private static final long SEC_PER_MIN = 60;
90 private static final long SEC_IN_NS = 1000000000;
91 private static final long MILLISEC_IN_NS = 1000000;
92
93 /**
94 * Update the time and date formats to use the current time zone
95 */
96 public static void updateTimeZone() {
97 TimeZone timeZone = TmfTimePreferences.getTimeZone();
98 TIME_FORMAT.setTimeZone(timeZone);
99 DATE_FORMAT.setTimeZone(timeZone);
100 }
101
102 static Rectangle clone(Rectangle source) {
103 return new Rectangle(source.x, source.y, source.width, source.height);
104 }
105
106 /**
107 * Initialize a Rectangle object to default values (all equal to 0)
108 *
109 * @param rect
110 * The Rectangle to initialize
111 */
112 public static void init(Rectangle rect) {
113 rect.x = 0;
114 rect.y = 0;
115 rect.width = 0;
116 rect.height = 0;
117 }
118
119 /**
120 * Initialize a Rectangle object with all the given values
121 *
122 * @param rect
123 * The Rectangle object to initialize
124 * @param x
125 * The X coordinate
126 * @param y
127 * The Y coordinate
128 * @param width
129 * The width of the rectangle
130 * @param height
131 * The height of the rectangle
132 */
133 public static void init(Rectangle rect, int x, int y, int width, int height) {
134 rect.x = x;
135 rect.y = y;
136 rect.width = width;
137 rect.height = height;
138 }
139
140 /**
141 * Initialize a Rectangle object to another existing Rectangle's values.
142 *
143 * @param rect
144 * The Rectangle to initialize
145 * @param source
146 * The reference Rectangle to copy
147 */
148 public static void init(Rectangle rect, Rectangle source) {
149 rect.x = source.x;
150 rect.y = source.y;
151 rect.width = source.width;
152 rect.height = source.height;
153 }
154
155 /**
156 * Reduce the size of a given rectangle by the given amounts.
157 *
158 * @param rect
159 * The rectangle to modify
160 * @param x
161 * The reduction in width
162 * @param y
163 * The reduction in height
164 */
165 public static void deflate(Rectangle rect, int x, int y) {
166 rect.x += x;
167 rect.y += y;
168 rect.width -= x + x;
169 rect.height -= y + y;
170 }
171
172 /**
173 * Increase the size of a given rectangle by the given amounts.
174 *
175 * @param rect
176 * The rectangle to modify
177 * @param x
178 * The augmentation in width
179 * @param y
180 * The augmentation in height
181 */
182 public static void inflate(Rectangle rect, int x, int y) {
183 rect.x -= x;
184 rect.y -= y;
185 rect.width += x + x;
186 rect.height += y + y;
187 }
188
189 static void dispose(Color col) {
190 if (null != col) {
191 col.dispose();
192 }
193 }
194
195 /**
196 * Get the resulting color from a mix of two existing ones for a given
197 * display.
198 *
199 * @param display
200 * The display device (which might affect the color conversion)
201 * @param c1
202 * The first color
203 * @param c2
204 * The second color
205 * @param w1
206 * The gamma level for color 1
207 * @param w2
208 * The gamma level for color 2
209 * @return The resulting color
210 */
211 public static Color mixColors(Device display, Color c1, Color c2, int w1,
212 int w2) {
213 return new Color(display, (w1 * c1.getRed() + w2 * c2.getRed())
214 / (w1 + w2), (w1 * c1.getGreen() + w2 * c2.getGreen())
215 / (w1 + w2), (w1 * c1.getBlue() + w2 * c2.getBlue())
216 / (w1 + w2));
217 }
218
219 /**
220 * Get the system color with the given ID.
221 *
222 * @param id
223 * The color ID
224 * @return The resulting color
225 */
226 public static Color getSysColor(int id) {
227 Color col = Display.getCurrent().getSystemColor(id);
228 return new Color(col.getDevice(), col.getRGB());
229 }
230
231 /**
232 * Get the resulting color from a mix of two existing ones for the current
233 * display.
234 *
235 * @param col1
236 * The first color
237 * @param col2
238 * The second color
239 * @param w1
240 * The gamma level for color 1
241 * @param w2
242 * The gamma level for color 2
243 * @return The resulting color
244 */
245 public static Color mixColors(Color col1, Color col2, int w1, int w2) {
246 return mixColors(Display.getCurrent(), col1, col2, w1, w2);
247 }
248
249 /**
250 * Draw text in a rectangle.
251 *
252 * @param gc
253 * The SWT GC object
254 * @param text
255 * The text to draw
256 * @param rect
257 * The rectangle object which is being drawn
258 * @param transp
259 * If true the background will be transparent
260 * @return The width of the written text
261 */
262 public static int drawText(GC gc, String text, Rectangle rect, boolean transp) {
263 Point size = gc.stringExtent(text);
264 gc.drawText(text, rect.x, rect.y, transp);
265 return size.x;
266 }
267
268 /**
269 * Draw text at a given location.
270 *
271 * @param gc
272 * The SWT GC object
273 * @param text
274 * The text to draw
275 * @param x
276 * The X coordinate of the starting point
277 * @param y
278 * the Y coordinate of the starting point
279 * @param transp
280 * If true the background will be transparent
281 * @return The width of the written text
282 */
283 public static int drawText(GC gc, String text, int x, int y, boolean transp) {
284 Point size = gc.stringExtent(text);
285 gc.drawText(text, x, y, transp);
286 return size.x;
287 }
288
289 /**
290 * Draw text in a rectangle, trimming the text to prevent exceeding the specified width.
291 *
292 * @param gc
293 * The SWT GC object
294 * @param text
295 * The string to be drawn
296 * @param x
297 * The x coordinate of the top left corner of the rectangular area where the text is to be drawn
298 * @param y
299 * The y coordinate of the top left corner of the rectangular area where the text is to be drawn
300 * @param width
301 * The width of the area to be drawn
302 * @param isCentered
303 * If <code>true</code> the text will be centered in the available width if space permits
304 * @param isTransparent
305 * If <code>true</code> the background will be transparent, otherwise it will be opaque
306 * @return The number of characters written
307 */
308 public static int drawText(GC gc, String text, int x, int y, int width, boolean isCentered, boolean isTransparent) {
309 if (width < 1) {
310 return 0;
311 }
312
313 int len = text.length();
314 int textWidth = 0;
315 boolean isReallyCentered = isCentered;
316 int realX = x;
317
318 while (len > 0) {
319 textWidth = gc.stringExtent(text.substring(0, len)).x;
320 if (textWidth <= width) {
321 break;
322 }
323 isReallyCentered = false;
324 len--;
325 }
326 if (len > 0) {
327 if (isReallyCentered) {
328 realX += (width - textWidth) / 2;
329 }
330 gc.drawText(text.substring(0, len), realX, y, isTransparent);
331 }
332 return len;
333 }
334
335 /**
336 * Formats time in format: MM:SS:NNN
337 *
338 * @param time time
339 * @param format 0: MMMM:ss:nnnnnnnnn, 1: HH:MM:ss MMM.mmmm.nnn
340 * @param resolution the resolution
341 * @return the formatted time
342 */
343 public static String formatTime(long time, TimeFormat format, Resolution resolution) {
344 switch (format) {
345 case CALENDAR:
346 return formatTimeAbs(time, resolution);
347 case NUMBER:
348 return NumberFormat.getInstance().format(time);
349 case CYCLES:
350 return NumberFormat.getInstance().format(time) + Messages.Utils_ClockCyclesUnit;
351 case RELATIVE:
352 default:
353 }
354
355 StringBuffer str = new StringBuffer();
356 long t = time;
357 boolean neg = t < 0;
358 if (neg) {
359 t = -t;
360 str.append('-');
361 }
362
363 long sec = t / SEC_IN_NS;
364 str.append(sec);
365 String ns = formatNs(t, resolution);
366 if (!ns.equals("")) { //$NON-NLS-1$
367 str.append('.');
368 str.append(ns);
369 }
370
371 return str.toString();
372 }
373
374 /**
375 * From input time in nanoseconds, convert to Date format YYYY-MM-dd
376 *
377 * @param absTime
378 * The source time, in ns
379 * @return the formatted date
380 */
381 public static String formatDate(long absTime) {
382 String sdate = DATE_FORMAT.format(new Date(absTime / MILLISEC_IN_NS));
383 return sdate;
384 }
385
386 /**
387 * Formats time in ns to Calendar format: HH:MM:SS MMM.mmm.nnn
388 *
389 * @param time
390 * The source time, in ns
391 * @param res
392 * The resolution to use
393 * @return the formatted time
394 */
395 public static String formatTimeAbs(long time, Resolution res) {
396 StringBuffer str = new StringBuffer();
397
398 // format time from nanoseconds to calendar time HH:MM:SS
399 String stime = TIME_FORMAT.format(new Date(time / MILLISEC_IN_NS));
400 str.append(stime);
401 str.append('.');
402 // append the Milliseconds, MicroSeconds and NanoSeconds as specified in
403 // the Resolution
404 str.append(formatNs(time, res));
405 return str.toString();
406 }
407
408 /**
409 * Formats time delta
410 *
411 * @param delta
412 * The time delta, in ns
413 * @param format
414 * The time format to use
415 * @param resolution
416 * The resolution to use
417 * @return the formatted time delta
418 */
419 public static String formatDelta(long delta, TimeFormat format, Resolution resolution) {
420 if (format == TimeFormat.CALENDAR) {
421 return formatDeltaAbs(delta, resolution);
422 }
423 return formatTime(delta, format, resolution);
424 }
425
426 /**
427 * Formats time delta in ns to Calendar format, only formatting the years,
428 * days, hours or minutes if necessary.
429 *
430 * @param delta
431 * The time delta, in ns
432 * @param resolution
433 * The resolution to use
434 * @return the formatted time delta
435 */
436 public static String formatDeltaAbs(long delta, Resolution resolution) {
437 StringBuffer str = new StringBuffer();
438 if (delta < 0) {
439 str.append('-');
440 }
441 long ns = Math.abs(delta);
442 long seconds = TimeUnit.NANOSECONDS.toSeconds(ns);
443 long minutes = TimeUnit.NANOSECONDS.toMinutes(ns);
444 long hours = TimeUnit.NANOSECONDS.toHours(ns);
445 long days = TimeUnit.NANOSECONDS.toDays(ns);
446 if (days > 0) {
447 str.append(days);
448 str.append("d "); //$NON-NLS-1$
449 }
450 if (hours > 0) {
451 str.append(hours % HOURS_PER_DAY);
452 str.append("h "); //$NON-NLS-1$
453 }
454 if (minutes > 0) {
455 str.append(minutes % MIN_PER_HOUR);
456 str.append("m "); //$NON-NLS-1$
457 }
458 str.append(seconds % SEC_PER_MIN);
459 str.append('.');
460 // append the ms, us and ns as specified in the resolution
461 str.append(formatNs(delta, resolution));
462 str.append("s"); //$NON-NLS-1$
463 return str.toString();
464 }
465
466 /**
467 * Obtains the remainder fraction on unit Seconds of the entered value in
468 * nanoseconds. e.g. input: 1241207054171080214 ns The number of fraction
469 * seconds can be obtained by removing the last 9 digits: 1241207054 the
470 * fractional portion of seconds, expressed in ns is: 171080214
471 *
472 * @param srcTime
473 * The source time in ns
474 * @param res
475 * The Resolution to use
476 * @return the formatted nanosec
477 */
478 public static String formatNs(long srcTime, Resolution res) {
479 StringBuffer str = new StringBuffer();
480 long ns = Math.abs(srcTime % SEC_IN_NS);
481 String nanos = Long.toString(ns);
482 str.append("000000000".substring(nanos.length())); //$NON-NLS-1$
483 str.append(nanos);
484
485 if (res == Resolution.MILLISEC) {
486 return str.substring(0, 3);
487 } else if (res == Resolution.MICROSEC) {
488 return str.substring(0, 6);
489 } else if (res == Resolution.NANOSEC) {
490 return str.substring(0, 9);
491 }
492 return ""; //$NON-NLS-1$
493 }
494
495 /**
496 * FIXME Currently does nothing.
497 *
498 * @param opt
499 * The option name
500 * @param def
501 * The option value
502 * @param min
503 * The minimal accepted value
504 * @param max
505 * The maximal accepted value
506 * @return The value that was read
507 */
508 public static int loadIntOption(String opt, int def, int min, int max) {
509 return def;
510 }
511
512 /**
513 * FIXME currently does nothing
514 *
515 * @param opt
516 * The option name
517 * @param val
518 * The option value
519 */
520 public static void saveIntOption(String opt, int val) {
521 }
522
523 static ITimeEvent getFirstEvent(ITimeGraphEntry entry) {
524 if (null == entry || ! entry.hasTimeEvents()) {
525 return null;
526 }
527 Iterator<ITimeEvent> iterator = entry.getTimeEventsIterator();
528 if (iterator != null && iterator.hasNext()) {
529 return iterator.next();
530 }
531 return null;
532 }
533
534 /**
535 * Gets the {@link ITimeEvent} at the given time from the given
536 * {@link ITimeGraphEntry}.
537 *
538 * @param entry
539 * a {@link ITimeGraphEntry}
540 * @param time
541 * a timestamp
542 * @param n
543 * this parameter means: <list> <li>-1: Previous Event</li> <li>
544 * 0: Current Event</li> <li>
545 * 1: Next Event</li> <li>2: Previous Event when located in a non
546 * Event Area </list>
547 * @return a {@link ITimeEvent}, or <code>null</code>.
548 */
549 public static ITimeEvent findEvent(ITimeGraphEntry entry, long time, int n) {
550 if (null == entry || ! entry.hasTimeEvents()) {
551 return null;
552 }
553 Iterator<ITimeEvent> iterator = entry.getTimeEventsIterator();
554 if (iterator == null) {
555 return null;
556 }
557 ITimeEvent nextEvent = null;
558 ITimeEvent currEvent = null;
559 ITimeEvent prevEvent = null;
560
561 while (iterator.hasNext()) {
562 nextEvent = iterator.next();
563 long nextStartTime = nextEvent.getTime();
564
565 if (nextStartTime > time) {
566 break;
567 }
568
569 if (currEvent == null || currEvent.getTime() != nextStartTime ||
570 (nextStartTime != time && currEvent.getDuration() != nextEvent.getDuration())) {
571 prevEvent = currEvent;
572 currEvent = nextEvent;
573 }
574 }
575
576 if (n == -1) { //previous
577 if (currEvent != null && currEvent.getTime() + currEvent.getDuration() >= time) {
578 return prevEvent;
579 }
580 return currEvent;
581 } else if (n == 0) { //current
582 if (currEvent != null && currEvent.getTime() + currEvent.getDuration() >= time) {
583 return currEvent;
584 }
585 return null;
586 } else if (n == 1) { //next
587 if (nextEvent != null && nextEvent.getTime() > time) {
588 return nextEvent;
589 }
590 return null;
591 } else if (n == 2) { //current or previous when in empty space
592 return currEvent;
593 }
594
595 return null;
596 }
597
598 /**
599 * Pretty-print a method signature.
600 *
601 * @param origSig
602 * The original signature
603 * @return The pretty signature
604 */
605 public static String fixMethodSignature(String origSig) {
606 String sig = origSig;
607 int pos = sig.indexOf('(');
608 if (pos >= 0) {
609 String ret = sig.substring(0, pos);
610 sig = sig.substring(pos);
611 sig = sig + " " + ret; //$NON-NLS-1$
612 }
613 return sig;
614 }
615
616 /**
617 * Restore an original method signature from a pretty-printed one.
618 *
619 * @param ppSig
620 * The pretty-printed signature
621 * @return The original method signature
622 */
623 public static String restoreMethodSignature(String ppSig) {
624 String ret = ""; //$NON-NLS-1$
625 String sig = ppSig;
626
627 int pos = sig.indexOf('(');
628 if (pos >= 0) {
629 ret = sig.substring(0, pos);
630 sig = sig.substring(pos + 1);
631 }
632 pos = sig.indexOf(')');
633 if (pos >= 0) {
634 sig = sig.substring(0, pos);
635 }
636 String args[] = sig.split(","); //$NON-NLS-1$
637 StringBuffer result = new StringBuffer("("); //$NON-NLS-1$
638 for (int i = 0; i < args.length; i++) {
639 String arg = args[i].trim();
640 if (arg.length() == 0 && args.length == 1) {
641 break;
642 }
643 result.append(getTypeSignature(arg));
644 }
645 result.append(")").append(getTypeSignature(ret)); //$NON-NLS-1$
646 return result.toString();
647 }
648
649 /**
650 * Get the mangled type information from an array of types.
651 *
652 * @param typeStr
653 * The types to convert. See method implementation for what it
654 * expects.
655 * @return The mangled string of types
656 */
657 public static String getTypeSignature(String typeStr) {
658 int dim = 0;
659 String type = typeStr;
660 for (int j = 0; j < type.length(); j++) {
661 if (type.charAt(j) == '[') {
662 dim++;
663 }
664 }
665 int pos = type.indexOf('[');
666 if (pos >= 0) {
667 type = type.substring(0, pos);
668 }
669 StringBuffer sig = new StringBuffer(""); //$NON-NLS-1$
670 for (int j = 0; j < dim; j++)
671 {
672 sig.append("["); //$NON-NLS-1$
673 }
674 if (type.equals("boolean")) { //$NON-NLS-1$
675 sig.append('Z');
676 } else if (type.equals("byte")) { //$NON-NLS-1$
677 sig.append('B');
678 } else if (type.equals("char")) { //$NON-NLS-1$
679 sig.append('C');
680 } else if (type.equals("short")) { //$NON-NLS-1$
681 sig.append('S');
682 } else if (type.equals("int")) { //$NON-NLS-1$
683 sig.append('I');
684 } else if (type.equals("long")) { //$NON-NLS-1$
685 sig.append('J');
686 } else if (type.equals("float")) { //$NON-NLS-1$
687 sig.append('F');
688 } else if (type.equals("double")) { //$NON-NLS-1$
689 sig.append('D');
690 } else if (type.equals("void")) { //$NON-NLS-1$
691 sig.append('V');
692 }
693 else {
694 sig.append('L').append(type.replace('.', '/')).append(';');
695 }
696 return sig.toString();
697 }
698
699 /**
700 * Compare two doubles together.
701 *
702 * @param d1
703 * First double
704 * @param d2
705 * Second double
706 * @return 1 if they are different, and 0 if they are *exactly* the same.
707 * Because of the way doubles are stored, it's possible for the
708 * same number obtained in two different ways to actually look
709 * different.
710 */
711 public static int compare(double d1, double d2) {
712 if (d1 > d2) {
713 return 1;
714 }
715 if (d1 < d2) {
716 return 1;
717 }
718 return 0;
719 }
720
721 /**
722 * Compare two character strings alphabetically. This is simply a wrapper
723 * around String.compareToIgnoreCase but that will handle cases where
724 * strings can be null
725 *
726 * @param s1
727 * The first string
728 * @param s2
729 * The second string
730 * @return A number below, equal, or greater than zero if the first string
731 * is smaller, equal, or bigger (alphabetically) than the second
732 * one.
733 */
734 public static int compare(String s1, String s2) {
735 if (s1 != null && s2 != null) {
736 return s1.compareToIgnoreCase(s2);
737 }
738 if (s1 != null) {
739 return 1;
740 }
741 if (s2 != null) {
742 return -1;
743 }
744 return 0;
745 }
746
747 /**
748 * Calculates the square of the distance between two points.
749 *
750 * @param x1
751 * x-coordinate of point 1
752 * @param y1
753 * y-coordinate of point 1
754 * @param x2
755 * x-coordinate of point 2
756 * @param y2
757 * y-coordinate of point 2
758 *
759 * @return the square of the distance in pixels^2
760 */
761 public static double distance2(int x1, int y1, int x2, int y2) {
762 int dx = x2 - x1;
763 int dy = y2 - y1;
764 int d2 = dx * dx + dy * dy;
765 return d2;
766 }
767
768 /**
769 * Calculates the distance between a point and a line segment. If the point
770 * is in the perpendicular region between the segment points, return the
771 * distance from the point to its projection on the segment. Otherwise
772 * return the distance from the point to its closest segment point.
773 *
774 * @param px
775 * x-coordinate of the point
776 * @param py
777 * y-coordinate of the point
778 * @param x1
779 * x-coordinate of segment point 1
780 * @param y1
781 * y-coordinate of segment point 1
782 * @param x2
783 * x-coordinate of segment point 2
784 * @param y2
785 * y-coordinate of segment point 2
786 *
787 * @return the distance in pixels
788 */
789 public static double distance(int px, int py, int x1, int y1, int x2, int y2) {
790 double length2 = distance2(x1, y1, x2, y2);
791 if (length2 == 0) {
792 return Math.sqrt(distance2(px, py, x1, y1));
793 }
794 // 'r' is the ratio of the position, between segment point 1 and segment
795 // point 2, of the projection of the point on the segment
796 double r = ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / length2;
797 if (r <= 0.0) {
798 // the projection is before segment point 1, return distance from
799 // the point to segment point 1
800 return Math.sqrt(distance2(px, py, x1, y1));
801 }
802 if (r >= 1.0) {
803 // the projection is after segment point 2, return distance from
804 // the point to segment point 2
805 return Math.sqrt(distance2(px, py, x2, y2));
806 }
807 // the projection is between the segment points, return distance from
808 // the point to its projection on the segment
809 int x = (int) (x1 + r * (x2 - x1));
810 int y = (int) (y1 + r * (y2 - y1));
811 return Math.sqrt(distance2(px, py, x, y));
812 }
813 }
This page took 0.057422 seconds and 4 git commands to generate.