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
0 commit comments