Skip to content

Commit 17c3af2

Browse files
liusiqi43copybara-github
authored andcommitted
Allow FieldBox to be added to the environment with ball rebound.
* Removed roof after switching walls to plane geoms since walls are infinitely tall. * Disable throw-in position detection if FieldBox is enabled with ball rebound. PiperOrigin-RevId: 288483845 Change-Id: I7acc9f4e06327ca601821fc9b914df74abf77611
1 parent 0b7369e commit 17c3af2

2 files changed

Lines changed: 62 additions & 29 deletions

File tree

dm_control/locomotion/soccer/pitch.py

Lines changed: 61 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
_WALL_THICKNESS = .5
3434
_SIDE_WIDTH = 32. / 6.
3535
_GROUND_GEOM_HEIGHT = 0.5
36+
_FIELD_BOX_CONTACT_BIT = 1 << 7 # Use a higher bit to prevent potential clash.
3637

3738
_DEFAULT_PITCH_SIZE = (12, 9)
3839
_DEFAULT_GOAL_LENGTH_RATIO = 0.33 # Goal length / pitch width.
@@ -43,7 +44,7 @@ def _top_down_cam_fovy(size, top_camera_distance):
4344
top_camera_distance)
4445

4546

46-
def _wall_pos_size(size):
47+
def _wall_pos_xyaxes(size):
4748
"""Infers position and size of bounding walls given pitch size.
4849
4950
Walls are placed around `ground_geom` that represents the pitch. Note that
@@ -54,15 +55,15 @@ def _wall_pos_size(size):
5455
size: a tuple of (length, width) of the pitch.
5556
5657
Returns:
57-
a list of 4 tuples, each representing the position and size of a wall. In
58-
order, walls are placed along x-negative, x-positive, y-negative,
58+
a list of 4 tuples, each representing the position and xyaxes of a wall
59+
plane. In order, walls are placed along x-negative, x-positive, y-negative,
5960
y-positive relative the center of the pitch.
6061
"""
6162
return [
62-
((-size[0], 0., _WALL_HEIGHT), (_WALL_THICKNESS, size[1], _WALL_HEIGHT)),
63-
((size[0], 0., _WALL_HEIGHT), (_WALL_THICKNESS, size[1], _WALL_HEIGHT)),
64-
((0., -size[1], _WALL_HEIGHT), (size[0], _WALL_THICKNESS, _WALL_HEIGHT)),
65-
((0., size[1], _WALL_HEIGHT), (size[0], _WALL_THICKNESS, _WALL_HEIGHT)),
63+
((0., -size[1], 0.), (-1, 0, 0, 0, 0, 1)),
64+
((0., size[1], 0.), (1, 0, 0, 0, 0, 1)),
65+
((-size[0], 0., 0.), (0, 1, 0, 0, 0, 1)),
66+
((size[0], 0., 0.), (0, -1, 0, 0, 0, 1)),
6667
]
6768

6869

@@ -77,6 +78,7 @@ def _build(self,
7778
size=_DEFAULT_PITCH_SIZE,
7879
goal_size=None,
7980
top_camera_distance=_TOP_CAMERA_DISTANCE,
81+
field_box=False,
8082
name='pitch'):
8183
"""Construct a pitch with walls and position detectors.
8284
@@ -86,6 +88,8 @@ def _build(self,
8688
If not specified, the goal size is inferred from pitch size with a fixed
8789
default ratio.
8890
top_camera_distance: the distance of the top-down camera to the pitch.
91+
field_box: adds a "field box" that collides with the ball but not the
92+
walkers.
8993
name: the name of this arena.
9094
"""
9195
super(Pitch, self)._build(name=name)
@@ -128,35 +132,32 @@ def _build(self,
128132

129133
# Build walls.
130134
self._walls = []
131-
for wall_pos, wall_size in _wall_pos_size(self._size):
135+
for wall_pos, wall_xyaxes in _wall_pos_xyaxes(self._size):
132136
self._walls.append(
133137
self._mjcf_root.worldbody.add(
134138
'geom',
135-
type='box',
136-
rgba=[.3, .3, .3, .0],
139+
type='plane',
140+
rgba=[.1, .1, .1, .8],
137141
pos=wall_pos,
138-
size=wall_size))
139-
# Build roof.
140-
self._roof = self._mjcf_root.worldbody.add(
141-
'geom',
142-
type='box',
143-
rgba=[.3, .3, .3, .3],
144-
pos=(0., 0., 2 * _WALL_HEIGHT),
145-
group=4,
146-
size=_roof_size(self._size))
142+
size=[1e-7, 1e-7, 1e-7],
143+
xyaxes=wall_xyaxes))
147144

148145
# Build goal position detectors.
146+
# If field_box is enabled, offset goal by 1.0 such that ball reaches the
147+
# goal position detector before bouncing off the field_box.
148+
self._fb_offset = 0.5 if field_box else 0.0
149149
goal_size = self._get_goal_size()
150150
self._home_goal = props.PositionDetector(
151-
pos=(-self._size[0] + goal_size[0], 0, goal_size[2]),
151+
pos=(-self._size[0] + goal_size[0] - self._fb_offset, 0,
152+
goal_size[2]),
152153
size=goal_size,
153154
rgba=(0, 0, 1, 0.5),
154155
visible=True,
155156
name='home_goal')
156157
self.attach(self._home_goal)
157158

158159
self._away_goal = props.PositionDetector(
159-
pos=(self._size[0] - goal_size[0], 0, goal_size[2]),
160+
pos=(self._size[0] - goal_size[0] + self._fb_offset, 0, goal_size[2]),
160161
size=goal_size,
161162
rgba=(1, 0, 0, 0.5),
162163
visible=True,
@@ -168,12 +169,26 @@ def _build(self,
168169
pos=(0, 0),
169170
size=(self._size[0] - 2 * goal_size[0],
170171
self._size[1] - 2 * goal_size[0]),
171-
rgba=(0, 0, 0, 0.1),
172+
rgba=(1, 0, 0, 0.1),
172173
inverted=True,
173174
visible=True,
174175
name='field')
175176
self.attach(self._field)
176177

178+
# Build field box.
179+
self._field_box = []
180+
if field_box:
181+
for wall_pos, wall_xyaxes in _wall_pos_xyaxes(
182+
(self._field.upper - self._field.lower) / 2.0):
183+
self._field_box.append(
184+
self._mjcf_root.worldbody.add(
185+
'geom',
186+
type='plane',
187+
rgba=[.3, .3, .3, .3],
188+
pos=wall_pos,
189+
size=[1e-7, 1e-7, 1e-7],
190+
xyaxes=wall_xyaxes))
191+
177192
def _get_goal_size(self):
178193
goal_size = self._goal_size
179194
if goal_size is None:
@@ -187,7 +202,17 @@ def _get_goal_size(self):
187202
def register_ball(self, ball):
188203
self._home_goal.register_entities(ball)
189204
self._away_goal.register_entities(ball)
190-
self._field.register_entities(ball)
205+
206+
if self._field_box:
207+
# Geoms a and b collides if:
208+
# (a.contype & b.conaffinity) || (b.contype & a.conaffinity) != 0.
209+
# See: http://www.mujoco.org/book/computation.html#Collision
210+
ball.geom.contype = (ball.geom.contype or 0) | _FIELD_BOX_CONTACT_BIT
211+
for wall in self._field_box:
212+
wall.conaffinity = _FIELD_BOX_CONTACT_BIT
213+
wall.contype = _FIELD_BOX_CONTACT_BIT
214+
else:
215+
self._field.register_entities(ball)
191216

192217
def detected_goal(self):
193218
"""Returning the team that scored a goal."""
@@ -230,6 +255,7 @@ def __init__(self,
230255
randomizer=None,
231256
keep_aspect_ratio=False,
232257
goal_size=None,
258+
field_box=False,
233259
top_camera_distance=_TOP_CAMERA_DISTANCE,
234260
name='randomized_pitch'):
235261
"""Construct a randomized pitch.
@@ -244,13 +270,16 @@ def __init__(self,
244270
goal_size: optional (depth, width, height) indicating the goal size.
245271
If not specified, the goal size is inferred from pitch size with a fixed
246272
default ratio.
273+
field_box: optional indicating if we should construct field box containing
274+
the ball (but not the walkers).
247275
top_camera_distance: the distance of the top-down camera to the pitch.
248276
name: the name of this arena.
249277
"""
250278
super(RandomizedPitch, self).__init__(
251279
size=max_size,
252280
goal_size=goal_size,
253281
top_camera_distance=top_camera_distance,
282+
field_box=field_box,
254283
name=name)
255284

256285
self._min_size = min_size
@@ -265,10 +294,10 @@ def __init__(self,
265294

266295
def _resize_goals(self, goal_size):
267296
self._home_goal.resize(
268-
pos=(-self._size[0] + goal_size[0], 0, goal_size[2]),
297+
pos=(-self._size[0] + goal_size[0] + self._fb_offset, 0, goal_size[2]),
269298
size=goal_size)
270299
self._away_goal.resize(
271-
pos=(self._size[0] - goal_size[0], 0, goal_size[2]),
300+
pos=(self._size[0] - goal_size[0] - self._fb_offset, 0, goal_size[2]),
272301
size=goal_size)
273302

274303
def initialize_episode_mjcf(self, random_state):
@@ -294,10 +323,8 @@ def initialize_episode_mjcf(self, random_state):
294323
self._ground_geom.size = list(self._size) + [_GROUND_GEOM_HEIGHT]
295324

296325
# Resize and reposition walls and roof geoms.
297-
for i, (wall_pos, wall_size) in enumerate(_wall_pos_size(self._size)):
298-
self._walls[i].size = wall_size
326+
for i, (wall_pos, _) in enumerate(_wall_pos_xyaxes(self._size)):
299327
self._walls[i].pos = wall_pos
300-
self._roof.size = _roof_size(self._size)
301328

302329
goal_size = self._get_goal_size()
303330
self._resize_goals(goal_size)
@@ -307,3 +334,9 @@ def initialize_episode_mjcf(self, random_state):
307334
pos=(0, 0),
308335
size=(self._size[0] - 2 * goal_size[0],
309336
self._size[1] - 2 * goal_size[0]))
337+
338+
# Resize and reposition field box geoms.
339+
if self._field_box:
340+
for i, (pos, _) in enumerate(
341+
_wall_pos_xyaxes((self._field.upper - self._field.lower) / 2.0)):
342+
self._field_box[i].pos = pos

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def find_data_files(package_dir, patterns):
166166

167167
setup(
168168
name='dm_control',
169-
version='0.0.288398964',
169+
version='0.0.288483845',
170170
description='Continuous control environments and MuJoCo Python bindings.',
171171
author='DeepMind',
172172
license='Apache License, Version 2.0',

0 commit comments

Comments
 (0)