utils3D.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. import numpy as np
  2. from numpy.matlib import repmat
  3. import pyrr as pyrr
  4. import os
  5. import sys
  6. sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../../Sampling-based Planning/")
  7. from rrt_3D.plot_util3D import visualization
  8. def getRay(x, y):
  9. direc = [y[0] - x[0], y[1] - x[1], y[2] - x[2]]
  10. return np.array([x, direc])
  11. def getAABB(blocks):
  12. AABB = []
  13. for i in blocks:
  14. AABB.append(np.array([np.add(i[0:3], -0), np.add(i[3:6], 0)])) # make AABBs alittle bit of larger
  15. return AABB
  16. def getDist(pos1, pos2):
  17. return np.sqrt(sum([(pos1[0] - pos2[0]) ** 2, (pos1[1] - pos2[1]) ** 2, (pos1[2] - pos2[2]) ** 2]))
  18. ''' The following utils can be used for rrt or rrt*,
  19. required param initparams should have
  20. env, environement generated from env3D
  21. V, node set
  22. E, edge set
  23. i, nodes added
  24. maxiter, maximum iteration allowed
  25. stepsize, leaf growth restriction
  26. '''
  27. def sampleFree(initparams, bias = 0.1):
  28. '''biased sampling'''
  29. x = np.random.uniform(initparams.env.boundary[0:3], initparams.env.boundary[3:6])
  30. i = np.random.random()
  31. if isinside(initparams, x):
  32. return sampleFree(initparams)
  33. else:
  34. if i < bias:
  35. return np.array(initparams.xt) + 1
  36. else:
  37. return x
  38. return x
  39. # ---------------------- Collision checking algorithms
  40. def isinside(initparams, x):
  41. '''see if inside obstacle'''
  42. for i in initparams.env.blocks:
  43. if isinbound(i, x):
  44. return True
  45. for i in initparams.env.OBB:
  46. if isinbound(i, x, mode = 'obb'):
  47. return True
  48. for i in initparams.env.balls:
  49. if isinball(i, x):
  50. return True
  51. return False
  52. def isinbound(i, x, mode = False, factor = 0, isarray = False):
  53. if mode == 'obb':
  54. return isinobb(i, x, isarray)
  55. if isarray:
  56. compx = (i[0] - factor <= x[:,0]) & (x[:,0] < i[3] + factor)
  57. compy = (i[1] - factor <= x[:,1]) & (x[:,1] < i[4] + factor)
  58. compz = (i[2] - factor <= x[:,2]) & (x[:,2] < i[5] + factor)
  59. return compx & compy & compz
  60. else:
  61. return i[0] - factor <= x[0] < i[3] + factor and i[1] - factor <= x[1] < i[4] + factor and i[2] - factor <= x[2] < i[5]
  62. def isinobb(i, x, isarray = False):
  63. # transform the point from {W} to {body}
  64. if isarray:
  65. pts = (i.T@np.column_stack((x, np.ones(len(x)))).T).T[:,0:3]
  66. block = [- i.E[0],- i.E[1],- i.E[2],+ i.E[0],+ i.E[1],+ i.E[2]]
  67. return isinbound(block, pts, isarray = isarray)
  68. else:
  69. pt = i.T@np.append(x,1)
  70. block = [- i.E[0],- i.E[1],- i.E[2],+ i.E[0],+ i.E[1],+ i.E[2]]
  71. return isinbound(block, pt)
  72. def isinball(i, x, factor = 0):
  73. if getDist(i[0:3], x) <= i[3] + factor:
  74. return True
  75. return False
  76. def lineSphere(p0, p1, ball):
  77. # https://cseweb.ucsd.edu/classes/sp19/cse291-d/Files/CSE291_13_CollisionDetection.pdf
  78. c, r = ball[0:3], ball[-1]
  79. line = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]]
  80. d1 = [c[0] - p0[0], c[1] - p0[1], c[2] - p0[2]]
  81. t = (1 / (line[0] * line[0] + line[1] * line[1] + line[2] * line[2])) * (
  82. line[0] * d1[0] + line[1] * d1[1] + line[2] * d1[2])
  83. if t <= 0:
  84. if (d1[0] * d1[0] + d1[1] * d1[1] + d1[2] * d1[2]) <= r ** 2: return True
  85. elif t >= 1:
  86. d2 = [c[0] - p1[0], c[1] - p1[1], c[2] - p1[2]]
  87. if (d2[0] * d2[0] + d2[1] * d2[1] + d2[2] * d2[2]) <= r ** 2: return True
  88. elif 0 < t < 1:
  89. x = [p0[0] + t * line[0], p0[1] + t * line[1], p0[2] + t * line[2]]
  90. k = [c[0] - x[0], c[1] - x[1], c[2] - x[2]]
  91. if (k[0] * k[0] + k[1] * k[1] + k[2] * k[2]) <= r ** 2: return True
  92. return False
  93. def lineAABB(p0, p1, dist, aabb):
  94. # https://www.gamasutra.com/view/feature/131790/simple_intersection_tests_for_games.php?print=1
  95. # aabb should have the attributes of P, E as center point and extents
  96. mid = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2, (p0[2] + p1[2]) / 2] # mid point
  97. I = [(p1[0] - p0[0]) / dist, (p1[1] - p0[1]) / dist, (p1[2] - p0[2]) / dist] # unit direction
  98. hl = dist / 2 # radius
  99. T = [aabb.P[0] - mid[0], aabb.P[1] - mid[1], aabb.P[2] - mid[2]]
  100. # do any of the principal axis form a separting axis?
  101. if abs(T[0]) > (aabb.E[0] + hl * abs(I[0])): return False
  102. if abs(T[1]) > (aabb.E[1] + hl * abs(I[1])): return False
  103. if abs(T[2]) > (aabb.E[2] + hl * abs(I[2])): return False
  104. # I.cross(x axis) ?
  105. r = aabb.E[1] * abs(I[2]) + aabb.E[2] * abs(I[1])
  106. if abs(T[1] * I[2] - T[2] * I[1]) > r: return False
  107. # I.cross(y axis) ?
  108. r = aabb.E[0] * abs(I[2]) + aabb.E[2] * abs(I[0])
  109. if abs(T[2] * I[0] - T[0] * I[2]) > r: return False
  110. # I.cross(z axis) ?
  111. r = aabb.E[0] * abs(I[1]) + aabb.E[1] * abs(I[0])
  112. if abs(T[0] * I[1] - T[1] * I[0]) > r: return False
  113. return True
  114. def lineOBB(p0, p1, dist, obb):
  115. # transform points to obb frame
  116. res = obb.T@np.column_stack([np.array([p0,p1]),[1,1]]).T
  117. # record old position and set the position to origin
  118. oldP, obb.P= obb.P, [0,0,0]
  119. # calculate segment-AABB testing
  120. ans = lineAABB(res[0:3,0],res[0:3,1],dist,obb)
  121. # reset the position
  122. obb.P = oldP
  123. return ans
  124. def isCollide(initparams, x, child, dist=None):
  125. '''see if line intersects obstacle'''
  126. '''specified for expansion in A* 3D lookup table'''
  127. if dist==None:
  128. dist = getDist(x, child)
  129. # check in bound
  130. if not isinbound(initparams.env.boundary, child):
  131. return True, dist
  132. # check collision in AABB
  133. for i in range(len(initparams.env.AABB)):
  134. if lineAABB(x, child, dist, initparams.env.AABB[i]):
  135. return True, dist
  136. # check collision in ball
  137. for i in initparams.env.balls:
  138. if lineSphere(x, child, i):
  139. return True, dist
  140. # check collision with obb
  141. for i in initparams.env.OBB:
  142. if lineOBB(x, child, dist, i):
  143. return True, dist
  144. return False, dist
  145. # ---------------------- leaf node extending algorithms
  146. def nearest(initparams, x):
  147. V = np.array(initparams.V)
  148. if initparams.i == 0:
  149. return initparams.V[0]
  150. xr = repmat(x, len(V), 1)
  151. dists = np.linalg.norm(xr - V, axis=1)
  152. return tuple(initparams.V[np.argmin(dists)])
  153. def near(initparams, x):
  154. x = np.array(x)
  155. V = np.array(initparams.V)
  156. cardV = len(initparams.V)
  157. eta = initparams.eta
  158. gamma = initparams.gamma
  159. r = min(gamma * (np.log(cardV) / cardV), eta)
  160. if initparams.done:
  161. r = 1
  162. if initparams.i == 0:
  163. return [initparams.V[0]]
  164. xr = repmat(x, len(V), 1)
  165. inside = np.linalg.norm(xr - V, axis=1) < r
  166. nearpoints = V[inside]
  167. return np.array(nearpoints)
  168. def steer(initparams, x, y):
  169. dist, step = getDist(y, x), initparams.stepsize
  170. increment = ((y[0] - x[0]) / dist * step, (y[1] - x[1]) / dist * step, (y[2] - x[2]) / dist * step)
  171. xnew = (x[0] + increment[0], x[1] + increment[1], x[2] + increment[2])
  172. # direc = (y - x) / np.linalg.norm(y - x)
  173. # xnew = x + initparams.stepsize * direc
  174. return xnew
  175. def cost(initparams, x):
  176. '''here use the additive recursive Cost function'''
  177. if x == initparams.x0:
  178. return 0
  179. return cost(initparams, initparams.Parent[x]) + getDist(x, initparams.Parent[x])
  180. def path(initparams, Path=[], dist=0):
  181. x = initparams.xt
  182. while x != initparams.x0:
  183. x2 = initparams.Parent[x]
  184. Path.append(np.array([x, x2]))
  185. dist += getDist(x, x2)
  186. x = x2
  187. return Path, dist
  188. class edgeset(object):
  189. def __init__(self):
  190. self.E = {}
  191. def add_edge(self, edge):
  192. x, y = edge[0], edge[1]
  193. if x in self.E:
  194. self.E[x].add(y)
  195. else:
  196. self.E[x] = set()
  197. self.E[x].add(y)
  198. def remove_edge(self, edge):
  199. x, y = edge[0], edge[1]
  200. self.E[x].remove(y)
  201. def get_edge(self, nodes = None):
  202. edges = []
  203. if nodes is None:
  204. for v in self.E:
  205. for n in self.E[v]:
  206. # if (n,v) not in edges:
  207. edges.append((v, n))
  208. else:
  209. for v in nodes:
  210. for n in self.E[tuple(v)]:
  211. edges.append((v, n))
  212. return edges
  213. def isEndNode(self, node):
  214. return node not in self.E
  215. class Node:
  216. def __init__(self, data):
  217. self.data = data
  218. self.sibling = None
  219. self.child = None
  220. class Tree:
  221. def __init__(self, start):
  222. self.root = Node(start)
  223. self.ind = 0
  224. self.index = {start:self.ind}
  225. def add_edge(self, edge):
  226. # y exists in the tree while x does not
  227. x, y = edge[0], edge[1]