-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmotion_planner.py
4045 lines (3409 loc) · 151 KB
/
motion_planner.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python
# coding: utf-8
import numpy as np
import os,sys
import matplotlib.pyplot as plt
import matplotlib.ticker as plticker
from matplotlib import patches
import time
import argparse
import operator
from math import acos
from collections import defaultdict,namedtuple
import pylab
import random
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
from pympler.asizeof import asizeof
try:
import cPickle as pickle
except:
logging.warn("Unable to load cPickle. Falling back to python pickle.")
import pickle
__doc__ = "Simple geometric motion planner for Valkyrie Robotics 2017 FIRST competition."
__author__ = "Mark E. Plutowski"
__credits__ = "Valkyrie Robotics LLC"
__copyright__ = "Copyright 2017 Mark E. Plutowski"
__license__ = "MIT"
__version__ = "1.0.0"
## namedtuple reduces memory 42% vs dict
StateNamedtuple = namedtuple('State', 'goal1 dist type r goal2 dist2 rot samples')
StateNamedtuple.__new__.__defaults__ = (None, None, None, None, 1) # goal1,dist,type required
class StateNT(object):
"""
Wraps StateNamedtuple with getters to facilitate easily swapping with StateP.
This is an unpacked version of StateP
"""
__slots__ = ('state') # prevents adding fields but reduces mem consumption by 36%
def __init__(self, goal1, dist, type, r=None, goal2=None, dist2=None, rot=None, samples=1):
self.state = StateNamedtuple(goal1,dist,type,r,goal2,dist2,rot,samples)
def goal1(self):
return self.state.goal1
def dist(self):
return self.state.dist
def type(self):
return self.state.type
def r(self):
return self.state.r
def goal2(self):
return self.state.goal2
def dist2(self):
return self.state.dist2
def rot(self):
return self.state.rot
def samples(self):
return self.state.samples
def __repr__(self):
return 'StateNT(goal1=%s,dist=%s,type=%s,r=%s,goal2=%s,dist2=%s,rot=%s,samples=%d' % \
(self.goal1, self.dist, self.type, self.r, self.goal2, self.dist2, self.rot, self.samples)
TURN_TYPES = ['lh', 'rh', '|', 'qt']
class StateP(object):
"""
Bytearray packed configuration state.
Namedtuple can require up to 1000 bytes per stage2 row,
"""
# See also unpack_v23
__slots__ = ('state')
def __init__(self, goal1, dist, type, r=None, goal2=None, dist2=None, rot=None, samples=1):
if type not in TURN_TYPES:
raise ValueError("Bad turntype: %s" % type)
self.state = pack_v23(StateNamedtuple(goal1,dist,type,r,goal2,dist2,rot,samples))
def goal1(self):
return (bytes_to_int(self.state[0:2])-SHIFT, bytes_to_int(self.state[2:4])-SHIFT, bytes_to_int(self.state[4:6])-SHIFT)
def dist(self):
return bytes_to_int(self.state[6:8])/100.0
def rawtype(self):
return chr(self.state[8])
def type(self):
type = self.rawtype()
return 'qt' if type in ('q','Q') else '|' if type in ('f', 'b') else 'rh' if type == 'r' else 'lh' if type == 'l' else None
def r(self):
val = bytes_to_int(self.state[9:11])
return None if val == NULL_2BYTE else val/10.0
def goal2(self):
val = (bytes_to_int(self.state[11:13]), bytes_to_int(self.state[13:15]), bytes_to_int(self.state[15:17]))
return None if val == NULL_XYT else (val[0]-SHIFT,val[1]-SHIFT,val[2]-SHIFT)
def dist2(self):
val = bytes_to_int(self.state[17:19])
rawtype = self.rawtype()
return None if val == NULL_2BYTE else -val/100.0 if rawtype=='b' else val/100.0 if rawtype in ['f','l','r','q','Q'] else None
def rot(self):
val = bytes_to_int(self.state[19:21])
rawtype = self.rawtype()
return None if val == NULL_2BYTE else -val/10.0 if rawtype=='Q' else val/10.0 if rawtype=='q' else None
def samples(self):
return bytes_to_int(self.state[21:23])
def __repr__(self):
state = unpack_v23(self.state)
return 'StateP(goal1=%s,dist=%s,type=%s,r=%s,goal2=%s,dist2=%s,rot=%s,samples=%d' % \
(state.goal1(), state.dist(), state.type(), state.r(), state.goal2(), state.dist2(), state.rot(), state.samples())
def pp(self):
"""
Same as __repr__ but using self methods.
"""
return 'StateP(goal1=%s,dist=%s,type=%s,r=%s,goal2=%s,dist2=%s,rot=%s,samples=%d' % \
(self.goal1(), self.dist(), self.type(), self.r(), self.goal2(), self.dist2(), self.rot(), self.samples())
# Gives the option of easily switching between a packed and unpacked representation.
# Class based on namedtuple is simpler however consumes much more memory.
# Note that the pickled file must be in the same representation selected here or loading it will fail.
#State = StateNamedtuple
State = StateP
# Planning goal can be either north peg or northeast peg
NORTH = "north"
NORTHEAST = "northeast"
## Dimensions of robot and field components.
BUMPER = 3.5 # likely 3", but adding half-inch for safety margin
ROBOT_WIDTH = 27.5
ROBOT_LENGTH = 28.5
WW = ROBOT_WIDTH + 2*BUMPER # robot width used in motion planning
LL = ROBOT_LENGTH + 2*BUMPER # robot length used in motion planning
# Spec'd : 34.5 x 34.81
# Chassis Measured: 27.5w x 28.375long (sans bumpers)
# Estimated with 3.5" bumpers: 34.5" x 35.375"
##
## CAD field measurements:
##
## Distance between airship and Alliance Station (the ”north wall”): 144.142
## - measured at Bellarmine practice field: 113"
NWALL_TO_AIRSHIP = 144.142
## (b) closest distance between airship and east wall, to left of drivers as they look onto field: 121.715 inches
## - measured at Bellarmine practice field: 122.5"
## - add 40" to get x => 162.5" = 13.54'
## (c) closest distance between airship and west wall: 121.965 inches
## - add 40" to get x => 161.965
## = 13.5'
CENTER_AIRSHIP_TO_WEST_WALL = 162
## (d) dimensions of the airship base (side lengths, angles, divider length): 38.375 inches, 120 degrees, 22 inches
## (e) are the corners of the field square or angled? angled
## (f) distance from alliance wall to midfield line that gives the 5 point for autonomous crossing : 93.5 inches
# Goal peg on wall facing alliance station.
N_PEG_XY = (0, 0)
PEG_LENGTH = 10
N_PEG_LINE = ([0, 0], [0, PEG_LENGTH]) # Line representing goal peg, as (X, Y) = (x1,x2),(y1,y2)
# Divider between n airship wall and nw airship wall
DIVIDER2 = ([-19, -33], [0, 21]) # X, Y
# Divider between n airship wall and ne airship wall
DIVIDER3 = ([19, 33], [0, 21]) # X, Y
def xy_dist(p1, p2=(0, 0)):
return np.linalg.norm(np.array(p1) - np.array(p2))
# Wall facing alliance station.
AIRSHIP_N_WALL = ([-19, 19],[0, 0]) # X,Y line, inches
# Wall facing alliance station.
AIRSHIP_S_WALL = ([-19, 19],[-70.6, -70.6]) # X,Y line, inches
# Northwest airship wall. This is the airship wall clockwise looking down with alliance station to north
AIRSHIP_NE_WALL = ([19, 40.035], [0,-35.295]) # X,Y line, eminating down at an angle from divider2.
AIRSHIP_NE_WALL_ANGLE = 1.047 # downward angle between wall3 and ray directed east from 0,0 (==radians(60))
AIRSHIP_NE_WALL_WIDTH = xy_dist((AIRSHIP_NE_WALL[0][0],AIRSHIP_NE_WALL[1][0],),(AIRSHIP_NE_WALL[0][1],AIRSHIP_NE_WALL[1][1],))
# Northwest airship wall. This is the airship wall counterclockwise looking down
AIRSHIP_NW_WALL = ([-19, -40.035], [0,-35.295]) # X,Y line, eminating down at an angle from divider1.
AIRSHIP_NW_WALL_ANGLE = -1.047 # downward angle between wall2 and ray directed east from 0,0 (==radians(-60))
AIRSHIP_NW_WALL_WIDTH = xy_dist((AIRSHIP_NW_WALL[0][0],AIRSHIP_NW_WALL[1][0],),(AIRSHIP_NW_WALL[0][1],AIRSHIP_NW_WALL[1][1],))
# Peg on northeast wall
NE_PEG_ANGLE = np.pi - AIRSHIP_NE_WALL_ANGLE # upward angle between ne peg and ray directed east from its base.
NE_PEG_TIP_XOFF = PEG_LENGTH * np.sin(NE_PEG_ANGLE) # y offset of ne peg tip relative to its own base
NE_PEG_TIP_YOFF = - PEG_LENGTH * np.cos(NE_PEG_ANGLE) # x offset of ne peg tip relative to its own base
NE_PEG_XY = ((AIRSHIP_NE_WALL[0][0]+AIRSHIP_NE_WALL[0][1])/2.0, (AIRSHIP_NE_WALL[1][0]+AIRSHIP_NE_WALL[1][1])/2.0) # (x,y) of ne peg base
NE_PEG = ([NE_PEG_XY[0],NE_PEG_XY[0]+NE_PEG_TIP_XOFF], [NE_PEG_XY[1],NE_PEG_XY[1]+NE_PEG_TIP_YOFF]) # (X,Y) line for peg3
# Peg on northwest wall
NW_PEG_ANGLE = np.pi - AIRSHIP_NW_WALL_ANGLE # upward angle between peg2 and ray directed east from its base.
NW_PEG_TIP_XOFF = PEG_LENGTH * np.sin(NW_PEG_ANGLE) # y offset of peg2 tip relative to its own base
NW_PEG_TIP_YOFF = - PEG_LENGTH * np.cos(NW_PEG_ANGLE) # x offset of peg2 tip relative to its own base
NW_PEG_XY = ((AIRSHIP_NW_WALL[0][0]+AIRSHIP_NW_WALL[0][1])/2.0, (AIRSHIP_NW_WALL[1][0]+AIRSHIP_NW_WALL[1][1])/2.0) # (x,y) of nw peg base
NW_PEG = ([NW_PEG_XY[0],NW_PEG_XY[0]+NW_PEG_TIP_XOFF], [NW_PEG_XY[1],NW_PEG_XY[1]+NW_PEG_TIP_YOFF]) # (X,Y) line for peg2
# NE Goal
NE_GOAL_XOFF = (PEG_LENGTH/2 + ROBOT_LENGTH/2) * np.sin(NE_PEG_ANGLE) # y offset of nw goal relative to its peg's base
NE_GOAL_YOFF = - (PEG_LENGTH/2 + ROBOT_LENGTH/2) * np.cos(NE_PEG_ANGLE) # x offset of nw goal relative to its peg's base
NE_GOAL_X = NE_PEG_XY[0]+NE_GOAL_XOFF
NE_GOAL_Y = NE_PEG_XY[1]+NE_GOAL_YOFF
# Southwest airship wall.
AIRSHIP_SW_WALL = ([-40.035, -19], [-35.295, -70.6])
# Southwest airship wall.
AIRSHIP_SE_WALL = ([40.035, 19], [-35.295, -70.6])
# Bounding box of configuration space, inches.
LOWER_XLIMIT = -5
UPPER_XLIMIT = int(CENTER_AIRSHIP_TO_WEST_WALL)
N_LOWER_YLIMIT = -36
NE_LOWER_YLIMIT = -72
UPPER_YLIMIT = int(NWALL_TO_AIRSHIP) # in dev, was: 12 * 6.5
N_BOUNDS = {"xmin": LOWER_XLIMIT, "xmax": UPPER_XLIMIT,
"ymin": N_LOWER_YLIMIT, "ymax": UPPER_YLIMIT}
NE_BOUNDS = {"xmin": LOWER_XLIMIT, "xmax": UPPER_XLIMIT,
"ymin": NE_LOWER_YLIMIT, "ymax": UPPER_YLIMIT}
LOAD_STATION_WIDTH = 75.5
NE_CORNER_SIZE = np.sqrt(LOAD_STATION_WIDTH * LOAD_STATION_WIDTH / 2) # using for both sides tho boiler is smaller
NE_CORNER = ([UPPER_XLIMIT - NE_CORNER_SIZE, UPPER_XLIMIT], [UPPER_YLIMIT, UPPER_YLIMIT - NE_CORNER_SIZE])
DISCRETIZATIONS = 180
DISCRETIZATION_FACTOR = 360 / DISCRETIZATIONS
RADII = [35, 40, 5000, 45, 50, 55, 60, 65, 70] # used only by plot bot, not for planning.
COLORS = ['y', 'b', 'c', 'm', 'k', 'w', 'g', 'r']
COLORS = COLORS + COLORS
#
# The planning goal is to place the CENTER of the vehicle at a location and heading where
# placement of gear onto peg is fairly easy. We want:
# - the nose of the vehicle a bit less than mid-way along the peg,
# - with (x,y) location within an inch left or right of center,
# - with heading within 10 degrees of center.
#
# Goal states and corresponding filepaths
# We want the CENTER of the vehicle where it is able to place the gear just past mid peg
N_XY_GOALS = [(0, int(PEG_LENGTH/2 + LL/2 - 2),), (1, int(PEG_LENGTH/2 + LL/2 - 2),), (-1, int(PEG_LENGTH/2 + LL/2 - 2),), ]
NE_XY_GOALS = [(int(NE_GOAL_X), int(NE_GOAL_Y),),
(int(NE_GOAL_X + 1.5*np.cos(NE_PEG_ANGLE)), int(NE_GOAL_Y + 1.1*np.sin(NE_PEG_ANGLE)),),
(int(NE_GOAL_X - 1.5*np.cos(NE_PEG_ANGLE)), int(NE_GOAL_Y - 2.*np.sin(NE_PEG_ANGLE)))]
N_STAGE1_FILEPATH = 'n_stage1.pickle'
N_STAGE2_FILEPATH = 'n_stage2.pickle'
N_STAGE3_FILEPATH = 'n_stage3.pickle'
N_STAGE4_FILEPATH = 'n_stage4.pickle'
N_UNREACHABLES_FILEPATH = 'n_unreachables.pickle'
NE_STAGE1_FILEPATH = 'ne_stage1.pickle'
NE_STAGE2_FILEPATH = 'ne_stage2.pickle'
NE_STAGE3_FILEPATH = 'ne_stage3.pickle'
NE_STAGE4_FILEPATH = 'ne_stage4.pickle'
NE_UNREACHABLES_FILEPATH = 'ne_unreachables.pickle'
N_FILEPATHS = ['Reserved', N_STAGE1_FILEPATH, N_STAGE2_FILEPATH, N_STAGE3_FILEPATH, N_STAGE4_FILEPATH]
NE_FILEPATHS = ['Reserved',NE_STAGE1_FILEPATH,NE_STAGE2_FILEPATH,NE_STAGE3_FILEPATH,NE_STAGE4_FILEPATH]
def make_arc(r, fc='y', a=0.9):
"""
Create a circle for drawing.
:param r: radius
:param fc: face color
:param a: alpha(translucency, lower is more transparent)
"""
return pylab.Circle((r, 0), radius=r, alpha=a, fc=fc)
def arctan_trick(t):
"""
Ensures that radian lies between -pi and pi
"""
return np.arctan2(np.sin(t), np.cos(t))
assert(arctan_trick(3 * np.pi / 2) <= -np.pi / 2 and arctan_trick(3 * np.pi / 2) > -1.5707963268)
def prec(ff,pp=4):
"""
Multiplies a number or tuple by a power of ten and truncates.
:param ff: the number or tuple
:param pp: precision
:return: integer
"""
if type(ff) is tuple:
ll=[]
for x in ff:
ll.append(prec(x))
return tuple(ll)
else:
return int(ff*pow(10,pp))
def radians_to360(angle):
"""
Converts angle in radians to be non-negative degrees, allowing degrees > 360 if angle > 2pi
"""
angle = np.degrees(angle)
return angle + 360 if angle < 0 else angle
assert(radians_to360(2.1 * np.pi) == 378)
def heading_tangent_circle(x, y, x_center, y_center, clockwise=True):
"""
Gets angle of a ray tangent to circle centered at (x_center, y_center) and pointed clockwise.
Angle is measured counterclockwise from south.
:param x: x coordinate of tangent point on the circle
:param y: y coordinate of tangent point on the circle
:param x_center: center of the circle
:param y_center:
:param clockwise: If False, rotate result by pi radians (180 degrees).
:return: float radians.
"""
if clockwise:
return -arctan_trick(np.arctan2(x_center-x, y_center-y)+np.pi/2)
else:
return -arctan_trick(np.arctan2(x_center-x, y_center-y)+(3.0*np.pi/2))
assert(radians_to360(heading_tangent_circle(-1, 0, 0, 0)) == 180)
assert(prec(heading_tangent_circle(-1, 1, 0, 0)) == prec(3.0*np.pi/4.0))
assert(prec(heading_tangent_circle(0, 1, 0, 0)) == prec(np.pi/2))
assert(prec(heading_tangent_circle(1, 1, 0, 0)) == prec(np.pi/4))
assert(prec(heading_tangent_circle(1, 0, 0, 0)) == prec(0))
assert(prec(heading_tangent_circle(1, -1, 0, 0)) == prec(-np.pi/4))
assert(prec(heading_tangent_circle(0, -1, 0, 0)) == prec(-np.pi/2))
assert(prec(heading_tangent_circle(-1, -1, 0, 0)) == prec(-3.0*np.pi/4))
assert(prec(heading_tangent_circle(-1, 0, 0, 0,clockwise=False)) == prec(0))
assert(prec(heading_tangent_circle(-1, 1, 0, 0,clockwise=False)) == prec(-1.0*np.pi/4.0))
assert(prec(heading_tangent_circle(0, 1, 0, 0,clockwise=False)) == prec(-np.pi/2))
assert(prec(heading_tangent_circle(1, 1, 0, 0,clockwise=False)) == prec(-3.0*np.pi/4))
assert(radians_to360(heading_tangent_circle(1, 0, 0, 0,clockwise=False))) == 180
assert(prec(heading_tangent_circle(1, -1, 0, 0,clockwise=False)) == prec(3.*np.pi/4))
assert(prec(heading_tangent_circle(0, -1, 0, 0,clockwise=False)) == prec(np.pi/2))
assert(prec(heading_tangent_circle(-1, -1, 0, 0,clockwise=False)) == prec(1.0*np.pi/4))
def get_arrow(theta, scalar=None):
"""
Returns (x,y) point.
Points in direction of theta (south is zero, counterclockwise increasing)
- theta=0 => points down
- theta=pi/2 points right, theta=-(pi/2) points left
- theta=pi points up.
"""
scalar = 1.0 if not scalar else scalar
return np.sin(theta) * scalar, -np.cos(theta) * scalar
assert(get_arrow(0) == (0.0, -1.0)) # down
assert(tuple(np.trunc(get_arrow(np.pi))) == (0, 1)) # up
assert(tuple(np.trunc(get_arrow(np.pi / 2))) == (1, 0)) # right
assert(tuple(np.trunc(get_arrow(-np.pi / 2))) == (-1, 0)) # left
def p_for_c_r(c, t, r, turntype="lh"):
"""
Inverts center_for_tangent.
(x,y) of a point at angle t (radians) on circle having center c and radius r
WARNING: UNTESTED.
If you're going to use it, please add asserts to prove it works correctly.
Otherwise, use at your own risk.
:param c:
:param t:
:param r:
:param turntype:
:return:
"""
if turntype not in ['rh', 'lh']:
logging.error("unsupported turn type: %s", turntype)
return None
cx,cy = c[0],c[1]
if turntype == 'lh':
px = cx - (r * np.cos(t))
py = cy - (r * np.sin(t))
elif turntype == 'rh':
px = cx + (r * np.cos(t))
py = cy + (r * np.sin(t))
return (px,py)
def center_for_tangent(p, t_rad, r, turntype="lh"):
"""
Gives the center of the circle tangent to a point on a ray having heading t (radians).
turntype: lh=counterclockwise, rh=clockwise
If turntype=rh, gives center to driver's right.
If turntype=lh, gives center to driver's left.
Arguments:
p : tangent point (x,y) inches
t : heading in RADIANS counterclockwise from South
r : radius inches
turntype : lh = clockwise, rh = counterclockwise
"""
if turntype not in ['rh', 'lh']:
logging.error("unsupported turn type: %s", turntype)
return None
if turntype == 'lh':
cx = (r * np.cos(t_rad)) + p[0]
cy = (r * np.sin(t_rad)) + p[1]
elif turntype == 'rh':
cx = -(r * np.cos(t_rad)) + p[0]
cy = -(r * np.sin(t_rad)) + p[1]
return (cx, cy)
# Following all use p=(0,0)
assert(center_for_tangent(p=(0, 0), t_rad=0, r=10, turntype="rh") == (-10, 0)) # Heading=South
assert(center_for_tangent(p=(0, 0), t_rad=0, r=10, turntype="lh") == (10, 0)) # Heading=S
assert(prec(center_for_tangent(p=(0, 0), t_rad=np.pi, r=10, turntype="rh")) == prec((10.0, 0.0))) # Heading=N
assert(prec(center_for_tangent(p=(0, 0), t_rad=np.pi, r=10, turntype="lh")) == prec((-10, 0))) # Heading N
# Ray points west
assert(prec(center_for_tangent(p=(0,0), t_rad=np.pi / 2, r=10, turntype="rh")) == prec((0, -10)))
assert(prec(center_for_tangent(p=(0,0), t_rad=np.pi / 2, r=10, turntype="lh")) == prec((0, 10)))
# Ray points east
assert(prec(center_for_tangent(p=(0,0), t_rad=-np.pi / 2, r=10, turntype="rh")) == prec((0, 10)))
assert(prec(center_for_tangent(p=(0,0), t_rad=-np.pi / 2, r=10, turntype="lh")) == prec((0, -10,)))
## Following all use p=(10,10)
assert(center_for_tangent(p=(10, 10), t_rad=0, r=10, turntype="rh") == (0, 10)) # Heading=S, rh turn, circle is to east
assert(center_for_tangent(p=(10, 10), t_rad=0, r=10, turntype="lh") == (20, 10)) # Heading=S, lh turn, circle is to west
# 45 degrees pointing to bottom left
assert(prec(center_for_tangent(p=(10, 10), t_rad=-np.pi / 4, r=10, turntype="rh")[0], 8) == prec(2.9289321881345245, 8))
assert(prec(center_for_tangent(p=(10, 10), t_rad=-np.pi / 4, r=10, turntype="rh")[1], 8) == prec(17.071067811865476, 8))
# 45 degrees pointing to bottom left
assert(prec(center_for_tangent(p=(10, 10), t_rad=-np.pi / 4, r=10, turntype="rh")[0], 8) == prec(2.9289321881345245, 8))
assert(prec(center_for_tangent(p=(10, 10), t_rad=-np.pi / 4, r=10, turntype="rh")[1], 8) == prec(17.071067811865476, 8))
assert(prec(center_for_tangent(p=(10, 10), t_rad=-np.pi / 4, r=10, turntype="lh")[0], 8) == prec(17.071067811865476, 8))
assert(prec(center_for_tangent(p=(10, 10), t_rad=-np.pi / 4, r=10, turntype="lh")[1], 8) == prec(2.9289321881345245, 8))
# # 45 degrees pointing to top right
assert(prec(center_for_tangent(p=(10, 10), t_rad=3 * np.pi / 4, r=10, turntype="rh")[0], 8) == prec(17.071067811865476, 8))
assert(prec(center_for_tangent(p=(10, 10), t_rad=3 * np.pi / 4, r=10, turntype="rh")[1], 8) == prec(2.9289321881345245, 8))
assert(prec(center_for_tangent(p=(10, 10), t_rad=3 * np.pi / 4, r=10, turntype="lh")[0], 8) == prec(2.9289321881345245, 8))
assert(prec(center_for_tangent(p=(10, 10), t_rad=3 * np.pi / 4, r=10, turntype="lh")[1], 8) == prec(17.071067811865476, 8))
def get_heading(p1, p2=(0, 0)):
"""
Gets heading of ray originating at p1 and pointed at p2.
Measured from south increasing counterclockwise.
- trivial case when p1 equals p2 returns zero.
- p1 above p2 returns zero
- p1 below p2 returns pi
- p1 east of p2 returns pi/2
- p1 west of p2 returns -pi/2
"""
if p1==p2:
return 0
return arctan_trick(np.arctan2(p1[1] - p2[1],p1[0] - p2[0])-np.pi/2)
assert(get_heading((0, 0,)) == 0) # trivial case p1 == origin => no angle
assert(get_heading((0, 10,)) == 0) # p1->p2 points south
assert(get_heading((10, 0,)) == -np.pi/2) # p1->p2 points west
assert(get_heading((0, -10,)) == -np.pi) # p1->p2 points north
assert(get_heading((-10, 0,)) == np.pi/2) # p1->p2 points east
assert(get_heading((0, 1,),(0,0)) == 0) # p1->p2 points south
assert(get_heading((1, 0,),(0,0)) == -np.pi/2) # p1->p2 points west
assert(get_heading((0, -1,),(0,0)) == -np.pi) # p1->p2 points north
assert(get_heading((-1, 0,),(0,0)) == np.pi/2) # p1->p2 points east
assert(prec(get_heading((1, 1,),(0,0)),8) == prec(-np.pi/4,8)) # p1->p2 points southwest
assert(get_heading((1, -1,),(0,0)) == -3*np.pi/4) # p1->p2 points northwest
assert(prec(get_heading((-1, -1,),(0,0)),8) == prec(3.0/4.0*np.pi,8)) # p1->p2 points northeast
assert(prec(get_heading((-1, 1,),(0,0)),8) == prec(np.pi/4,8)) # p1->p2 points southeast
MAXD=1000 # maximum arc length on this playing field
def arc_dist(p1, p2, r, h, turntype='lh', tol=0.5, debug=False, maxd=MAXD):
"""
p1,p2 : (x,y)
r : radius
h : heading, radians, counterclockwise from south.
turntype : lh=counterclockwise, rh=clockwise
Given p1, p2, radius r, and heading, calculate length of arc circle path from p1 to p2
Two cases:
(a) p2 is on the same circle. Distance is the length of the arc in the direction of travel
(b) p2 is not on the circle: distance is d_a plus distance from point to the circle.
Some edge cases give a negative distance, such as when the offset is negative and exceeds the arc distance.
This can also blow up in degenerate cases at long radius and short distance, which are guarded by <maxd>.
Returns: 2-tuple
- arc distance
- radius offset: negative if inside circle, positive if outside
Returns (None, None) in following cases:
- unrecognized turn type.
- xy distance between the two points exceeds 2 * r + tol
"""
if turntype not in ['lh','rh']:
logging.error("ERROR: unsupported type: %s", turntype)
return None, None
# sanity check.=
if xy_dist(p1, p2) > 2 * r + tol:
logging.info("ERROR: d_12 (%.1f) > 2r (%0.1f) ", xy_dist(p1, p2), r)
return None, None
h = arctan_trick(h)
h_deg = np.degrees(h)
if debug:
logging.info("p1: (%.3f,%.3f)", p1[0],p1[1])
logging.info("p2: (%.3f,%.3f)", p2[0],p2[1])
logging.info("turn: %s", turntype)
logging.info("h: %.2f (pi) %d (degrees)", h / np.pi, h_deg)
logging.info("r: %d", r)
logging.info("circum: %.1f", 2 * np.pi * r)
# Get center of turning radius r for ray originating from p1 at radians heading h and turning direction
c = center_for_tangent(p1, h, r, turntype=turntype)
t_c_p1 = radians_to360(get_heading(c, p1))%360 # heading of ray pointing from c to p1
t_c_p2 = radians_to360(get_heading(c, p2))%360 # heading of ray pointing from c to p2
if debug:
logging.info("c: %s", c)
logging.info("t(c->p1): %d (degrees)", t_c_p1)
logging.info("t(c->p2): %d (degrees)", t_c_p2)
if turntype == 'rh': # clockwise
if t_c_p1 > t_c_p2:
t_12 = t_c_p1 - t_c_p2
else:
t_12 = (t_c_p2 - t_c_p1)%360
if debug:
logging.info("t(p2->p1): %d (degrees)", t_12)
elif turntype == 'lh': # counterclockwise
if t_c_p1>t_c_p2:
t_12 = 360-(t_c_p1-t_c_p2)
else:
t_12 = abs(t_c_p2+360-t_c_p1)%360
if debug:
logging.info("t(p1->p2): %d (degrees)", t_12)
dist = 2*np.pi*r*t_12/360.0
r2 = xy_dist(c, p2)
if dist > maxd:
return None, None
return dist, r2 - r
# Heading=300, lh
assert(prec(arc_dist((-1.5,2.65625),(2.64,-1.54),3.03225,np.radians(300),turntype='lh'),8) == prec((11.066241813119897, 0.025590366864600256),8))
# # Heading=120, rh
assert(prec(arc_dist((-1.5,2.65625),(2.64,-1.54),3.03225,np.radians(120),turntype='rh'),8) == prec((7.9859468345753992, 0.0255903668646007),8))
# # # inside circle
assert(prec(arc_dist((-1.5,2.65625),(2.6,-1.54),3.03225,np.radians(120),turntype='rh'),8) == prec((8.0065464136041165, -0.0086631053950783077)))
# # # outside circle"
assert(prec(arc_dist((-1.5,2.65625),(2.7,-1.54),3.03225,np.radians(120),turntype='rh'),8) == prec((7.9559007176690093, 0.077227883701158184)))
# # # sums 2-tuple values
assert(prec(sum(arc_dist((2.64, -1.54),(-1.5, 2.65625), 3.03225,h=np.radians(150),turntype='lh')),8)==prec(7.9558773531677716,8))
# ##
# ## Swaps points used above
# ##
# -30, rh
assert(prec(arc_dist((2.64, -1.54),(-1.5, 2.65625),3.03225,np.radians(-30),turntype='rh'),8) == prec((7.9099380690395202, 0.045939284128249991),8))
# #
# # 150, lh
assert(prec(arc_dist((2.64, -1.54),(-1.5, 2.65625),3.03225,np.radians(150),turntype='lh'),8) == prec((7.9099380690395211, 0.045939284128249991),8))
# #
# # inside circle : radius offset should be negative
assert(prec(arc_dist((2.64, -1.54),(-1.5, 2.6),3.03225,np.radians(150),turntype='lh'),8) == prec((7.9376313590248184, -0.0029102585840607986),8))
# center @ (0,0), r=1
assert(prec(arc_dist(p1=(-1,0,),p2=(1,0), r=1, h=0, turntype='lh', tol=0.1),8) == prec((np.pi,0,),8))
assert(prec(arc_dist(p1=(-1,0,),p2=(1,0), r=1, h=np.pi, turntype='rh', tol=0.1),8) == prec((np.pi,0,),8))
assert(prec(arc_dist(p1=(1,0,),p2=(-1,0), r=1, h=0, turntype='rh', tol=0.1),8) == prec((np.pi,0,),8))
assert(prec(arc_dist(p1=(1,0,),p2=(-1,0), r=1, h=np.pi, turntype='lh', tol=0.1),8) == prec((np.pi,0,),8))
assert(prec(arc_dist(p1=(-1,0,),p2=(0,-1), r=1, h=0, turntype='lh', tol=0.1),8) == prec((np.pi/2,0,),8))
assert(prec(arc_dist(p1=(-1,0,),p2=(0,1), r=1, h=np.pi, turntype='rh', tol=0.1),8) == prec((np.pi/2,0,),8))
assert(prec(arc_dist(p1=(1,0,),p2=(0,-1), r=1, h=0, turntype='rh', tol=0.1),8) == prec((np.pi/2,0,),8))
assert(prec(arc_dist(p1=(1,0,),p2=(0,1), r=1, h=np.pi, turntype='lh', tol=0.1),8) == prec((np.pi/2,0,),8))
CW_ARC_TOL = 2.01 # 1"x1" grid discretization can contribute up to 2" of discrepancy to the arc length calculation.
def cw_arclength(p1, p2, r, tol=CW_ARC_TOL, debug=False):
"""
Returns the circular arc length in inches from p1 to p2 along arc of curvature r,
as float inches to one decimal precision.
Assumes that points are within 180 degrees of each other.
Takes the shortest path, which is why it doesn't require a heading or turn direction.
Arguments:
- p1 : (x,y)
- p2 : (x2,y2)
- tol : allows points that are within d=2r+tol to still return
Returns: None if error or out of tolerance, otherwise a float.
"""
if p1 == p2:
return 0
err = 0.0
d = xy_dist(p1, p2) # cartesian beeline distance
if d > 2 * r + tol:
if debug or d > 2*r + (2 * tol) :
logging.error("xy dist (p1,p2) = %.2f, which is > 2*r + tol for radius %.2f, ", d, r)
return None
err = 2*r - d # hack to handle measurement error due to discretization
d = 2*r if d > 2*r else d
try:
xx = (2 * (r * r) - d * d) / (2.0 * r * r)
xx = 1.0 if xx > 0.999999 else -1 if xx < -0.999999 else xx # avoid math domain error due to rounding
rads = acos(xx) # <==TODO dcheck rm when done testing
except ValueError as e:
# if debug:
logging.error("%s in cw_arclength: p1:(%d,%d), p2:(%d,%d), r:%.1f ",
e, p1[0], p1[1], p2[0], p2[1], r)
return None
arclength = r * rads + err
assert(arclength <= np.pi * r + tol) # maximum length is circumference/2
return arclength
assert(cw_arclength((0, 0), (2, 0), 1) == np.pi)
assert(cw_arclength((0, 2), (0, 0), 1) == np.pi)
assert(cw_arclength((2, 2), (4, 2), 1) == np.pi)
assert(round(100.0 * cw_arclength((0, 0), (2, 0), 1)) / 100.0 == 3.14)
assert(round(100.0 * cw_arclength((0.0001, 0.0001), (1.999, 0.0001), 1)) / 100.0 == 3.08)
assert (prec(cw_arclength((2.64, -1.54), (-1.5, 2.65625), 3.03225, tol=0.4),8)==prec(8.08785331109,8))
def discretize_angle_deg(t_deg, factor=DISCRETIZATION_FACTOR):
"""
Bounds an angle in degrees to lie in [0,359]
:param t_deg: angle in degrees
:return: integer between 0 and 359
"""
t_deg = int(round(t_deg / factor) * factor)
return (t_deg + 360 if t_deg < 0 else t_deg) % 360
def discretize_angle(t_rad, bound=True, factor=DISCRETIZATION_FACTOR):
"""
Converts angle in radians to degree, ensures non-negative and discretizes.
Arguments:
- bound : ensures within (0,359)
- factor : degree stepsize, = 360/discretizations, ideally a factor of 360
"""
t_deg = int(round(np.degrees(t_rad) / factor) * factor)
t_deg = t_deg + 360 if t_deg < 0 else t_deg
return t_deg % 360 if bound else t_deg
assert(discretize_angle(0) == 0)
assert(discretize_angle(np.pi, factor=6) == 180)
assert(discretize_angle(2 * np.pi, False, factor=6) == 360)
assert(discretize_angle(3 * np.pi, False, factor=6) == 540)
assert(discretize_angle(4 * np.pi, False, factor=6) == 720)
assert(discretize_angle(2 * np.pi, factor=6) == 0)
assert(discretize_angle(3 * np.pi, factor=6) == 180)
assert(discretize_angle(4 * np.pi, factor=6) == 0)
assert(discretize_angle(-2 * np.pi, factor=6) == 0)
assert(discretize_angle(-2 * np.pi, factor=6) == 0)
assert(discretize_angle(0.49999 * np.pi, factor=6) == 90)
assert(discretize_angle(0.50001 * np.pi, factor=6) == 90)
assert(discretize_angle(np.pi / 4, factor=6) == 48)
assert(discretize_angle(0.52 * np.pi, factor=6) == 96)
assert(discretize_angle(0.52 * np.pi, factor=3) == 93)
# Goal headings
DISCRETIZED_GOAL_HEADINGS = sorted(set([discretize_angle(angle) for angle in np.arange(0, 2 * np.pi, 0.01)]))
DGH = DISCRETIZED_GOAL_HEADINGS
N_GOAL_HEADINGS = [0, DGH[-3], DGH[3], DGH[-5], DGH[5], DGH[-7], DGH[7]]
NE_GOAL_HEADINGS = []
for hh in N_GOAL_HEADINGS:
NE_GOAL_HEADINGS.append(discretize_angle_deg(hh-60))
def get_xy_explored(config2):
"""
Set of xy locations that have a path for some heading.
"""
return set([(x, y) for x, y, z in config2.iterkeys()])
def get_xy_unexplored(whichpeg, config2, unreachables):
"""
Set of xy locations that don't have a path for any heading.
"""
all_xy = xy_grid_set(whichpeg) - unreachables
explored = get_xy_explored(config2)
return set([k for k in all_xy if k not in explored])
def unreachables_path_for_peg(whichpeg):
if whichpeg == NORTH:
return N_UNREACHABLES_FILEPATH
elif whichpeg == NORTHEAST:
return NE_UNREACHABLES_FILEPATH
def get_xy_unexplored_file(whichpeg, planpath):
explored = load_config(planpath)
unreachables = load_unreachables(whichpeg)
xlim,ylim = lower_xylim_for_peg(whichpeg)
return get_xy_unexplored(whichpeg, explored, unreachables, xlim, ylim)
def get_xyt_explored(config2):
"""
Set of states that have a path.
"""
return set(config2.iterkeys())
def get_xyt_unexplored(whichpeg, states, unreachables):
"""
Set of reachable xyt states that don't have a path.
"""
all_xyt = xyt_grid_set(whichpeg)
explored = get_xyt_explored(states)
unexplored = set([k for k in all_xyt if k not in explored])
if unreachables:
return unexplored - unreachables
else:
logging.warn("Unreachables set not provided, result may contain unreachable states.")
return unexplored
def get_xyt_unexplored_file(whichpeg, stage=None):
explored = load_config(configpath_for_stage(whichpeg, stage))
explored = set(explored.iterkeys())
unreachables = load_unreachables(whichpeg)
return explored - unreachables
def get_remaining(whichpeg, plan_states, unreachables, max=20000):
reachable_grid = get_reachable_grid(whichpeg, unreachables)
num_reachable = len(reachable_grid)
logging.info("Total number of reachable states: %8d", num_reachable)
remaining = reachable_grid - get_xyt_explored(plan_states)
num_missing = len(remaining)
logging.info("Number reachable states lacking path: %8d", num_missing)
logging.info("Percent reachable states having path: %6.2f", (100.0 - (100.0 * num_missing / num_reachable)))
if len(remaining) > max:
logging.info("Saving only %d/%d", max, len(remaining))
remaining = set(random.sample(remaining,max))
return remaining
N_REMAINING = "n_remaining.pickle"
NE_REMAINING = "ne_remaining.pickle"
def save_remaining(whichpeg):
config = config_for_stage(whichpeg, 4)
unreachables = load_unreachables(whichpeg)
remaining = get_remaining(whichpeg, config, unreachables)
path = N_REMAINING if whichpeg==NORTH else NE_REMAINING if whichpeg==NORTHEAST else None
save_pickle(remaining,path)
def get_xyt_fillable(whichpeg, explored, unreachables):
"""
Returns list of keys for reachable xyt states that do not yet have an entry for this heading,
but do have an entry for some other heading.
"""
# Gets the xyt states that do not have a state.
logging.info("Calculating unexplored")
xyt_fillable = get_xyt_unexplored(whichpeg, explored, unreachables)
logging.info("Number of xyt unexplored: %d", len(xyt_fillable))
# Gets the xy cells that do have an entry for some configuration
xy_filled = get_xy_explored(explored)
# Get xyt states that have a corresponding xy in the explored states.
return [k for k in xyt_fillable if (k[0],k[1]) in xy_filled]
def get_xyt_fillable_file(whichpeg, planpath, unreachables_path, logonly=False):
"""
Returns list of keys for reachable xyt states that do not yet have an entry for this heading,
but do have an entry for some other heading. Note: use stage2 config.
"""
explored = load_config(planpath)
unreachables = load_unreachables(unreachables_path)
# Get xyt states that have a corresponding xy in the explored states.
xyt_fillable = get_xyt_fillable(whichpeg, explored, unreachables)
logging.info("Number of xyt fillable: %d", len(xyt_fillable))
if not logonly:
return xyt_fillable
def get_reachable_grid(whichpeg, unreachables):
return xyt_grid_set(whichpeg) - unreachables
def bin_unexplored_xy(whichpeg, config2, unreachables):
"""
For each xy location gives count of how many states don't have a path.
"""
binned = defaultdict(int)
unexplored = get_xyt_unexplored(whichpeg, config2, unreachables)
for k in unexplored:
binned[(k[0], k[1],)] += 1
return binned
def deltap_onheading(state,d,discretization_factor=DISCRETIZATION_FACTOR):
"""
Calculates a new state from the current one
driving either directly forwards, if d>0,
or in reverse, if d<0.
New state location and heading is discretized.
:param state:
:param d: distance
:return: state
"""
x,y,h = state[0],state[1],state[2]
dx = d * np.sin(np.radians(h))
dy = -d * np.cos(np.radians(h))
return (int(x + dx), int(y + dy), discretize_angle_deg(state[2], discretization_factor))
assert(deltap_onheading((0,0,0),10, 1)==(0, -10, 0))
assert(deltap_onheading((0,0,90),10, 1)==(10, 0, 90))
assert(deltap_onheading((0,0,45),10, 1)==(7, -7, 45))
assert(deltap_onheading((0,0,135),10, 1)==(7, 7, 135))
assert(deltap_onheading((0,0,180),10, 1)==(0, 10, 180))
assert(deltap_onheading((0,0,225),10, 1)==(-7, 7, 225))
assert(deltap_onheading((0,0,270),10, 1)==(-10, 0, 270))
assert(deltap_onheading((0,0,315),10, 1)==(-7, -7, 315))
assert(deltap_onheading((0,0,360),10, 1)==(0, -10, 0))
assert(deltap_onheading((10,10,0),10, 1)==(10, 0, 0))
assert(deltap_onheading((20,20,-45),28.284, 1)==(0, 0, 315))
assert(deltap_onheading((0,0,0),-10, 1)==(0, 10, 0))
assert(deltap_onheading((0,0,90),-10, 1)==(-10, 0, 90))
assert(deltap_onheading((0,0,45),-10, 1)==(-7, 7, 45))
assert(deltap_onheading((0,0,135),-10, 1)==(-7, -7, 135))
assert(deltap_onheading((0,0,180),-10, 1)==(0, -10, 180))
assert(deltap_onheading((0,0,225),-10, 1)==(7, -7, 225))
assert(deltap_onheading((0,0,270),-10, 1)==(10, 0, 270))
assert(deltap_onheading((0,0,315),-10, 1)==(7, 7, 315))
assert(deltap_onheading((0,0,360),-10, 1)==(0, 10, 0))
assert(deltap_onheading((10,10,0),-10, 1)==(10, 20, 0))
assert(deltap_onheading((20,20,135),-28.284, 1)==(0, 0, 135))
def line_grid_ahead(whichpeg, state, obstacles=None, verbose=False):
"""
Returns a grid of (x,y) points directly ahead of robot,
up until first obstacle or out of bounds.
:param p: (x,y)
:param h: heading, degrees
:param obstacles: use xy_obstacles_set()
:param verbose:
:return:
"""
if whichpeg==NORTH:
bounds = N_BOUNDS
elif whichpeg==NORTHEAST:
bounds = NE_BOUNDS
else:
ValueError("Unsupported choice of peg.")
grid=[]
max=1000
x,y,h = state[0],state[1],state[2]
for d in range(1,max):
dx = d * np.sin(np.radians(h))
dy = -d * np.cos(np.radians(h))
pp3 = (int(x+dx),int(y+dy), h)
if is_out_of_bounds((pp3[0],pp3[1],), bounds):
break
if obstacles and collides(pp3, obstacles):
break
if pp3 not in grid:
grid.append(pp3)
if verbose:
logging.info("%s", pp3)
if len(grid) == max:
logging.error("Exhausted grid limit.")
return grid
def line_grid_behind(whichpeg, state, obstacles=None, verbose=False):
"""
Returns a grid of (x,y) points directly behind robot,
up until first obstacle or out of bounds.
:param p: (x,y)
:param h: heading, degrees
:param obstacles: use xy_obstacles_set()
:param verbose:
:return:
"""
if whichpeg==NORTH:
bounds = N_BOUNDS
elif whichpeg==NORTHEAST:
bounds = NE_BOUNDS
else:
ValueError("Unsupported choice of peg.")
grid=[]
max=1000
x,y,h = state[0],state[1],state[2]
for d in range(1,max):
dx = -d * np.sin(np.radians(h))
dy = d * np.cos(np.radians(h))
pp3 = (int(x+dx),int(y+dy),h,)
if is_out_of_bounds((pp3[0],pp3[1],), bounds):
break
if obstacles and collides(pp3, obstacles):
break
if pp3 not in grid:
grid.append(pp3)
if verbose:
logging.info("%s", pp3)
if len(grid) == max:
logging.error("Exhausted grid limit.")
return grid
#====================================================================
# robot and obstacles
#====================================================================
def get_robot_rear(nose, theta):
return (nose[0] - (LL * np.sin(theta)), nose[1] + (LL * np.cos(theta))) # rear midpoint
def get_robot_center(nose, theta):
return (nose[0] - (LL/2 * np.sin(theta)), nose[1] + (LL/2 * np.cos(theta)))
def get_robot_nose_from_center(center, theta):
return (center[0]+(LL/2 * np.sin(theta)), center[1]-(LL/2 * np.cos(theta)),)
def int_tup(tup):
return map(int, tup)
def get_robot_corners(nose, theta, rear, grid=False):
xy_fcdl = [nose[0] + (WW / 2 * np.cos(theta)), nose[1] +
(WW / 2 * np.sin(theta))] # front driver left
xy_fcdr = [nose[0] - (WW / 2 * np.cos(theta)), nose[1] -
(WW / 2 * np.sin(theta))] # front driver right
xy_rcdl = [rear[0] + (WW / 2 * np.cos(theta)), rear[1] +
(WW / 2 * np.sin(theta))] # rear driver left
xy_rcdr = [rear[0] - (WW / 2 * np.cos(theta)), rear[1] -
(WW / 2 * np.sin(theta))] # rear driver right
if grid:
return int_tup(xy_fcdl), int_tup(xy_fcdr), int_tup(xy_rcdl), int_tup(xy_rcdr)
else:
return xy_fcdl, xy_fcdr, xy_rcdl, xy_rcdr
def get_robot_chassis(xy_fcdl, xy_fcdr, xy_rcdl, xy_rcdr):
"""
:param xy_fcdl: front corner driver left
:param xy_fcdr: front corner driver right
:param xy_rcdl: rear corner driver left
:param xy_rcdr: rear corner driver right
:return:
"""
line_front = ([xy_fcdr[0], xy_fcdl[0]], [xy_fcdr[1], xy_fcdl[1]])
line_rear = ([xy_rcdr[0], xy_rcdl[0]], [xy_rcdr[1], xy_rcdl[1]])
line_dleft = ([xy_fcdl[0], xy_rcdl[0]], [xy_fcdl[1], xy_rcdl[1]])
line_dright = ([xy_fcdr[0], xy_rcdr[0]], [xy_fcdr[1], xy_rcdr[1]])
return line_front, line_rear, line_dleft, line_dright
def rtpairs(r, n):
"""
Generates points evenly distributed on a circle centered at origin.
r : radius