package org.squidy.nodes.g2drecognizer; import java.util.ArrayList; import java.util.Random; public class G2DUtils { private static final Random rand = new Random(); public static G2DRectangle FindBox(G2DPoint[] points) { double minX = Double.MAX_VALUE; double maxX = Double.MIN_VALUE; double minY = Double.MAX_VALUE; double maxY = Double.MIN_VALUE; for (G2DPoint p : points) { if (p.X < minX) minX = p.X; if (p.X > maxX) maxX = p.X; if (p.Y < minY) minY = p.Y; if (p.Y > maxY) maxY = p.Y; } return new G2DRectangle(minX, minY, maxX - minX, maxY - minY); } public static double Distance(G2DPoint p1, G2DPoint p2) { double dx = p2.X - p1.X; double dy = p2.Y - p1.Y; return Math.sqrt(dx * dx + dy * dy); } // compute the centroid of the points given public static G2DPoint Centroid(G2DPoint[] points) { double xsum = 0.0; double ysum = 0.0; for (G2DPoint p : points) { xsum += p.X; ysum += p.Y; } return new G2DPoint(xsum / (double)points.length, ysum / (double)points.length); } public static double PathLength(G2DPoint[] points) { double length = 0; for (int i = 1; i < points.length; i++) { length += Distance(points[i - 1], points[i]); } return length; } // determines the angle, in degrees, between two points. the angle is defined // by the circle centered on the start point with a radius to the end point, // where 0 degrees is straight right from start (+x-axis) and 90 degrees is // straight down (+y-axis). public static double AngleInDegrees(G2DPoint start, G2DPoint end, boolean positiveOnly) { double radians = AngleInRadians(start, end, positiveOnly); return Rad2Deg(radians); } // determines the angle, in radians, between two points. the angle is defined // by the circle centered on the start point with a radius to the end point, // where 0 radians is straight right from start (+x-axis) and PI/2 radians is // straight down (+y-axis). public static double AngleInRadians(G2DPoint start, G2DPoint end, boolean positiveOnly) { double radians = 0.0; if (start.X != end.X) { radians = Math.atan2(end.Y - start.Y, end.X - start.X); } else // pure vertical movement { if (end.Y < start.Y) radians = -Math.PI / 2.0; // -90 degrees is straight up else if (end.Y > start.Y) radians = Math.PI / 2.0; // 90 degrees is straight down } if (positiveOnly && radians < 0.0) { radians += Math.PI * 2.0; } return radians; } public static double Rad2Deg(double rad) { return (rad * 180d / Math.PI); } public static double Deg2Rad(double deg) { return (deg * Math.PI / 180d); } // rotate the points by the given degrees about their centroid public static G2DPoint[] RotateByDegrees(G2DPoint[] points, double degrees) { double radians = Deg2Rad(degrees); return RotateByRadians(points, radians); } // rotate the points by the given radians about their centroid public static G2DPoint[] RotateByRadians(G2DPoint[] points, double radians) { G2DPoint[] newPoints = new G2DPoint[points.length]; G2DPoint c = Centroid(points); double cos = Math.cos(radians); double sin = Math.sin(radians); double cx = c.X; double cy = c.Y; for (int i = 0; i < points.length; i++) { G2DPoint p = (G2DPoint) points[i]; double dx = p.X - cx; double dy = p.Y - cy; G2DPoint q = new G2DPoint(0.0, 0.0); q.X = dx * cos - dy * sin + cx; q.Y = dx * sin + dy * cos + cy; newPoints[i] = q; } return newPoints; } // Rotate a point 'p' around a point 'c' by the given radians. // Rotation (around the origin) amounts to a 2x2 matrix of the form: // // [ cos A -sin A ] [ p.x ] // [ sin A cos A ] [ p.y ] // // Note that the C# Math coordinate system has +x-axis stright right and // +y-axis straight down. Rotation is clockwise such that from +x-axis to // +y-axis is +90 degrees, from +x-axis to -x-axis is +180 degrees, and // from +x-axis to -y-axis is -90 degrees. public static G2DPoint RotatePoint(G2DPoint p, G2DPoint c, double radians) { G2DPoint q = new G2DPoint(0d, 0d); q.X = (p.X - c.X) * Math.cos(radians) - (p.Y - c.Y) * Math.sin(radians) + c.X; q.Y = (p.X - c.X) * Math.sin(radians) + (p.Y - c.Y) * Math.cos(radians) + c.Y; return q; } // translates the points so that the upper-left corner of their bounding box lies at 'toPt' public static G2DPoint[] TranslateBBoxTo(G2DPoint[] points, G2DPoint toPt) { G2DPoint[] newPoints = new G2DPoint[points.length]; G2DRectangle r = G2DUtils.FindBox(points); for (int i = 0; i < points.length; i++) { G2DPoint p = (G2DPoint) points[i]; p.X += (toPt.X - r.X); p.Y += (toPt.Y - r.Y); newPoints[i] = p; } return newPoints; } // translates the points so that their centroid lies at 'toPt' public static G2DPoint[] TranslateCentroidTo(G2DPoint[] points, G2DPoint toPt) { G2DPoint[] newPoints = new G2DPoint[points.length]; G2DPoint centroid = Centroid(points); for (int i = 0; i < points.length; i++) { G2DPoint p = (G2DPoint) points[i]; p.X += (toPt.X - centroid.X); p.Y += (toPt.Y - centroid.Y); newPoints[i] = p; } return newPoints; } // translates the points by the given delta amounts public static G2DPoint[] TranslateBy(G2DPoint[] points, G2DSize sz) { G2DPoint[] newPoints = new G2DPoint[points.length]; for (int i = 0; i < points.length; i++) { G2DPoint p = (G2DPoint) points[i]; p.X += sz.Width; p.Y += sz.Height; newPoints[i] = p; } return newPoints; } // scales the points so that they form the size given. does not restore the // origin of the box. public static G2DPoint[] ScaleTo(G2DPoint[] points, G2DSize sz) { G2DPoint[] newPoints = new G2DPoint[points.length]; G2DRectangle r = FindBox(points); for (int i = 0; i < points.length; i++) { G2DPoint p = (G2DPoint) points[i]; if (r.Width != 0d) p.X *= (sz.Width / r.Width); if (r.Height != 0d) p.Y *= (sz.Height / r.Height); newPoints[i] = p; } return newPoints; } // scales by the percentages contained in the 'sz' parameter. values of 1.0 would result in the // identity scale (that is, no change). public static G2DPoint[] ScaleBy(G2DPoint[] points, G2DSize sz) { G2DPoint[] newPoints = new G2DPoint[points.length]; G2DRectangle r = FindBox(points); for (int i = 0; i < points.length; i++) { G2DPoint p = (G2DPoint) points[i]; p.X *= sz.Width; p.Y *= sz.Height; newPoints[i] = p; } return newPoints; } // scales the points so that the length of their longer side // matches the length of the longer side of the given box. // thus, both dimensions are warped proportionally, rather than // independently, like in the function ScaleTo. public static G2DPoint[] ScaleToMax(G2DPoint[] points, G2DRectangle box) { G2DPoint[] newPoints = new G2DPoint[points.length]; G2DRectangle r = FindBox(points); for (int i = 0; i < points.length; i++) { G2DPoint p = (G2DPoint) points[i]; p.X *= (box.getMaxSide() / r.getMaxSide()); p.Y *= (box.getMaxSide() / r.getMaxSide()); newPoints[i] = p; } return newPoints; } // scales the points so that the length of their shorter side // matches the length of the shorter side of the given box. // thus, both dimensions are warped proportionally, rather than // independently, like in the function ScaleTo. public static G2DPoint[] ScaleToMin(G2DPoint[] points, G2DRectangle box) { G2DPoint[] newPoints = new G2DPoint[points.length]; G2DRectangle r = FindBox(points); for (int i = 0; i < points.length; i++) { G2DPoint p = (G2DPoint) points[i]; p.X *= (box.getMinSide() / r.getMinSide()); p.Y *= (box.getMinSide() / r.getMinSide()); newPoints[i] = p; } return newPoints; } public static G2DPoint[] Resample(G2DPoint[] points, int n) { double I = PathLength(points) / (double)(n - 1); // interval length double D = 0.0; ArrayList<G2DPoint> srcPts = new ArrayList<G2DPoint>(); for(int j=0;j<points.length;j++){ srcPts.add(points[j]); } ArrayList<G2DPoint> dstPts = new ArrayList<G2DPoint>(); dstPts.add(srcPts.get(0)); for (int i = 1; i < srcPts.size(); i++) { G2DPoint pt1 = srcPts.get(i - 1); G2DPoint pt2 = srcPts.get(i); double d = Distance(pt1, pt2); if ((D + d) >= I) { double qx = pt1.X + ((I - D) / d) * (pt2.X - pt1.X); double qy = pt1.Y + ((I - D) / d) * (pt2.Y - pt1.Y); G2DPoint q = new G2DPoint(qx, qy); dstPts.add(q); // append new point 'q' srcPts.add(i, q); // insert 'q' at position i in points s.t. 'q' will be the next i D = 0.0; } else { D += d; } } // somtimes we fall a rounding-error short of adding the last point, so add it if so if (dstPts.size() == n - 1) { dstPts.add(srcPts.get(srcPts.size()- 1)); } G2DPoint[] array = new G2DPoint[dstPts.size()]; dstPts.toArray(array); return array; } // computes the 'distance' between two point paths by summing their corresponding point distances. // assumes that each path has been resampled to the same number of points at the same distance apart. public static double PathDistance(G2DPoint[] path1, G2DPoint[] path2) { double distance = 0; for (int i = 0; i < path1.length; i++) { distance += Distance((G2DPoint) path1[i], (G2DPoint) path2[i]); } return distance / (double)path1.length; } /// <summary> /// Gets a random number between low and high, inclusive. /// </summary> /// <param name="low"></param> /// <param name="high"></param> /// <returns></returns> public static int Random(int low, int high) { int tmp = rand.nextInt(high + 1 - low); return tmp + low; } /// <summary> /// Gets multiple random numbers between low and high, inclusive. The /// numbers are guaranteed to be distinct. /// </summary> /// <param name="low"></param> /// <param name="high"></param> /// <param name="num"></param> /// <returns></returns> public static int[] Random(int low, int high, int num) { int[] array = new int[num]; for (int i = 0; i < num; i++) { array[i] = Random(low, high); for (int j = 0; j < i; j++) { if (array[i] == array[j]) { i--; // redo i break; } } } return array; } }