first pass of updated minitaur quadruped environment
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
"""Randomize the minitaur_gym_alternating_leg_env when reset() is called.
|
||||
|
||||
The randomization include swing_offset, extension_offset of all legs that mimics
|
||||
bent legs, desired_pitch from user input, battery voltage and motor damping.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import tensorflow as tf
|
||||
from pybullet_envs.minitaur.minitaur.envs import env_randomizer_base
|
||||
|
||||
# Absolute range.
|
||||
NUM_LEGS = 4
|
||||
BATTERY_VOLTAGE_RANGE = (14.8, 16.8)
|
||||
MOTOR_VISCOUS_DAMPING_RANGE = (0, 0.01)
|
||||
|
||||
|
||||
class MinitaurAlternatingLegsEnvRandomizer(
|
||||
env_randomizer_base.EnvRandomizerBase):
|
||||
"""A randomizer that changes the minitaur_gym_alternating_leg_env."""
|
||||
|
||||
def __init__(self,
|
||||
perturb_swing_bound=0.1,
|
||||
perturb_extension_bound=0.1,
|
||||
perturb_desired_pitch_bound=0.01):
|
||||
super(MinitaurAlternatingLegsEnvRandomizer, self).__init__()
|
||||
self.perturb_swing_bound = perturb_swing_bound
|
||||
self.perturb_extension_bound = perturb_extension_bound
|
||||
self.perturb_desired_pitch_bound = perturb_desired_pitch_bound
|
||||
|
||||
def randomize_env(self, env):
|
||||
perturb_magnitude = np.random.uniform(
|
||||
low=-self.perturb_swing_bound,
|
||||
high=self.perturb_swing_bound,
|
||||
size=NUM_LEGS)
|
||||
env.set_swing_offset(perturb_magnitude)
|
||||
tf.logging.info("swing_offset: {}".format(perturb_magnitude))
|
||||
|
||||
perturb_magnitude = np.random.uniform(
|
||||
low=-self.perturb_extension_bound,
|
||||
high=self.perturb_extension_bound,
|
||||
size=NUM_LEGS)
|
||||
env.set_extension_offset(perturb_magnitude)
|
||||
tf.logging.info("extension_offset: {}".format(perturb_magnitude))
|
||||
|
||||
perturb_magnitude = np.random.uniform(
|
||||
low=-self.perturb_desired_pitch_bound,
|
||||
high=self.perturb_desired_pitch_bound)
|
||||
env.set_desired_pitch(perturb_magnitude)
|
||||
tf.logging.info("desired_pitch: {}".format(perturb_magnitude))
|
||||
|
||||
randomized_battery_voltage = np.random.uniform(BATTERY_VOLTAGE_RANGE[0],
|
||||
BATTERY_VOLTAGE_RANGE[1])
|
||||
env.minitaur.SetBatteryVoltage(randomized_battery_voltage)
|
||||
tf.logging.info("battery_voltage: {}".format(randomized_battery_voltage))
|
||||
|
||||
randomized_motor_damping = np.random.uniform(MOTOR_VISCOUS_DAMPING_RANGE[0],
|
||||
MOTOR_VISCOUS_DAMPING_RANGE[1])
|
||||
env.minitaur.SetMotorViscousDamping(randomized_motor_damping)
|
||||
tf.logging.info("motor_damping: {}".format(randomized_motor_damping))
|
||||
@@ -0,0 +1,69 @@
|
||||
"""Randomize the minitaur_gym_env when reset() is called."""
|
||||
import random
|
||||
|
||||
import numpy as np
|
||||
from pybullet_envs.minitaur.envs import env_randomizer_base
|
||||
|
||||
# Relative range.
|
||||
MINITAUR_BASE_MASS_ERROR_RANGE = (-0.2, 0.2) # 0.2 means 20%
|
||||
MINITAUR_LEG_MASS_ERROR_RANGE = (-0.2, 0.2) # 0.2 means 20%
|
||||
# Absolute range.
|
||||
BATTERY_VOLTAGE_RANGE = (14.8, 16.8) # Unit: Volt
|
||||
MOTOR_VISCOUS_DAMPING_RANGE = (0, 0.01) # Unit: N*m*s/rad (torque/angular vel)
|
||||
MINITAUR_LEG_FRICTION = (0.8, 1.5) # Unit: dimensionless
|
||||
|
||||
|
||||
class MinitaurEnvRandomizer(env_randomizer_base.EnvRandomizerBase):
|
||||
"""A randomizer that change the minitaur_gym_env during every reset."""
|
||||
|
||||
def __init__(self,
|
||||
minitaur_base_mass_err_range=MINITAUR_BASE_MASS_ERROR_RANGE,
|
||||
minitaur_leg_mass_err_range=MINITAUR_LEG_MASS_ERROR_RANGE,
|
||||
battery_voltage_range=BATTERY_VOLTAGE_RANGE,
|
||||
motor_viscous_damping_range=MOTOR_VISCOUS_DAMPING_RANGE):
|
||||
self._minitaur_base_mass_err_range = minitaur_base_mass_err_range
|
||||
self._minitaur_leg_mass_err_range = minitaur_leg_mass_err_range
|
||||
self._battery_voltage_range = battery_voltage_range
|
||||
self._motor_viscous_damping_range = motor_viscous_damping_range
|
||||
|
||||
def randomize_env(self, env):
|
||||
self._randomize_minitaur(env.minitaur)
|
||||
|
||||
def _randomize_minitaur(self, minitaur):
|
||||
"""Randomize various physical properties of minitaur.
|
||||
|
||||
It randomizes the mass/inertia of the base, mass/inertia of the legs,
|
||||
friction coefficient of the feet, the battery voltage and the motor damping
|
||||
at each reset() of the environment.
|
||||
|
||||
Args:
|
||||
minitaur: the Minitaur instance in minitaur_gym_env environment.
|
||||
"""
|
||||
base_mass = minitaur.GetBaseMassesFromURDF()
|
||||
randomized_base_mass = random.uniform(
|
||||
np.array(base_mass) * (1.0 + self._minitaur_base_mass_err_range[0]),
|
||||
np.array(base_mass) * (1.0 + self._minitaur_base_mass_err_range[1]))
|
||||
minitaur.SetBaseMasses(randomized_base_mass)
|
||||
|
||||
leg_masses = minitaur.GetLegMassesFromURDF()
|
||||
leg_masses_lower_bound = np.array(leg_masses) * (
|
||||
1.0 + self._minitaur_leg_mass_err_range[0])
|
||||
leg_masses_upper_bound = np.array(leg_masses) * (
|
||||
1.0 + self._minitaur_leg_mass_err_range[1])
|
||||
randomized_leg_masses = [
|
||||
np.random.uniform(leg_masses_lower_bound[i], leg_masses_upper_bound[i])
|
||||
for i in xrange(len(leg_masses))
|
||||
]
|
||||
minitaur.SetLegMasses(randomized_leg_masses)
|
||||
|
||||
randomized_battery_voltage = random.uniform(BATTERY_VOLTAGE_RANGE[0],
|
||||
BATTERY_VOLTAGE_RANGE[1])
|
||||
minitaur.SetBatteryVoltage(randomized_battery_voltage)
|
||||
|
||||
randomized_motor_damping = random.uniform(MOTOR_VISCOUS_DAMPING_RANGE[0],
|
||||
MOTOR_VISCOUS_DAMPING_RANGE[1])
|
||||
minitaur.SetMotorViscousDamping(randomized_motor_damping)
|
||||
|
||||
randomized_foot_friction = random.uniform(MINITAUR_LEG_FRICTION[0],
|
||||
MINITAUR_LEG_FRICTION[1])
|
||||
minitaur.SetFootFriction(randomized_foot_friction)
|
||||
@@ -0,0 +1,25 @@
|
||||
"""A config file for parameters and their ranges in dynamics randomization."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
|
||||
PARAM_RANGE = {
|
||||
# The following ranges are in percentage. e.g. 0.8 means 80%.
|
||||
"mass": [0.8, 1.2],
|
||||
"inertia": [0.5, 1.5],
|
||||
"motor strength": [0.8, 1.2],
|
||||
# The following ranges are the physical values, in SI unit.
|
||||
"motor friction": [0, 0.05], # Viscous damping (Nm s/rad).
|
||||
"control step": [0.003, 0.02], # Time inteval (s).
|
||||
"latency": [0.0, 0.04], # Time inteval (s).
|
||||
"lateral friction": [0.5, 1.25], # Friction coefficient (dimensionless).
|
||||
"battery": [14.0, 16.8], # Voltage (V).
|
||||
"joint friction": [0, 0.05], # Coulomb friction torque (Nm).
|
||||
}
|
||||
|
||||
|
||||
def all_params():
|
||||
"""Randomize all the physical parameters."""
|
||||
return PARAM_RANGE
|
||||
@@ -0,0 +1,139 @@
|
||||
"""An environment randomizer that randomizes physical parameters from config."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import functools
|
||||
import random
|
||||
|
||||
import numpy as np
|
||||
import tensorflow as tf
|
||||
from pybullet_envs.minitaur.envs import env_randomizer_base
|
||||
from pybullet_envs.minitaur.envs.env_randomizers import minitaur_env_randomizer_config
|
||||
|
||||
SIMULATION_TIME_STEP = 0.001
|
||||
|
||||
|
||||
class MinitaurEnvRandomizerFromConfig(env_randomizer_base.EnvRandomizerBase):
|
||||
"""A randomizer that change the minitaur_gym_env during every reset."""
|
||||
|
||||
def __init__(self, config=None):
|
||||
if config is None:
|
||||
config = "all_params"
|
||||
try:
|
||||
config = getattr(minitaur_env_randomizer_config, config)
|
||||
except AttributeError:
|
||||
raise ValueError("Config {} is not found.".format(config))
|
||||
self._randomization_param_dict = config()
|
||||
tf.logging.info("Randomization config is: {}".format(
|
||||
self._randomization_param_dict))
|
||||
|
||||
def randomize_env(self, env):
|
||||
"""Randomize various physical properties of the environment.
|
||||
|
||||
It randomizes the physical parameters according to the input configuration.
|
||||
|
||||
Args:
|
||||
env: A minitaur gym environment.
|
||||
"""
|
||||
self._randomization_function_dict = self._build_randomization_function_dict(
|
||||
env)
|
||||
for param_name, random_range in self._randomization_param_dict.iteritems():
|
||||
self._randomization_function_dict[param_name](
|
||||
lower_bound=random_range[0], upper_bound=random_range[1])
|
||||
|
||||
def _build_randomization_function_dict(self, env):
|
||||
func_dict = {}
|
||||
func_dict["mass"] = functools.partial(
|
||||
self._randomize_masses, minitaur=env.minitaur)
|
||||
func_dict["inertia"] = functools.partial(
|
||||
self._randomize_inertia, minitaur=env.minitaur)
|
||||
func_dict["latency"] = functools.partial(
|
||||
self._randomize_latency, minitaur=env.minitaur)
|
||||
func_dict["joint friction"] = functools.partial(
|
||||
self._randomize_joint_friction, minitaur=env.minitaur)
|
||||
func_dict["motor friction"] = functools.partial(
|
||||
self._randomize_motor_friction, minitaur=env.minitaur)
|
||||
func_dict["restitution"] = functools.partial(
|
||||
self._randomize_contact_restitution, minitaur=env.minitaur)
|
||||
func_dict["lateral friction"] = functools.partial(
|
||||
self._randomize_contact_friction, minitaur=env.minitaur)
|
||||
func_dict["battery"] = functools.partial(
|
||||
self._randomize_battery_level, minitaur=env.minitaur)
|
||||
func_dict["motor strength"] = functools.partial(
|
||||
self._randomize_motor_strength, minitaur=env.minitaur)
|
||||
# Settinmg control step needs access to the environment.
|
||||
func_dict["control step"] = functools.partial(
|
||||
self._randomize_control_step, env=env)
|
||||
return func_dict
|
||||
|
||||
def _randomize_control_step(self, env, lower_bound, upper_bound):
|
||||
randomized_control_step = random.uniform(lower_bound, upper_bound)
|
||||
env.set_time_step(randomized_control_step)
|
||||
tf.logging.info("control step is: {}".format(randomized_control_step))
|
||||
|
||||
def _randomize_masses(self, minitaur, lower_bound, upper_bound):
|
||||
base_mass = minitaur.GetBaseMassesFromURDF()
|
||||
random_base_ratio = random.uniform(lower_bound, upper_bound)
|
||||
randomized_base_mass = random_base_ratio * np.array(base_mass)
|
||||
minitaur.SetBaseMasses(randomized_base_mass)
|
||||
tf.logging.info("base mass is: {}".format(randomized_base_mass))
|
||||
|
||||
leg_masses = minitaur.GetLegMassesFromURDF()
|
||||
random_leg_ratio = random.uniform(lower_bound, upper_bound)
|
||||
randomized_leg_masses = random_leg_ratio * np.array(leg_masses)
|
||||
minitaur.SetLegMasses(randomized_leg_masses)
|
||||
tf.logging.info("leg mass is: {}".format(randomized_leg_masses))
|
||||
|
||||
def _randomize_inertia(self, minitaur, lower_bound, upper_bound):
|
||||
base_inertia = minitaur.GetBaseInertiasFromURDF()
|
||||
random_base_ratio = random.uniform(lower_bound, upper_bound)
|
||||
randomized_base_inertia = random_base_ratio * np.array(base_inertia)
|
||||
minitaur.SetBaseInertias(randomized_base_inertia)
|
||||
tf.logging.info("base inertia is: {}".format(randomized_base_inertia))
|
||||
leg_inertia = minitaur.GetLegInertiasFromURDF()
|
||||
random_leg_ratio = random.uniform(lower_bound, upper_bound)
|
||||
randomized_leg_inertia = random_leg_ratio * np.array(leg_inertia)
|
||||
minitaur.SetLegInertias(randomized_leg_inertia)
|
||||
tf.logging.info("leg inertia is: {}".format(randomized_leg_inertia))
|
||||
|
||||
def _randomize_latency(self, minitaur, lower_bound, upper_bound):
|
||||
randomized_latency = random.uniform(lower_bound, upper_bound)
|
||||
minitaur.SetControlLatency(randomized_latency)
|
||||
tf.logging.info("control latency is: {}".format(randomized_latency))
|
||||
|
||||
def _randomize_joint_friction(self, minitaur, lower_bound, upper_bound):
|
||||
num_knee_joints = minitaur.GetNumKneeJoints()
|
||||
randomized_joint_frictions = np.random.uniform(
|
||||
[lower_bound] * num_knee_joints, [upper_bound] * num_knee_joints)
|
||||
minitaur.SetJointFriction(randomized_joint_frictions)
|
||||
tf.logging.info("joint friction is: {}".format(randomized_joint_frictions))
|
||||
|
||||
def _randomize_motor_friction(self, minitaur, lower_bound, upper_bound):
|
||||
randomized_motor_damping = random.uniform(lower_bound, upper_bound)
|
||||
minitaur.SetMotorViscousDamping(randomized_motor_damping)
|
||||
tf.logging.info("motor friction is: {}".format(randomized_motor_damping))
|
||||
|
||||
def _randomize_contact_restitution(self, minitaur, lower_bound, upper_bound):
|
||||
randomized_restitution = random.uniform(lower_bound, upper_bound)
|
||||
minitaur.SetFootRestitution(randomized_restitution)
|
||||
tf.logging.info("foot restitution is: {}".format(randomized_restitution))
|
||||
|
||||
def _randomize_contact_friction(self, minitaur, lower_bound, upper_bound):
|
||||
randomized_foot_friction = random.uniform(lower_bound, upper_bound)
|
||||
minitaur.SetFootFriction(randomized_foot_friction)
|
||||
tf.logging.info("foot friction is: {}".format(randomized_foot_friction))
|
||||
|
||||
def _randomize_battery_level(self, minitaur, lower_bound, upper_bound):
|
||||
randomized_battery_voltage = random.uniform(lower_bound, upper_bound)
|
||||
minitaur.SetBatteryVoltage(randomized_battery_voltage)
|
||||
tf.logging.info("battery voltage is: {}".format(randomized_battery_voltage))
|
||||
|
||||
def _randomize_motor_strength(self, minitaur, lower_bound, upper_bound):
|
||||
randomized_motor_strength_ratios = np.random.uniform(
|
||||
[lower_bound] * minitaur.num_motors,
|
||||
[upper_bound] * minitaur.num_motors)
|
||||
minitaur.SetMotorStrengthRatios(randomized_motor_strength_ratios)
|
||||
tf.logging.info(
|
||||
"motor strength is: {}".format(randomized_motor_strength_ratios))
|
||||
@@ -0,0 +1,91 @@
|
||||
"""Adds random forces to the base of Minitaur during the simulation steps."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import math
|
||||
import numpy as np
|
||||
from pybullet_envs.minitaur.envs import env_randomizer_base
|
||||
|
||||
_PERTURBATION_START_STEP = 100
|
||||
_PERTURBATION_INTERVAL_STEPS = 200
|
||||
_PERTURBATION_DURATION_STEPS = 10
|
||||
_HORIZONTAL_FORCE_UPPER_BOUND = 120
|
||||
_HORIZONTAL_FORCE_LOWER_BOUND = 240
|
||||
_VERTICAL_FORCE_UPPER_BOUND = 300
|
||||
_VERTICAL_FORCE_LOWER_BOUND = 500
|
||||
|
||||
|
||||
class MinitaurPushRandomizer(env_randomizer_base.EnvRandomizerBase):
|
||||
"""Applies a random impulse to the base of Minitaur."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
perturbation_start_step=_PERTURBATION_START_STEP,
|
||||
perturbation_interval_steps=_PERTURBATION_INTERVAL_STEPS,
|
||||
perturbation_duration_steps=_PERTURBATION_DURATION_STEPS,
|
||||
horizontal_force_bound=None,
|
||||
vertical_force_bound=None,
|
||||
):
|
||||
"""Initializes the randomizer.
|
||||
|
||||
Args:
|
||||
perturbation_start_step: No perturbation force before the env has advanced
|
||||
this amount of steps.
|
||||
perturbation_interval_steps: The step interval between applying
|
||||
perturbation forces.
|
||||
perturbation_duration_steps: The duration of the perturbation force.
|
||||
horizontal_force_bound: The lower and upper bound of the applied force
|
||||
magnitude when projected in the horizontal plane.
|
||||
vertical_force_bound: The z component (abs value) bound of the applied
|
||||
perturbation force.
|
||||
"""
|
||||
self._perturbation_start_step = perturbation_start_step
|
||||
self._perturbation_interval_steps = perturbation_interval_steps
|
||||
self._perturbation_duration_steps = perturbation_duration_steps
|
||||
self._horizontal_force_bound = (
|
||||
horizontal_force_bound if horizontal_force_bound else
|
||||
[_HORIZONTAL_FORCE_LOWER_BOUND, _HORIZONTAL_FORCE_UPPER_BOUND])
|
||||
self._vertical_force_bound = (
|
||||
vertical_force_bound if vertical_force_bound else
|
||||
[_VERTICAL_FORCE_LOWER_BOUND, _VERTICAL_FORCE_UPPER_BOUND])
|
||||
|
||||
def randomize_env(self, env):
|
||||
"""Randomizes the simulation environment.
|
||||
|
||||
Args:
|
||||
env: The Minitaur gym environment to be randomized.
|
||||
"""
|
||||
pass
|
||||
|
||||
def randomize_step(self, env):
|
||||
"""Randomizes simulation steps.
|
||||
|
||||
Will be called at every timestep. May add random forces/torques to Minitaur.
|
||||
|
||||
Args:
|
||||
env: The Minitaur gym environment to be randomized.
|
||||
"""
|
||||
base_link_ids = env.minitaur.chassis_link_ids
|
||||
if env.env_step_counter % self._perturbation_interval_steps == 0:
|
||||
self._applied_link_id = base_link_ids[np.random.randint(
|
||||
0, len(base_link_ids))]
|
||||
horizontal_force_magnitude = np.random.uniform(
|
||||
self._horizontal_force_bound[0], self._horizontal_force_bound[1])
|
||||
theta = np.random.uniform(0, 2 * math.pi)
|
||||
vertical_force_magnitude = np.random.uniform(
|
||||
self._vertical_force_bound[0], self._vertical_force_bound[1])
|
||||
self._applied_force = horizontal_force_magnitude * np.array(
|
||||
[math.cos(theta), math.sin(theta), 0]) + np.array(
|
||||
[0, 0, -vertical_force_magnitude])
|
||||
|
||||
if (env.env_step_counter % self._perturbation_interval_steps <
|
||||
self._perturbation_duration_steps) and (env.env_step_counter >=
|
||||
self._perturbation_start_step):
|
||||
env.pybullet_client.applyExternalForce(
|
||||
objectUniqueId=env.minitaur.quadruped,
|
||||
linkIndex=self._applied_link_id,
|
||||
forceObj=self._applied_force,
|
||||
posObj=[0.0, 0.0, 0.0],
|
||||
flags=env.pybullet_client.LINK_FRAME)
|
||||
@@ -0,0 +1,295 @@
|
||||
"""Generates a random terrain at Minitaur gym environment reset."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import itertools
|
||||
import math
|
||||
import enum
|
||||
import numpy as np
|
||||
|
||||
from pybullet_envs.minitaur.envs import env_randomizer_base
|
||||
|
||||
_GRID_LENGTH = 15
|
||||
_GRID_WIDTH = 10
|
||||
_MAX_SAMPLE_SIZE = 30
|
||||
_MIN_BLOCK_DISTANCE = 0.7
|
||||
_MAX_BLOCK_LENGTH = _MIN_BLOCK_DISTANCE
|
||||
_MIN_BLOCK_LENGTH = _MAX_BLOCK_LENGTH / 2
|
||||
_MAX_BLOCK_HEIGHT = 0.05
|
||||
_MIN_BLOCK_HEIGHT = _MAX_BLOCK_HEIGHT / 2
|
||||
|
||||
|
||||
class PoissonDisc2D(object):
|
||||
"""Generates 2D points using Poisson disk sampling method.
|
||||
|
||||
Implements the algorithm described in:
|
||||
http://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf
|
||||
Unlike the uniform sampling method that creates small clusters of points,
|
||||
Poisson disk method enforces the minimum distance between points and is more
|
||||
suitable for generating a spatial distribution of non-overlapping objects.
|
||||
"""
|
||||
|
||||
def __init__(self, grid_length, grid_width, min_radius, max_sample_size):
|
||||
"""Initializes the algorithm.
|
||||
|
||||
Args:
|
||||
grid_length: The length of the bounding square in which points are
|
||||
sampled.
|
||||
grid_width: The width of the bounding square in which points are
|
||||
sampled.
|
||||
min_radius: The minimum distance between any pair of points.
|
||||
max_sample_size: The maximum number of sample points around a active site.
|
||||
See details in the algorithm description.
|
||||
"""
|
||||
self._cell_length = min_radius / math.sqrt(2)
|
||||
self._grid_length = grid_length
|
||||
self._grid_width = grid_width
|
||||
self._grid_size_x = int(grid_length / self._cell_length) + 1
|
||||
self._grid_size_y = int(grid_width / self._cell_length) + 1
|
||||
self._min_radius = min_radius
|
||||
self._max_sample_size = max_sample_size
|
||||
|
||||
# Flattern the 2D grid as an 1D array. The grid is used for fast nearest
|
||||
# point searching.
|
||||
self._grid = [None] * self._grid_size_x * self._grid_size_y
|
||||
|
||||
# Generate the first sample point and set it as an active site.
|
||||
first_sample = np.array(
|
||||
np.random.random_sample(2)) * [grid_length, grid_width]
|
||||
self._active_list = [first_sample]
|
||||
|
||||
# Also store the sample point in the grid.
|
||||
self._grid[self._point_to_index_1d(first_sample)] = first_sample
|
||||
|
||||
def _point_to_index_1d(self, point):
|
||||
"""Computes the index of a point in the grid array.
|
||||
|
||||
Args:
|
||||
point: A 2D point described by its coordinates (x, y).
|
||||
|
||||
Returns:
|
||||
The index of the point within the self._grid array.
|
||||
"""
|
||||
return self._index_2d_to_1d(self._point_to_index_2d(point))
|
||||
|
||||
def _point_to_index_2d(self, point):
|
||||
"""Computes the 2D index (aka cell ID) of a point in the grid.
|
||||
|
||||
Args:
|
||||
point: A 2D point (list) described by its coordinates (x, y).
|
||||
|
||||
Returns:
|
||||
x_index: The x index of the cell the point belongs to.
|
||||
y_index: The y index of the cell the point belongs to.
|
||||
"""
|
||||
x_index = int(point[0] / self._cell_length)
|
||||
y_index = int(point[1] / self._cell_length)
|
||||
return x_index, y_index
|
||||
|
||||
def _index_2d_to_1d(self, index2d):
|
||||
"""Converts the 2D index to the 1D position in the grid array.
|
||||
|
||||
Args:
|
||||
index2d: The 2D index of a point (aka the cell ID) in the grid.
|
||||
|
||||
Returns:
|
||||
The 1D position of the cell within the self._grid array.
|
||||
"""
|
||||
return index2d[0] + index2d[1] * self._grid_size_x
|
||||
|
||||
def _is_in_grid(self, point):
|
||||
"""Checks if the point is inside the grid boundary.
|
||||
|
||||
Args:
|
||||
point: A 2D point (list) described by its coordinates (x, y).
|
||||
|
||||
Returns:
|
||||
Whether the point is inside the grid.
|
||||
"""
|
||||
return (0 <= point[0] < self._grid_length) and (0 <= point[1] <
|
||||
self._grid_width)
|
||||
|
||||
def _is_in_range(self, index2d):
|
||||
"""Checks if the cell ID is within the grid.
|
||||
|
||||
Args:
|
||||
index2d: The 2D index of a point (aka the cell ID) in the grid.
|
||||
|
||||
Returns:
|
||||
Whether the cell (2D index) is inside the grid.
|
||||
"""
|
||||
|
||||
return (0 <= index2d[0] < self._grid_size_x) and (0 <= index2d[1] <
|
||||
self._grid_size_y)
|
||||
|
||||
def _is_close_to_existing_points(self, point):
|
||||
"""Checks if the point is close to any already sampled (and stored) points.
|
||||
|
||||
Args:
|
||||
point: A 2D point (list) described by its coordinates (x, y).
|
||||
|
||||
Returns:
|
||||
True iff the distance of the point to any existing points is smaller than
|
||||
the min_radius
|
||||
"""
|
||||
px, py = self._point_to_index_2d(point)
|
||||
# Now we can check nearby cells for existing points
|
||||
for neighbor_cell in itertools.product(
|
||||
xrange(px - 1, px + 2), xrange(py - 1, py + 2)):
|
||||
|
||||
if not self._is_in_range(neighbor_cell):
|
||||
continue
|
||||
|
||||
maybe_a_point = self._grid[self._index_2d_to_1d(neighbor_cell)]
|
||||
if maybe_a_point is not None and np.linalg.norm(
|
||||
maybe_a_point - point) < self._min_radius:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def sample(self):
|
||||
"""Samples new points around some existing point.
|
||||
|
||||
Removes the sampling base point and also stores the new jksampled points if
|
||||
they are far enough from all existing points.
|
||||
"""
|
||||
active_point = self._active_list.pop()
|
||||
for _ in xrange(self._max_sample_size):
|
||||
# Generate random points near the current active_point between the radius
|
||||
random_radius = np.random.uniform(self._min_radius, 2 * self._min_radius)
|
||||
random_angle = np.random.uniform(0, 2 * math.pi)
|
||||
|
||||
# The sampled 2D points near the active point
|
||||
sample = random_radius * np.array(
|
||||
[np.cos(random_angle), np.sin(random_angle)]) + active_point
|
||||
|
||||
if not self._is_in_grid(sample):
|
||||
continue
|
||||
|
||||
if self._is_close_to_existing_points(sample):
|
||||
continue
|
||||
|
||||
self._active_list.append(sample)
|
||||
self._grid[self._point_to_index_1d(sample)] = sample
|
||||
|
||||
def generate(self):
|
||||
"""Generates the Poisson disc distribution of 2D points.
|
||||
|
||||
Although the while loop looks scary, the algorithm is in fact O(N), where N
|
||||
is the number of cells within the grid. When we sample around a base point
|
||||
(in some base cell), new points will not be pushed into the base cell
|
||||
because of the minimum distance constraint. Once the current base point is
|
||||
removed, all future searches cannot start from within the same base cell.
|
||||
|
||||
Returns:
|
||||
All sampled points. The points are inside the quare [0, grid_length] x [0,
|
||||
grid_width]
|
||||
"""
|
||||
|
||||
while self._active_list:
|
||||
self.sample()
|
||||
|
||||
all_sites = []
|
||||
for p in self._grid:
|
||||
if p is not None:
|
||||
all_sites.append(p)
|
||||
|
||||
return all_sites
|
||||
|
||||
|
||||
class TerrainType(enum.Enum):
|
||||
"""The randomzied terrain types we can use in the gym env."""
|
||||
RANDOM_BLOCKS = 1
|
||||
TRIANGLE_MESH = 2
|
||||
|
||||
|
||||
class MinitaurTerrainRandomizer(env_randomizer_base.EnvRandomizerBase):
|
||||
"""Generates an uneven terrain in the gym env."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
terrain_type=TerrainType.TRIANGLE_MESH,
|
||||
mesh_filename="robotics/reinforcement_learning/minitaur/envs/testdata/"
|
||||
"triangle_mesh_terrain/terrain9735.obj",
|
||||
mesh_scale=None):
|
||||
"""Initializes the randomizer.
|
||||
|
||||
Args:
|
||||
terrain_type: Whether to generate random blocks or load a triangle mesh.
|
||||
mesh_filename: The mesh file to be used. The mesh will only be loaded if
|
||||
terrain_type is set to TerrainType.TRIANGLE_MESH.
|
||||
mesh_scale: the scaling factor for the triangles in the mesh file.
|
||||
"""
|
||||
self._terrain_type = terrain_type
|
||||
self._mesh_filename = mesh_filename
|
||||
self._mesh_scale = mesh_scale if mesh_scale else [1.0, 1.0, 0.3]
|
||||
|
||||
def randomize_env(self, env):
|
||||
"""Generate a random terrain for the current env.
|
||||
|
||||
Args:
|
||||
env: A minitaur gym environment.
|
||||
"""
|
||||
|
||||
if self._terrain_type is TerrainType.TRIANGLE_MESH:
|
||||
self._load_triangle_mesh(env)
|
||||
if self._terrain_type is TerrainType.RANDOM_BLOCKS:
|
||||
self._generate_convex_blocks(env)
|
||||
|
||||
def _load_triangle_mesh(self, env):
|
||||
"""Represents the random terrain using a triangle mesh.
|
||||
|
||||
It is possible for Minitaur leg to stuck at the common edge of two triangle
|
||||
pieces. To prevent this from happening, we recommend using hard contacts
|
||||
(or high stiffness values) for Minitaur foot in sim.
|
||||
|
||||
Args:
|
||||
env: A minitaur gym environment.
|
||||
"""
|
||||
env.pybullet_client.removeBody(env.ground_id)
|
||||
terrain_collision_shape_id = env.pybullet_client.createCollisionShape(
|
||||
shapeType=env.pybullet_client.GEOM_MESH,
|
||||
fileName=self._mesh_filename,
|
||||
flags=1,
|
||||
meshScale=self._mesh_scale)
|
||||
env.ground_id = env.pybullet_client.createMultiBody(
|
||||
baseMass=0,
|
||||
baseCollisionShapeIndex=terrain_collision_shape_id,
|
||||
basePosition=[0, 0, 0])
|
||||
|
||||
def _generate_convex_blocks(self, env):
|
||||
"""Adds random convex blocks to the flat ground.
|
||||
|
||||
We use the Possion disk algorithm to add some random blocks on the ground.
|
||||
Possion disk algorithm sets the minimum distance between two sampling
|
||||
points, thus voiding the clustering effect in uniform N-D distribution.
|
||||
|
||||
Args:
|
||||
env: A minitaur gym environment.
|
||||
|
||||
"""
|
||||
|
||||
poisson_disc = PoissonDisc2D(_GRID_LENGTH, _GRID_WIDTH, _MIN_BLOCK_DISTANCE,
|
||||
_MAX_SAMPLE_SIZE)
|
||||
|
||||
block_centers = poisson_disc.generate()
|
||||
|
||||
for center in block_centers:
|
||||
# We want the blocks to be in front of the robot.
|
||||
shifted_center = np.array(center) - [2, _GRID_WIDTH / 2]
|
||||
|
||||
# Do not place blocks near the point [0, 0], where the robot will start.
|
||||
if abs(shifted_center[0]) < 1.0 and abs(shifted_center[1]) < 1.0:
|
||||
continue
|
||||
half_length = np.random.uniform(_MIN_BLOCK_LENGTH, _MAX_BLOCK_LENGTH) / (
|
||||
2 * math.sqrt(2))
|
||||
half_height = np.random.uniform(_MIN_BLOCK_HEIGHT, _MAX_BLOCK_HEIGHT) / 2
|
||||
box_id = env.pybullet_client.createCollisionShape(
|
||||
env.pybullet_client.GEOM_BOX,
|
||||
halfExtents=[half_length, half_length, half_height])
|
||||
env.pybullet_client.createMultiBody(
|
||||
baseMass=0,
|
||||
baseCollisionShapeIndex=box_id,
|
||||
basePosition=[shifted_center[0], shifted_center[1], half_height])
|
||||
Reference in New Issue
Block a user