def _place_walls(self, relative_wall_margin=0.05): """Returns blocks with walls at the edges of the scene, with some margin.""" wall_width = self.height / 2 margin = self.scene_width * relative_wall_margin scene_height = self.scene_width * 2 / 3 right_wall = block_utils.Block(x=self.scene_width + margin + wall_width / 2., y=scene_height / 2, height=scene_height, width=wall_width) left_wall = block_utils.Block(x=-margin - wall_width / 2., y=scene_height / 2, height=scene_height, width=wall_width) return [right_wall, left_wall]
def _place_floor(self): floor_height = self.height / 2 floor = block_utils.Block(x=self.scene_width / 2., y=-floor_height / 2., height=floor_height, width=self.scene_width * 2) return floor
def add_block(width): block = block_utils.Block(x=current_position["x"] + width / 2, y=current_position["y"], width=width, height=self.height) current_position["x"] += width + margin return block
def create_block(width, height, shape): """Returns a block with the specified properties.""" block = block_utils.Block(width=width, height=height, angle=0., shape=shape, x=0, y=0) # x and y will be set later. return block
def _try_to_generate_one(self): """Generates a single marble_run scene. Returns: observation: a block_utils.BlocksObservation object solution: a list of Block objects in their final locations. This generator in particular always returns an empty list here. Unused """ # Generate ball and target. # Make sure that there is enough horizontal separation. min_horiz_dist = self._rel_horizontal_distance_range[ 0] * self.scene_width max_horiz_dist = self._rel_horizontal_distance_range[ 1] * self.scene_width # Start by assuming that the ball will go left to right. # Sample the position of the ball to make sure that is it at least # min_horiz_dist away from the right. ball_x = self.random_state.uniform(0, self.scene_width - min_horiz_dist) min_target_x = ball_x + min_horiz_dist # The minimum target separation is the size of the target and the ball, # so the ball cannot drop directly on the target. min_target_x = max(self._targets_height + self.height, min_target_x) max_target_x = ball_x + max_horiz_dist max_target_x = min(self.scene_width, max_target_x) target_x = self.random_state.uniform(min_target_x, max_target_x) assert target_x - ball_x < max_horiz_dist assert target_x - ball_x > min_horiz_dist # We flip a coin to exchage the positions of target and ball if self.random_state.randint(2): target_x, ball_x = ball_x, target_x # Make sure that the ball is at least two levels above the target. target_level = np.random.choice(self._target_levels) ball_level = np.random.choice(self._ball_levels) ball_level = max(ball_level, target_level + self._min_ball_target_level_diff) targets = [ block_utils.Block(x=target_x, y=target_level, shape=unity_constants.BALL_SHAPE, width=self._targets_height, height=self._targets_height) ] balls = [ block_utils.Block(x=ball_x, y=ball_level, shape=unity_constants.BALL_SHAPE, width=self.height, height=self.height) ] # Generating obstacles. obstacles = [] # Tesselate space and remove blocks too close to balls and targets. tessellation_blocks_xyw = self._build_tessellation( num_levels=ball_level + 1) for ball in balls: tessellation_blocks_xyw = self._remove_nearby_blocks( ball, tessellation_blocks_xyw) for target in targets: tessellation_blocks_xyw = self._remove_nearby_blocks( target, tessellation_blocks_xyw) # Add obstacles sequentially. num_obstacles = np.random.randint(*self._num_obstacles_range) for _ in range(num_obstacles): if tessellation_blocks_xyw.shape[0] == 0: raise stacking.GenerationError( "Not possible to generate all obstacles.") block = self._sample_block_from_tessellation( tessellation_blocks_xyw, balls, targets) obstacles.append( block_utils.Block(x=block[0], y=block[1], width=block[2], height=self.obstacles_height)) tessellation_blocks_xyw = self._remove_nearby_blocks( obstacles[-1], tessellation_blocks_xyw) floor = self._place_floor() observation_blocks = self._place_available_objects() fixed_blocks = [floor] + observation_blocks # Convert discrete vertical positions into continuous values. obstacles = self._scale_vertical_positions(obstacles) targets = self._scale_vertical_positions(targets) balls = self._scale_vertical_positions(balls) walls = self._place_walls() observation = block_utils.BlocksObservation(balls=balls, blocks=fixed_blocks, obstacles=walls + obstacles, targets=targets) return observation
def generate_one(self): """Generate a single scene. Returns: observation: a block_utils.BlocksObservation object solution: a list of Block objects in their final locations """ # Tessellate space, fractions of the blocks lengths, with a small margin. # And with the standard block height. discrete_heights = (self._corrected_height, ) discrete_widths = (self.small_width + self.margin, self.medium_width + self.margin, self.large_width + self.margin) # Sample a maximum height for the scene. num_levels = self.random_state.randint(*self._num_levels_range) this_scene_height = self.height * (num_levels + 0.5) # Set the targets. num_targets = self.random_state.randint(*self._num_targets_range) max_attempts = 10 min_overlap = 0.9 * self.small_width for _ in range(max_attempts): # Tessellate space. This returns a list of blocks in the tesselation # with shape [num_blocks, 4], where the last axis, indicats, center_x, # center_y, width, height. tessellation_blocks = ( _tessellate_discrete_rectangles_by_row_from_options( scene_width=self.scene_width, scene_height=this_scene_height, discrete_widths=discrete_widths, discrete_heights=discrete_heights, random_state=self.random_state, do_x_offset=True, return_as="cxy_dxy")) # Pick num_targets blocks from possible options. existing_blocks = [] for _ in range(num_targets): candidates = self._get_supported_blocks( tessellation_blocks, existing_blocks, min_overlap=min_overlap) if not candidates: break block_i, block = self._sample_candidates(candidates, num_samples=1)[0] tessellation_blocks = np.delete(tessellation_blocks, block_i, axis=0) existing_blocks.append(block) else: # If we successfully added as many targets as we needed, we do not need # to keep attempting by breaking the loop. break else: # If we got here, is because we did not break out of the loop, and # we have exhausted all attempts. raise ValueError( "Maximum number of attempts reached to generate silhouette.") targets = [ block_utils.Block(x=b[0], y=b[1] + _VERTICAL_CORRECTION / 2, width=b[2] - self.margin, height=b[3] - _VERTICAL_CORRECTION) for b in existing_blocks ] # Set the obstacles. num_obstacles = self.random_state.randint(*self._num_obstacles_range) # We only require negative overlap for obstacles. candidates = self._get_supported_blocks(tessellation_blocks, existing_blocks, min_overlap=-2.) sampled_candidates = self._sample_candidates(candidates, num_samples=min( num_obstacles, len(candidates))) obstacles = [ block_utils.Block(x=block[0], y=block[1], width=block[2] - self.margin, height=self.obstacles_height) for _, block in sampled_candidates ] tessellation_blocks = np.delete( tessellation_blocks, [block_i for block_i, _ in sampled_candidates], axis=0) observation_blocks = self._place_available_objects() floor = self._place_floor() observation = block_utils.BlocksObservation(blocks=[floor] + observation_blocks, obstacles=obstacles, targets=targets, balls=[]) return observation
def generate_one(self): """Generate a single scene. Returns: observation: a block_utils.BlocksObservation object solution: a list of Block objects in their final locations """ # Pick the set of y-positions we want for our obstacles idx = np.arange(len(self._obstacles_ys_range)) obstacles_ys_range = self._obstacles_ys_range[self.random_state.choice( idx)] # Place the obstacles at each level, going from bottom to top obstacles = [] for y in obstacles_ys_range: available_widths = np.arange(*self._obstacles_width_range) obstacles_begins, obstacles_ends = self._generate_line_of_blocks( available_widths=available_widths, num_blocks=self.random_state.randint( *self._num_obstacles_range), min_available_width=self.small_width + 1, min_interdistance=self._min_obstacles_interdistance) # Now actually create the obstacles for obstacle_begin, obstacle_end in zip(obstacles_begins, obstacles_ends): center = (obstacle_begin + obstacle_end) // 2 width = obstacle_end - obstacle_begin obstacle = block_utils.Block(x=center, y=y, width=width, height=self.obstacles_height) obstacles.append(obstacle) # Pick y positions for the targets idx = np.arange(len(self._targets_ys_range)) targets_ys_range = self._targets_ys_range[self.random_state.choice( idx)] targets = [] for y in targets_ys_range: available_widths = [self.small_width, self.medium_width] if not self._use_legacy_obstacles_heights: available_widths = [self._targets_side] num_targets = self.random_state.randint(*self._num_targets_range) targets_begins, targets_ends = self._generate_line_of_blocks( available_widths=available_widths, num_blocks=num_targets, min_interdistance=self._min_targets_interdistance) for target_begin, target_end in zip(targets_begins, targets_ends): center = (target_begin + target_end) // 2 width = target_end - target_begin target = block_utils.Block(x=center, y=y, width=width, height=self._targets_height) targets.append(target) observation_blocks = self._place_available_objects() floor = self._place_floor() observation = block_utils.BlocksObservation(blocks=[floor] + observation_blocks, obstacles=obstacles, targets=targets, balls=[]) return observation
def generate_one(self): """Generate a single scene. Returns: observation: a BlocksObservation object solution: a list of Block objects in their final locations """ # pick the set of y-positions we want for our obstacles idx = np.arange(len(self._obstacles_ys_range)) obstacles_ys = self._obstacles_ys_range[self.random_state.choice(idx)] # place the obstacles at each level, going from bottom to top obstacles = [] for y in obstacles_ys: # get the number of obstacles at this layer num_obstacles = self.random_state.randint(*self.num_blocks_range) # pick a set of obstacle widths, and check that the sum of the widths is # not greater than the scene width plus some buffer room. keep regenerting # obstacles until this is the case. available_width = 0 while available_width < self.small_width + 1: available_widths = np.arange(*self._obstacles_width_range) obstacles_lengths = self.random_state.choice( available_widths, size=[num_obstacles]) available_width = self.scene_width - np.sum(obstacles_lengths) # compute the left and right edges of each obstacle, assuming the # obstacles are all placed right next to each other beginning from the # left side of the scene. obstacles_begins = np.concatenate( [np.array([0], dtype=np.int32), np.cumsum(obstacles_lengths)[:-1]]) obstacles_ends = np.cumsum(obstacles_lengths) # available_width now is the amount of space left on the floor, not taken # up by obstacles. we split this into a few chunks of random size to space # the obstacles out along the floor relative_shifts = self.random_state.uniform( 0., 1., size=[num_obstacles + 1]) relative_shifts /= np.sum(relative_shifts) relative_shifts = np.floor(relative_shifts * available_width) shifts = np.cumsum(relative_shifts.astype(np.int32))[:-1] obstacles_begins += shifts obstacles_ends += shifts # now actually create the obstacles for obstacle_begin, obstacle_end in zip(obstacles_begins, obstacles_ends): center = (obstacle_begin + obstacle_end) // 2 width = obstacle_end - obstacle_begin obstacle = block_utils.Block( x=center, y=y, width=width, height=self.obstacle_height) obstacles.append(obstacle) observation_blocks = self._place_available_objects() floor = self._place_floor() observation = block_utils.BlocksObservation( blocks=[floor] + observation_blocks, obstacles=obstacles, targets=[], balls=[]) return observation