blob: 232d1a72ebc16ad7aac7e73d9c123cdab838ada2 [file] [log] [blame]
import numpy
# joint_center in x-y space.
joint_center = (-12.275, 11.775)
# Joint distances (l1 = "proximal", l2 = "distal")
l1 = 46.25
l2 = 43.75
# Convert from x-y coordinates to theta coordinates.
# orientation is a bool. This orientation is c_i mod 2.
# where c_i is the circular index, or the position in the
# "hyperextension" zones. "cross_point" allows shifting the place where
# it rounds the result so that it draws nicer (no other functional differences).
def to_theta(x, y, orient, cross_point = -numpy.pi):
x -= joint_center[0]
y -= joint_center[1]
l3 = numpy.sqrt(x ** 2 + y ** 2)
t3 = numpy.arctan2(y, x)
t1 = numpy.arccos((l1 ** 2 + l3 ** 2 - l2 ** 2) / (2 * l1 * l3))
if orient:
t1 = -t1
t1 += t3
t1 = (t1 - cross_point) % (2 * numpy.pi) + cross_point
t2 = numpy.arctan2(y - l1 * numpy.sin(t1), x - l1 * numpy.cos(t1))
return (t1, t2)
# Simple trig to go back from theta1, theta2 to x-y
def to_xy(t1, t2):
x = numpy.cos(t1) * l1 + numpy.cos(t2) * l2 + joint_center[0]
y = numpy.sin(t1) * l1 + numpy.sin(t2) * l2 + joint_center[1]
orient = ((t2 - t1) % (2 * numpy.pi)) < numpy.pi
return (x, y, orient)
# Draw a list of lines to a cairo context.
def draw_lines(cr, lines):
cr.move_to(lines[0][0], lines[0][1])
for pt in lines[1:]:
cr.line_to(pt[0], pt[1])
max_dist = 1.0
max_dist_theta = numpy.pi / 64
# Subdivide in theta space.
def subdivide_theta(lines):
out = []
last_pt = lines[0]
out.append(last_pt)
for n_pt in lines[1:]:
for pt in subdivide(last_pt, n_pt, max_dist_theta):
out.append(pt)
last_pt = n_pt
return out
# subdivide in xy space.
def subdivide_xy(lines, max_dist = max_dist):
out = []
last_pt = lines[0]
out.append(last_pt)
for n_pt in lines[1:]:
for pt in subdivide(last_pt, n_pt, max_dist):
out.append(pt)
last_pt = n_pt
return out
# to_theta, but distinguishes between
def to_theta_with_ci(x, y, ci):
t1, t2 = to_theta(x, y, (ci % 2) == 0)
n_ci = int(numpy.floor((t2 - t1) / numpy.pi))
t2 = t2 + ((ci - n_ci)) * numpy.pi
return numpy.array((t1, t2))
# alpha is in [0, 1] and is the weight to merge a and b.
def alpha_blend(a, b, alpha):
return b * alpha + (1 - alpha) * a
# Pure vector normalization.
def normalize(v):
norm = numpy.linalg.norm(v)
if norm == 0:
return v
return v / norm
# CI is circular index and allows selecting between all the stats that map
# to the same x-y state (by giving them an integer index).
# This will compute approximate first and second derivatives with respect
# to path length.
def to_theta_with_ci_and_derivs(x, y, dx, dy, c_i_select):
a = to_theta_with_ci(x, y, c_i_select)
b = to_theta_with_ci(x + dx * 0.0001, y + dy * 0.0001, c_i_select)
c = to_theta_with_ci(x - dx * 0.0001, y - dy * 0.0001, c_i_select)
d1 = normalize(b - a)
d2 = normalize(c - a)
accel = (d1 + d2) / numpy.linalg.norm(a - b)
return (a[0], a[1], d1[0], d1[1], accel[0], accel[1])
# Generic subdivision algorithm.
def subdivide(p1, p2, max_dist):
dx = p2[0] - p1[0]
dy = p2[1] - p1[1]
dist = numpy.sqrt(dx ** 2 + dy ** 2)
n = int(numpy.ceil(dist / max_dist))
return [(alpha_blend(p1[0], p2[0], float(i) / n),
alpha_blend(p1[1], p2[1], float(i) / n)) for i in range(1, n + 1)]
# subdivision thresholds.
max_dist = 1.0
max_dist_theta = numpy.pi / 64
# convert from an xy space loop into a theta loop.
# All segements are expected go from one "hyper-extension" boundary
# to another, thus we must go backwards over the "loop" to get a loop in
# x-y space.
def to_theta_loop(lines, cross_point = -numpy.pi):
out = []
last_pt = lines[0]
for n_pt in lines[1:]:
for pt in subdivide(last_pt, n_pt, max_dist):
out.append(to_theta(pt[0], pt[1], True, cross_point))
last_pt = n_pt
for n_pt in reversed(lines[:-1]):
for pt in subdivide(last_pt, n_pt, max_dist):
out.append(to_theta(pt[0], pt[1], False, cross_point))
last_pt = n_pt
return out
# Convert a loop (list of line segments) into
# The name incorrectly suggests that it is cyclic.
def back_to_xy_loop(lines):
out = []
last_pt = lines[0]
out.append(to_xy(last_pt[0], last_pt[1]))
for n_pt in lines[1:]:
for pt in subdivide(last_pt, n_pt, max_dist_theta):
out.append(to_xy(pt[0], pt[1]))
last_pt = n_pt
return out
items = [to_xy(t1, t2) for t1, t2 in lines]
return [(item[0], item[1]) for item in items]
# Segment in angle space.
class AngleSegment:
def __init__(self, st, ed):
self.st = st
self.ed = ed
def __repr__(self):
return "AngleSegment(%s, %s)" % (repr(self.st), repr(self.ed))
def DrawTo(self, cr, theta_version):
if (theta_version):
cr.move_to(self.st[0], self.st[1])
cr.line_to(self.ed[0], self.ed[1])
else:
draw_lines(cr, back_to_xy_loop([self.st, self.ed]))
def ToThetaPoints(self):
return [self.st, self.ed]
# Segment in X-Y space.
class XYSegment:
def __init__(self, st, ed):
self.st = st
self.ed = ed
def __repr__(self):
return "XYSegment(%s, %s)" % (repr(self.st), repr(self.ed))
def DrawTo(self, cr, theta_version):
if (theta_version):
t1, t2 = self.st
c_i_select = int(numpy.floor((self.st[1] - self.st[0]) / numpy.pi))
st = to_xy(*self.st)
ed = to_xy(*self.ed)
ln = [(st[0], st[1]), (ed[0], ed[1])]
draw_lines(cr, [to_theta_with_ci(x, y, c_i_select) for x, y in subdivide_xy(ln)])
else:
st = to_xy(*self.st)
ed = to_xy(*self.ed)
cr.move_to(st[0], st[1])
cr.line_to(ed[0], ed[1])
# Converts to points in theta space via to_theta_with_ci_and_derivs
def ToThetaPoints(self):
t1, t2 = self.st
c_i_select = int(numpy.floor((self.st[1] - self.st[0]) / numpy.pi))
st = to_xy(*self.st)
ed = to_xy(*self.ed)
ln = [(st[0], st[1]), (ed[0], ed[1])]
dx = ed[0] - st[0]
dy = ed[1] - st[1]
mag = numpy.sqrt((dx) ** 2 + (dy) ** 2)
dx /= mag
dy /= mag
return [to_theta_with_ci_and_derivs(x, y, dx, dy, c_i_select) for x, y in subdivide_xy(ln, 1.0)]
segs = [XYSegment((1.3583511559969876, 0.99753029519739866), (0.97145546090878643, -1.4797428713062153))]
segs = [XYSegment((1.3583511559969876, 0.9975302951973987), (1.5666193247337956, 0.042054827580659759))]