Parker Schuh | 19b93b1 | 2018-03-02 23:26:58 -0800 | [diff] [blame^] | 1 | import numpy |
| 2 | |
| 3 | # joint_center in x-y space. |
| 4 | joint_center = (-12.275, 11.775) |
| 5 | |
| 6 | # Joint distances (l1 = "proximal", l2 = "distal") |
| 7 | l1 = 46.25 |
| 8 | l2 = 43.75 |
| 9 | |
| 10 | # Convert from x-y coordinates to theta coordinates. |
| 11 | # orientation is a bool. This orientation is c_i mod 2. |
| 12 | # where c_i is the circular index, or the position in the |
| 13 | # "hyperextension" zones. "cross_point" allows shifting the place where |
| 14 | # it rounds the result so that it draws nicer (no other functional differences). |
| 15 | def to_theta(x, y, orient, cross_point = -numpy.pi): |
| 16 | x -= joint_center[0] |
| 17 | y -= joint_center[1] |
| 18 | l3 = numpy.sqrt(x ** 2 + y ** 2) |
| 19 | t3 = numpy.arctan2(y, x) |
| 20 | t1 = numpy.arccos((l1 ** 2 + l3 ** 2 - l2 ** 2) / (2 * l1 * l3)) |
| 21 | |
| 22 | if orient: |
| 23 | t1 = -t1 |
| 24 | t1 += t3 |
| 25 | t1 = (t1 - cross_point) % (2 * numpy.pi) + cross_point |
| 26 | t2 = numpy.arctan2(y - l1 * numpy.sin(t1), x - l1 * numpy.cos(t1)) |
| 27 | return (t1, t2) |
| 28 | |
| 29 | # Simple trig to go back from theta1, theta2 to x-y |
| 30 | def to_xy(t1, t2): |
| 31 | x = numpy.cos(t1) * l1 + numpy.cos(t2) * l2 + joint_center[0] |
| 32 | y = numpy.sin(t1) * l1 + numpy.sin(t2) * l2 + joint_center[1] |
| 33 | orient = ((t2 - t1) % (2 * numpy.pi)) < numpy.pi |
| 34 | return (x, y, orient) |
| 35 | |
| 36 | # Draw a list of lines to a cairo context. |
| 37 | def draw_lines(cr, lines): |
| 38 | cr.move_to(lines[0][0], lines[0][1]) |
| 39 | for pt in lines[1:]: |
| 40 | cr.line_to(pt[0], pt[1]) |
| 41 | |
| 42 | max_dist = 1.0 |
| 43 | max_dist_theta = numpy.pi / 64 |
| 44 | |
| 45 | # Subdivide in theta space. |
| 46 | def subdivide_theta(lines): |
| 47 | out = [] |
| 48 | last_pt = lines[0] |
| 49 | out.append(last_pt) |
| 50 | for n_pt in lines[1:]: |
| 51 | for pt in subdivide(last_pt, n_pt, max_dist_theta): |
| 52 | out.append(pt) |
| 53 | last_pt = n_pt |
| 54 | |
| 55 | return out |
| 56 | |
| 57 | # subdivide in xy space. |
| 58 | def subdivide_xy(lines, max_dist = max_dist): |
| 59 | out = [] |
| 60 | last_pt = lines[0] |
| 61 | out.append(last_pt) |
| 62 | for n_pt in lines[1:]: |
| 63 | for pt in subdivide(last_pt, n_pt, max_dist): |
| 64 | out.append(pt) |
| 65 | last_pt = n_pt |
| 66 | |
| 67 | return out |
| 68 | |
| 69 | # to_theta, but distinguishes between |
| 70 | def to_theta_with_ci(x, y, ci): |
| 71 | t1, t2 = to_theta(x, y, (ci % 2) == 0) |
| 72 | n_ci = int(numpy.floor((t2 - t1) / numpy.pi)) |
| 73 | t2 = t2 + ((ci - n_ci)) * numpy.pi |
| 74 | return numpy.array((t1, t2)) |
| 75 | |
| 76 | # alpha is in [0, 1] and is the weight to merge a and b. |
| 77 | def alpha_blend(a, b, alpha): |
| 78 | return b * alpha + (1 - alpha) * a |
| 79 | |
| 80 | # Pure vector normalization. |
| 81 | def normalize(v): |
| 82 | norm = numpy.linalg.norm(v) |
| 83 | if norm == 0: |
| 84 | return v |
| 85 | return v / norm |
| 86 | |
| 87 | # CI is circular index and allows selecting between all the stats that map |
| 88 | # to the same x-y state (by giving them an integer index). |
| 89 | # This will compute approximate first and second derivatives with respect |
| 90 | # to path length. |
| 91 | def to_theta_with_ci_and_derivs(x, y, dx, dy, c_i_select): |
| 92 | a = to_theta_with_ci(x, y, c_i_select) |
| 93 | b = to_theta_with_ci(x + dx * 0.0001, y + dy * 0.0001, c_i_select) |
| 94 | c = to_theta_with_ci(x - dx * 0.0001, y - dy * 0.0001, c_i_select) |
| 95 | d1 = normalize(b - a) |
| 96 | d2 = normalize(c - a) |
| 97 | accel = (d1 + d2) / numpy.linalg.norm(a - b) |
| 98 | return (a[0], a[1], d1[0], d1[1], accel[0], accel[1]) |
| 99 | |
| 100 | # Generic subdivision algorithm. |
| 101 | def subdivide(p1, p2, max_dist): |
| 102 | dx = p2[0] - p1[0] |
| 103 | dy = p2[1] - p1[1] |
| 104 | dist = numpy.sqrt(dx ** 2 + dy ** 2) |
| 105 | n = int(numpy.ceil(dist / max_dist)) |
| 106 | return [(alpha_blend(p1[0], p2[0], float(i) / n), |
| 107 | alpha_blend(p1[1], p2[1], float(i) / n)) for i in range(1, n + 1)] |
| 108 | |
| 109 | # subdivision thresholds. |
| 110 | max_dist = 1.0 |
| 111 | max_dist_theta = numpy.pi / 64 |
| 112 | |
| 113 | # convert from an xy space loop into a theta loop. |
| 114 | # All segements are expected go from one "hyper-extension" boundary |
| 115 | # to another, thus we must go backwards over the "loop" to get a loop in |
| 116 | # x-y space. |
| 117 | def to_theta_loop(lines, cross_point = -numpy.pi): |
| 118 | out = [] |
| 119 | last_pt = lines[0] |
| 120 | for n_pt in lines[1:]: |
| 121 | for pt in subdivide(last_pt, n_pt, max_dist): |
| 122 | out.append(to_theta(pt[0], pt[1], True, cross_point)) |
| 123 | last_pt = n_pt |
| 124 | for n_pt in reversed(lines[:-1]): |
| 125 | for pt in subdivide(last_pt, n_pt, max_dist): |
| 126 | out.append(to_theta(pt[0], pt[1], False, cross_point)) |
| 127 | last_pt = n_pt |
| 128 | return out |
| 129 | |
| 130 | # Convert a loop (list of line segments) into |
| 131 | # The name incorrectly suggests that it is cyclic. |
| 132 | def back_to_xy_loop(lines): |
| 133 | out = [] |
| 134 | last_pt = lines[0] |
| 135 | out.append(to_xy(last_pt[0], last_pt[1])) |
| 136 | for n_pt in lines[1:]: |
| 137 | for pt in subdivide(last_pt, n_pt, max_dist_theta): |
| 138 | out.append(to_xy(pt[0], pt[1])) |
| 139 | last_pt = n_pt |
| 140 | |
| 141 | return out |
| 142 | |
| 143 | items = [to_xy(t1, t2) for t1, t2 in lines] |
| 144 | return [(item[0], item[1]) for item in items] |
| 145 | |
| 146 | # Segment in angle space. |
| 147 | class AngleSegment: |
| 148 | def __init__(self, st, ed): |
| 149 | self.st = st |
| 150 | self.ed = ed |
| 151 | def __repr__(self): |
| 152 | return "AngleSegment(%s, %s)" % (repr(self.st), repr(self.ed)) |
| 153 | |
| 154 | def DrawTo(self, cr, theta_version): |
| 155 | if (theta_version): |
| 156 | cr.move_to(self.st[0], self.st[1]) |
| 157 | cr.line_to(self.ed[0], self.ed[1]) |
| 158 | else: |
| 159 | draw_lines(cr, back_to_xy_loop([self.st, self.ed])) |
| 160 | |
| 161 | def ToThetaPoints(self): |
| 162 | return [self.st, self.ed] |
| 163 | |
| 164 | # Segment in X-Y space. |
| 165 | class XYSegment: |
| 166 | def __init__(self, st, ed): |
| 167 | self.st = st |
| 168 | self.ed = ed |
| 169 | def __repr__(self): |
| 170 | return "XYSegment(%s, %s)" % (repr(self.st), repr(self.ed)) |
| 171 | def DrawTo(self, cr, theta_version): |
| 172 | if (theta_version): |
| 173 | t1, t2 = self.st |
| 174 | c_i_select = int(numpy.floor((self.st[1] - self.st[0]) / numpy.pi)) |
| 175 | st = to_xy(*self.st) |
| 176 | ed = to_xy(*self.ed) |
| 177 | |
| 178 | ln = [(st[0], st[1]), (ed[0], ed[1])] |
| 179 | draw_lines(cr, [to_theta_with_ci(x, y, c_i_select) for x, y in subdivide_xy(ln)]) |
| 180 | else: |
| 181 | st = to_xy(*self.st) |
| 182 | ed = to_xy(*self.ed) |
| 183 | cr.move_to(st[0], st[1]) |
| 184 | cr.line_to(ed[0], ed[1]) |
| 185 | |
| 186 | # Converts to points in theta space via to_theta_with_ci_and_derivs |
| 187 | def ToThetaPoints(self): |
| 188 | t1, t2 = self.st |
| 189 | c_i_select = int(numpy.floor((self.st[1] - self.st[0]) / numpy.pi)) |
| 190 | st = to_xy(*self.st) |
| 191 | ed = to_xy(*self.ed) |
| 192 | |
| 193 | ln = [(st[0], st[1]), (ed[0], ed[1])] |
| 194 | |
| 195 | dx = ed[0] - st[0] |
| 196 | dy = ed[1] - st[1] |
| 197 | mag = numpy.sqrt((dx) ** 2 + (dy) ** 2) |
| 198 | dx /= mag |
| 199 | dy /= mag |
| 200 | |
| 201 | return [to_theta_with_ci_and_derivs(x, y, dx, dy, c_i_select) for x, y in subdivide_xy(ln, 1.0)] |
| 202 | |
| 203 | segs = [XYSegment((1.3583511559969876, 0.99753029519739866), (0.97145546090878643, -1.4797428713062153))] |
| 204 | segs = [XYSegment((1.3583511559969876, 0.9975302951973987), (1.5666193247337956, 0.042054827580659759))] |