-
Notifications
You must be signed in to change notification settings - Fork 0
/
ShapeValidTest.py
330 lines (261 loc) · 9.98 KB
/
ShapeValidTest.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
from Shape import Shape
from LineMath import linesIntersect
from LineMath import segmentIntersectsArc
import numpy as np
def rotMat(theta):
""" Returns a rotation matrix that rotates vectors around (0,0) """
s = np.sin(theta)
c = np.cos(theta)
return np.array([[c, -s],
[s, c]])
def isThrough(shape, pos, rot):
"""
Returns True if the shape transposed by pos and rotated
(in the positive direction) by rot is through the corridor
"""
for node in shape.nodes:
# if node is outside shape, continue
if not node.o == shape.o:
continue
if (np.matmul(rotMat(rot), node.pos) + pos)[1] - node.r < -0.5:
return False
# No node (after trasposition and rotation) was below -0.5
return True
def isInBounds(shape, pos, rot):
"Returns True if no part of shape is outside of corridor"
# Facts used: There is no need to check if lines cross the outer border,
# if they did, that necessarily means a node is outside the outer border
for node in shape.nodes:
# Calculate the position of the node
nodePos = np.matmul(rotMat(rot), node.pos) + pos
# If the node is inside the shape
if node.o == shape.o:
# If node is outside outer bounds
if nodePos[1] + node.r > 0.5 or nodePos[0] - node.r < -0.5:
return False
# If the inner corner (0.5,-0.5) is inside node
cornerPos = np.array([0.5, -0.5])
if np.linalg.norm(nodePos-cornerPos) < node.r:
return False
# If the node is below the corner and outside inner line
if nodePos[1] < -0.5 and nodePos[0] + node.r > 0.5:
return False
# If the node is past the corner and below inner line
if nodePos[0] > 0.5 and nodePos[1] - node.r < -0.5:
return False
# If node is outside the shape
# An outside node can never be causing trouble at the outer wall
else:
# If arc of node intersects lower inner line,
# TODO No need to check upper inner line?
innerLine = np.array([[0.5, -0.5], [0.5, -100]])
if segmentIntersectsArc(innerLine, nodePos, node.r,
shape.nodeAngles[node.ID] + rot, node.o):
return False
# No nodes are bad if you got here
# Now test if any of the binding lines cross the inner line
innerLine = np.array([[0.5, -100], [0.5, -0.5]])
for line in shape.lines:
# Shift line
line = np.array(
[np.matmul(rotMat(rot), point) + pos for point in line])
# Check if line crosses the inner line
if linesIntersect(line, innerLine):
return False
# All tests passed, the shape is in bounds
return True
def nodesOutOfBounds(shape, pos, rot):
"""
Return node ids of (inside) nodes out of bounds
Also which quadrant they are out of bounds in
# II : I
# .......:________
# | 0
# III | ._____
# | |
# | | IV
"""
rvList = []
# Because of comparing floats, we need a tolerance for machine
# precision magnitude of errors
tol = 1e-13
for node in shape.nodes:
# If node is not an inside node, continue
if node.o != shape.o:
continue
nodePos = np.matmul(rotMat(rot), node.pos) + pos
if nodePos[1] + node.r > 0.5 + tol:
rvList.append([node.ID, 1])
if nodePos[0] - node.r < -0.5 - tol:
rvList.append([node.ID, 3])
if nodePos[0] - node.r < -0.5 - tol and \
nodePos[1] + node.r > 0.5 + tol:
rvList.append([node.ID, 2])
if nodePos[0] + node.r > 0.5 + tol and nodePos[1] <= -0.5:
rvList.append([node.ID, 4])
elif nodePos[1] - node.r < -0.5 - tol and nodePos[0] >= 0.5:
rvList.append([node.ID, 4])
elif np.linalg.norm(nodePos - np.array([0.5, -0.5])) < node.r + tol:
rvList.append([node.ID, 4])
return rvList
def posRotToShiftTwoNodes(nodePos1, nodePos2, delta1, delta2):
"""
Takes two nodes' (original) positions and where to move them
Returns deltaPos and deltaRot that will acomplish this
"""
# Calculate rotation
vector12 = nodePos2 - nodePos1
origAngle = np.arctan2(vector12[1], vector12[0])
newVector12 = nodePos2 + delta2 - (nodePos1 + delta1)
newAngle = np.arctan2(newVector12[1], newVector12[0])
deltaRot = newAngle - origAngle
# Calculate translation
vectorOldNew1 = np.matmul(rotMat(deltaRot),
nodePos2 + delta2 - (nodePos1 + delta1))
deltaPos = vectorOldNew1
return (deltaPos, deltaRot)
def posRotToRotateAroundNode(shape, currentRot, theta, nodeID):
"""
Takes (original) position around which we wish to rotate
And theta, the (signed) angle we wish to rotate with
Returns the pos and rot that accomplishes this
"""
deltaRot = theta
origNodePos = np.matmul(rotMat(currentRot), shape.nodes[nodeID].pos)
newNodePos = np.matmul(rotMat(theta), origNodePos)
deltaPos = origNodePos - newNodePos
return (deltaPos, deltaRot)
def posRotToShiftRightWithRot(shape, pos, rot):
"""
Returns the deltapos and deltarot required to shift shape
right by stepRight
If it isn't possible, returns None.
"""
stepRight = 0.01
stepRot = -0.01
# Move shape right
newPos = pos + np.array([stepRight, 0])
newRot = rot
# Get the top node
topNodeID = getTopNodeId(shape, newPos, rot)
# While shape is not in bounds, rotate cw until it is
# or something new goes out of bounds
while not isInBounds(shape, newPos, newRot):
deltaPos, deltaRot = posRotToRotateAroundNode(
shape, newRot, stepRot, topNodeID)
# Check all of the out of bounds (inside) nodes
for nodeWithQuadrant in nodesOutOfBounds(shape, newPos + deltaPos,
newRot + deltaRot):
# If anyone went out to quadrant I, rotate around that one
# instead next pass
if nodeWithQuadrant[1] == 1:
topNodeID = nodeWithQuadrant[0]
break
# If anyone is out in quadrant III, the game is lost
# because then we can't rotate so that the shape clears
# the corner and the leftest wall
if nodeWithQuadrant[1] == 3:
return None
else: # If for loop not broken
newPos += deltaPos
newRot += deltaRot
return (newPos - pos, newRot - rot)
def shapeIsValid(shape):
"""
Returns true if shape can be moved through a corridor with
width 1 and a 90 degree turn to the right
The corridor is initially centered on 0
"""
topNode = shape.nodes[getTopNodeId(shape)]
maximumY = topNode.pos[1] + topNode.r
rightNode = shape.nodes[getRightNodeId(shape)]
minimumX = rightNode.pos[0] - topNode.r
# Place shape as far up and as far left as possible
pos = [-0.5 - minimumX, 0.5 - maximumY]
rot = 0
# Check that it is in bounds to begin with
for node in shape.nodes:
if node.o != shape.o:
continue
elif node.pos[0] + pos[0] + node.r > 0.5 or \
node.pos[0] + pos[0] - node.r < -0.5:
return False
while True:
deltaPosRot = posRotToShiftRightWithRot(shape, pos, rot)
if deltaPosRot is None:
return False
else:
pos += deltaPosRot[0]
rot += deltaPosRot[1]
if isThrough(shape, pos, rot):
return True
def getWalk(shape):
"""
Returns a sequence of positions and rotations that will get
the shape through a corrodor with
width 1 and a 90 degree turn to the right
The corridor is centered on 0
"""
topNode = shape.nodes[getTopNodeId(shape)]
maximumY = topNode.pos[1] + topNode.r
rightNode = shape.nodes[getRightNodeId(shape)]
minimumX = rightNode.pos[0] - topNode.r
# Place shape as far left as possible and below the starting line
pos = [-0.5 - minimumX, -0.5 - maximumY]
rot = 0
# Initialize arrays to hold positions and rotations
poss = [pos.copy()]
rots = [rot]
# Check that it is in bounds to begin with
if not isInBounds(shape, pos, rot):
return (poss, rots)
# Move up
for y in np.linspace(-0.5 - maximumY, 0.5 - maximumY, 10):
poss.append([pos[0], y])
rots.append(0)
# Place shape as far up and as far left as possible
pos = [-0.5 - minimumX, 0.5 - maximumY]
while True:
deltaPosRot = posRotToShiftRightWithRot(shape, pos, rot)
# print(deltaPosRot)
if deltaPosRot is None:
return (poss, rots)
else:
pos += deltaPosRot[0]
rot += deltaPosRot[1]
poss.append(pos.copy())
rots.append(rot)
if isThrough(shape, pos, rot):
return (poss, rots)
def getTopNodeId(shape, pos=np.array([0, 0]), rot=0):
"""
Returns the id of the (inside) node that reaches the
highest y-value
"""
maximumY = None
topNodeID = None
for node in shape.nodes:
# If node isn't inside node
if node.o != shape.o:
continue
nodePos = np.matmul(rotMat(rot), node.pos) + pos
if maximumY is None or nodePos[1] + node.r > maximumY:
maximumY = nodePos[1] + node.r
topNodeID = node.ID
return topNodeID
def getRightNodeId(shape, pos=np.array([0, 0]), rot=0):
"""
Returns the id of the (inside) node that reaches the
lowest x-value
"""
minimumX = None
rightNodeID = None
for node in shape.nodes:
# If node isn't inside node
if node.o != shape.o:
continue
nodePos = np.matmul(rotMat(rot), node.pos) + pos
if minimumX is None or nodePos[0] - node.r < minimumX:
minimumX = nodePos[0] - node.r
rightNodeID = node.ID
return rightNodeID