コード例 #1
0
    def _config_pathfinder(self, config: Configuration) -> None:
        if "navmesh" in config.sim_cfg.scene.filepaths:
            navmesh_filenname = config.sim_cfg.scene.filepaths["navmesh"]
        else:
            scene_basename = osp.basename(config.sim_cfg.scene.id)
            # "mesh.ply" is identified as a replica model, whose navmesh
            # is named as "mesh_semantic.navmesh" and is placed in the
            # subfolder called "habitat" (a level deeper than the "mesh.ply")
            if scene_basename == "mesh.ply":
                scene_dir = osp.dirname(config.sim_cfg.scene.id)
                navmesh_filenname = osp.join(
                    scene_dir, "habitat", "mesh_semantic.navmesh"
                )
            else:
                navmesh_filenname = (
                    osp.splitext(config.sim_cfg.scene.id)[0] + ".navmesh"
                )

        self.pathfinder = PathFinder()
        if osp.exists(navmesh_filenname):
            self.pathfinder.load_nav_mesh(navmesh_filenname)
            logger.info(f"Loaded navmesh {navmesh_filenname}")
        else:
            logger.warning(
                f"Could not find navmesh {navmesh_filenname}, no collision checking will be done"
            )

        agent_legacy_config = AgentConfiguration()
        default_agent_config = config.agents[config.sim_cfg.default_agent_id]
        if not np.isclose(
            agent_legacy_config.radius, default_agent_config.radius
        ) or not np.isclose(agent_legacy_config.height, default_agent_config.height):
            logger.info(
                f"Recomputing navmesh for agent's height {default_agent_config.height} and radius"
                f" {default_agent_config.radius}."
            )
            navmesh_settings = NavMeshSettings()
            navmesh_settings.set_defaults()
            navmesh_settings.agent_radius = default_agent_config.radius
            navmesh_settings.agent_height = default_agent_config.height
            self.recompute_navmesh(self.pathfinder, navmesh_settings)

        self.pathfinder.seed(config.sim_cfg.random_seed)
コード例 #2
0
ファイル: simulator.py プロジェクト: cihuang123/habitat-sim
class Simulator(SimulatorBackend):
    r"""The core class of habitat-sim

    :property config: configuration for the simulator

    The simulator ties together the backend, the agent, controls functions,
    NavMesh collision checking/pathfinding, attribute template management,
    object manipulation, and physics simulation.
    """

    config: Configuration
    agents: List[Agent] = attr.ib(factory=list, init=False)
    _num_total_frames: int = attr.ib(default=0, init=False)
    _default_agent_id: int = attr.ib(default=0, init=False)
    __sensors: List[Dict[str, "Sensor"]] = attr.ib(factory=list, init=False)
    _initialized: bool = attr.ib(default=False, init=False)
    _previous_step_time: float = attr.ib(
        default=0.0, init=False
    )  # track the compute time of each step
    __last_state: Dict[int, AgentState] = attr.ib(factory=dict, init=False)

    @staticmethod
    def _sanitize_config(config: Configuration) -> None:
        if len(config.agents) == 0:
            raise RuntimeError(
                "Config has not agents specified.  Must specify at least 1 agent"
            )

        config.sim_cfg.create_renderer = any(
            map(lambda cfg: len(cfg.sensor_specifications) > 0, config.agents)
        )
        config.sim_cfg.load_semantic_mesh = any(
            map(
                lambda cfg: any(
                    map(
                        lambda sens_spec: sens_spec.sensor_type == SensorType.SEMANTIC,
                        cfg.sensor_specifications,
                    )
                ),
                config.agents,
            )
        )

        config.sim_cfg.requires_textures = any(
            map(
                lambda cfg: any(
                    map(
                        lambda sens_spec: sens_spec.sensor_type == SensorType.COLOR,
                        cfg.sensor_specifications,
                    )
                ),
                config.agents,
            )
        )

    def __attrs_post_init__(self) -> None:
        self._sanitize_config(self.config)
        self.__set_from_config(self.config)

    def close(self) -> None:
        for agent_sensorsuite in self.__sensors:
            for sensor in agent_sensorsuite.values():
                sensor.close()
                del sensor

        self.__sensors = []

        for agent in self.agents:
            agent.close()
            del agent

        self.agents = []

        self.__last_state.clear()

        super().close()

    def __enter__(self) -> "Simulator":
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    def seed(self, new_seed: int) -> None:
        super().seed(new_seed)
        self.pathfinder.seed(new_seed)

    @overload
    def reset(self, agent_ids: List[int]) -> Dict[int, ObservationDict]:
        ...

    @overload
    def reset(self, agent_ids: Optional[int] = None) -> ObservationDict:
        ...

    def reset(
        self, agent_ids: Union[Optional[int], List[int]] = None
    ) -> Union[ObservationDict, Dict[int, ObservationDict],]:
        super().reset()
        for i in range(len(self.agents)):
            self.reset_agent(i)

        if agent_ids is None:
            agent_ids = [self._default_agent_id]
            return_single = True
        else:
            agent_ids = cast(List[int], agent_ids)
            return_single = False
        obs = self.get_sensor_observations(agent_ids=agent_ids)
        if return_single:
            return obs[agent_ids[0]]
        return obs

    def reset_agent(self, agent_id: int) -> None:
        agent = self.get_agent(agent_id)
        initial_agent_state = agent.initial_state
        if initial_agent_state is None:
            raise RuntimeError("reset called before agent was given an initial state")

        self.initialize_agent(agent_id, initial_agent_state)

    def _config_backend(self, config: Configuration) -> None:
        if not self._initialized:
            super().__init__(config.sim_cfg, config.metadata_mediator)
            self._initialized = True
        else:
            super().reconfigure(config.sim_cfg)

    def _config_agents(self, config: Configuration) -> None:
        self.agents = [
            Agent(self.get_active_scene_graph().get_root_node().create_child(), cfg)
            for cfg in config.agents
        ]

    def _config_pathfinder(self, config: Configuration) -> None:
        scene_basename = osp.basename(config.sim_cfg.scene_id)
        # "mesh.ply" is identified as a replica model, whose navmesh
        # is named as "mesh_semantic.navmesh" and is placed in the
        # subfolder called "habitat" (a level deeper than the "mesh.ply")
        if scene_basename == "mesh.ply":
            scene_dir = osp.dirname(config.sim_cfg.scene_id)
            navmesh_filenname = osp.join(scene_dir, "habitat", "mesh_semantic.navmesh")
        else:
            navmesh_filenname = osp.splitext(config.sim_cfg.scene_id)[0] + ".navmesh"

        self.pathfinder = PathFinder()
        if osp.exists(navmesh_filenname):
            self.pathfinder.load_nav_mesh(navmesh_filenname)
            logger.info(f"Loaded navmesh {navmesh_filenname}")
        else:
            logger.warning(
                f"Could not find navmesh {navmesh_filenname}, no collision checking will be done"
            )

        agent_legacy_config = AgentConfiguration()
        default_agent_config = config.agents[config.sim_cfg.default_agent_id]
        if not np.isclose(
            agent_legacy_config.radius, default_agent_config.radius
        ) or not np.isclose(agent_legacy_config.height, default_agent_config.height):
            logger.info(
                f"Recomputing navmesh for agent's height {default_agent_config.height} and radius"
                f" {default_agent_config.radius}."
            )
            navmesh_settings = NavMeshSettings()
            navmesh_settings.set_defaults()
            navmesh_settings.agent_radius = default_agent_config.radius
            navmesh_settings.agent_height = default_agent_config.height
            self.recompute_navmesh(self.pathfinder, navmesh_settings)

        self.pathfinder.seed(config.sim_cfg.random_seed)

    def reconfigure(self, config: Configuration) -> None:
        self._sanitize_config(config)

        if self.config != config:
            self.__set_from_config(config)
            self.config = config

    def __set_from_config(self, config: Configuration) -> None:
        self._config_backend(config)
        self._config_agents(config)
        self._config_pathfinder(config)
        self.frustum_culling = config.sim_cfg.frustum_culling

        for i in range(len(self.agents)):
            self.agents[i].controls.move_filter_fn = self.step_filter

        self._default_agent_id = config.sim_cfg.default_agent_id

        self.__sensors: List[Dict[str, Sensor]] = [
            dict() for i in range(len(config.agents))
        ]
        self.__last_state = dict()
        for agent_id, agent_cfg in enumerate(config.agents):
            for spec in agent_cfg.sensor_specifications:
                self._update_simulator_sensors(spec.uuid, agent_id=agent_id)
            self.initialize_agent(agent_id)

    def _update_simulator_sensors(self, uuid: str, agent_id: int) -> None:
        self.__sensors[agent_id][uuid] = Sensor(
            sim=self, agent=self.get_agent(agent_id), sensor_id=uuid
        )

    def add_sensor(
        self, sensor_spec: SensorSpec, agent_id: Optional[int] = None
    ) -> None:
        if (
            (
                not self.config.sim_cfg.load_semantic_mesh
                and sensor_spec.sensor_type == SensorType.SEMANTIC
            )
            or (
                not self.config.sim_cfg.requires_textures
                and sensor_spec.sensor_type == SensorType.COLOR
            )
            or (
                not self.config.sim_cfg.create_renderer
                and sensor_spec.sensor_type == SensorType.DEPTH
            )
        ):
            sensor_type = sensor_spec.sensor_type
            raise ValueError(
                f"""Data for {sensor_type} sensor was not loaded during Simulator init.
                    Cannot dynamically add a {sensor_type} sensor unless one already exists.
                    """
            )
        if agent_id is None:
            agent_id = self._default_agent_id
        agent = self.get_agent(agent_id=agent_id)
        agent._add_sensor(sensor_spec)
        self._update_simulator_sensors(sensor_spec.uuid, agent_id=agent_id)

    def get_agent(self, agent_id: int) -> Agent:
        return self.agents[agent_id]

    def initialize_agent(
        self, agent_id: int, initial_state: Optional[AgentState] = None
    ) -> Agent:
        agent = self.get_agent(agent_id=agent_id)
        if initial_state is None:
            initial_state = AgentState()
            if self.pathfinder.is_loaded:
                initial_state.position = self.pathfinder.get_random_navigable_point()
                initial_state.rotation = quat_from_angle_axis(
                    self.random.uniform_float(0, 2.0 * np.pi), np.array([0, 1, 0])
                )

        agent.set_state(initial_state, is_initial=True)
        self.__last_state[agent_id] = agent.state
        return agent

    @overload
    def get_sensor_observations(self, agent_ids: int = 0) -> ObservationDict:
        ...

    @overload
    def get_sensor_observations(
        self, agent_ids: List[int]
    ) -> Dict[int, ObservationDict]:
        ...

    def get_sensor_observations(
        self, agent_ids: Union[int, List[int]] = 0
    ) -> Union[ObservationDict, Dict[int, ObservationDict],]:
        if isinstance(agent_ids, int):
            agent_ids = [agent_ids]
            return_single = True
        else:
            return_single = False

        for agent_id in agent_ids:
            agent_sensorsuite = self.__sensors[agent_id]
            for _sensor_uuid, sensor in agent_sensorsuite.items():
                sensor.draw_observation()

        # As backport. All Dicts are ordered in Python >= 3.7
        observations: Dict[int, ObservationDict] = OrderedDict()
        for agent_id in agent_ids:
            agent_observations: ObservationDict = {}
            for sensor_uuid, sensor in self.__sensors[agent_id].items():
                agent_observations[sensor_uuid] = sensor.get_observation()
            observations[agent_id] = agent_observations
        if return_single:
            return next(iter(observations.values()))
        return observations

    @property
    def _default_agent(self) -> Agent:
        # TODO Deprecate and remove
        return self.get_agent(agent_id=self._default_agent_id)

    @property
    def _last_state(self) -> AgentState:
        # TODO Deprecate and remove
        return self.__last_state[self._default_agent_id]

    @_last_state.setter
    def _last_state(self, state: AgentState) -> None:
        # TODO Deprecate and remove
        self.__last_state[self._default_agent_id] = state

    @property
    def _sensors(self) -> Dict[str, "Sensor"]:
        # TODO Deprecate and remove
        return self.__sensors[self._default_agent_id]

    def last_state(self, agent_id: Optional[int] = None) -> AgentState:
        if agent_id is None:
            agent_id = self._default_agent_id
        return self.__last_state[agent_id]

    @overload
    def step(self, action: Union[str, int], dt: float = 1.0 / 60.0) -> ObservationDict:
        ...

    @overload
    def step(
        self, action: MutableMapping_T[int, Union[str, int]], dt: float = 1.0 / 60.0
    ) -> Dict[int, ObservationDict]:
        ...

    def step(
        self,
        action: Union[str, int, MutableMapping_T[int, Union[str, int]]],
        dt: float = 1.0 / 60.0,
    ) -> Union[ObservationDict, Dict[int, ObservationDict],]:
        self._num_total_frames += 1
        if isinstance(action, MutableMapping):
            return_single = False
        else:
            action = cast(Dict[int, Union[str, int]], {self._default_agent_id: action})
            return_single = True
        collided_dict: Dict[int, bool] = {}
        for agent_id, agent_act in action.items():
            agent = self.get_agent(agent_id)
            collided_dict[agent_id] = agent.act(agent_act)
            self.__last_state[agent_id] = agent.get_state()

        # step physics by dt
        step_start_Time = time.time()
        super().step_world(dt)
        self._previous_step_time = time.time() - step_start_Time

        multi_observations = self.get_sensor_observations(agent_ids=list(action.keys()))
        for agent_id, agent_observation in multi_observations.items():
            agent_observation["collided"] = collided_dict[agent_id]
        if return_single:
            return multi_observations[self._default_agent_id]
        return multi_observations

    def make_greedy_follower(
        self,
        agent_id: Optional[int] = None,
        goal_radius: float = None,
        *,
        stop_key: Optional[Any] = None,
        forward_key: Optional[Any] = None,
        left_key: Optional[Any] = None,
        right_key: Optional[Any] = None,
        fix_thrashing: bool = True,
        thrashing_threshold: int = 16,
    ):
        if agent_id is None:
            agent_id = self._default_agent_id
        return GreedyGeodesicFollower(
            self.pathfinder,
            self.get_agent(agent_id),
            goal_radius,
            stop_key=stop_key,
            forward_key=forward_key,
            left_key=left_key,
            right_key=right_key,
            fix_thrashing=fix_thrashing,
            thrashing_threshold=thrashing_threshold,
        )

    def step_filter(self, start_pos: Vector3, end_pos: Vector3) -> Vector3:
        r"""Computes a valid navigable end point given a target translation on the NavMesh.
        Uses the configured sliding flag.

        :param start_pos: The valid initial position of a translation.
        :param end_pos: The target end position of a translation.
        """
        if self.pathfinder.is_loaded:
            if self.config.sim_cfg.allow_sliding:
                end_pos = self.pathfinder.try_step(start_pos, end_pos)
            else:
                end_pos = self.pathfinder.try_step_no_sliding(start_pos, end_pos)

        return end_pos

    def __del__(self) -> None:
        self.close()

    def step_physics(self, dt: float, scene_id: int = 0) -> None:
        self.step_world(dt)
コード例 #3
0
class Simulator(SimulatorBackend):
    r"""The core class of habitat-sim

    :property config: configuration for the simulator

    The simulator ties together the backend, the agent, controls functions,
    NavMesh collision checking/pathfinding, attribute template management,
    object manipulation, and physics simulation.
    """

    config: Configuration
    agents: List[Agent] = attr.ib(factory=list, init=False)
    _num_total_frames: int = attr.ib(default=0, init=False)
    _default_agent: Agent = attr.ib(init=False, default=None)
    _sensors: Dict = attr.ib(factory=dict, init=False)
    _initialized: bool = attr.ib(default=False, init=False)
    _previous_step_time: float = attr.ib(
        default=0.0, init=False)  # track the compute time of each step

    @staticmethod
    def _sanitize_config(config: Configuration) -> None:
        if not len(config.agents) > 0:
            raise RuntimeError(
                "Config has not agents specified.  Must specify at least 1 agent"
            )

        config.sim_cfg.create_renderer = any(
            map(lambda cfg: len(cfg.sensor_specifications) > 0, config.agents))
        config.sim_cfg.load_semantic_mesh = any(
            map(
                lambda cfg: any(
                    map(
                        lambda sens_spec: sens_spec.sensor_type == SensorType.
                        SEMANTIC,
                        cfg.sensor_specifications,
                    )),
                config.agents,
            ))

        config.sim_cfg.requires_textures = any(
            map(
                lambda cfg: any(
                    map(
                        lambda sens_spec: sens_spec.sensor_type == SensorType.
                        COLOR,
                        cfg.sensor_specifications,
                    )),
                config.agents,
            ))

    def __attrs_post_init__(self) -> None:
        self._sanitize_config(self.config)
        self.__set_from_config(self.config)

    def close(self) -> None:
        for sensor in self._sensors.values():
            sensor.close()
            del sensor

        self._sensors = {}

        for agent in self.agents:
            agent.close()
            del agent

        self.agents = []

        del self._default_agent
        self._default_agent = None

        self.config = None

        super().close()

    def __enter__(self) -> "Simulator":
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    def seed(self, new_seed: int) -> None:
        super().seed(new_seed)
        self.pathfinder.seed(new_seed)

    def reset(self) -> Dict[str, ndarray]:
        super().reset()
        for i in range(len(self.agents)):
            self.reset_agent(i)

        return self.get_sensor_observations()

    def reset_agent(self, agent_id: int) -> None:
        agent = self.get_agent(agent_id)
        initial_agent_state = agent.initial_state
        if initial_agent_state is None:
            raise RuntimeError(
                "reset called before agent was given an initial state")

        self.initialize_agent(agent_id, initial_agent_state)

    def _config_backend(self, config: Configuration) -> None:
        if not self._initialized:
            super().__init__(config.sim_cfg)
            self._initialized = True
        else:
            super().reconfigure(config.sim_cfg)

    def _config_agents(self, config: Configuration) -> None:
        self.agents = [
            Agent(self.get_active_scene_graph().get_root_node().create_child(),
                  cfg) for cfg in config.agents
        ]

    def _config_pathfinder(self, config: Configuration) -> None:
        if "navmesh" in config.sim_cfg.scene.filepaths:
            navmesh_filenname = config.sim_cfg.scene.filepaths["navmesh"]
        else:
            scene_basename = osp.basename(config.sim_cfg.scene.id)
            # "mesh.ply" is identified as a replica model, whose navmesh
            # is named as "mesh_semantic.navmesh" and is placed in the
            # subfolder called "habitat" (a level deeper than the "mesh.ply")
            if scene_basename == "mesh.ply":
                scene_dir = osp.dirname(config.sim_cfg.scene.id)
                navmesh_filenname = osp.join(scene_dir, "habitat",
                                             "mesh_semantic.navmesh")
            else:
                navmesh_filenname = (osp.splitext(config.sim_cfg.scene.id)[0] +
                                     ".navmesh")

        self.pathfinder = PathFinder()
        if osp.exists(navmesh_filenname):
            self.pathfinder.load_nav_mesh(navmesh_filenname)
            logger.info(f"Loaded navmesh {navmesh_filenname}")
        else:
            logger.warning(
                f"Could not find navmesh {navmesh_filenname}, no collision checking will be done"
            )

        agent_legacy_config = AgentConfiguration()
        default_agent_config = config.agents[config.sim_cfg.default_agent_id]
        if not np.isclose(agent_legacy_config.radius,
                          default_agent_config.radius) or not np.isclose(
                              agent_legacy_config.height,
                              default_agent_config.height):
            logger.info(
                f"Recomputing navmesh for agent's height {default_agent_config.height} and radius"
                f" {default_agent_config.radius}.")
            navmesh_settings = NavMeshSettings()
            navmesh_settings.set_defaults()
            navmesh_settings.agent_radius = default_agent_config.radius
            navmesh_settings.agent_height = default_agent_config.height
            self.recompute_navmesh(self.pathfinder, navmesh_settings)

        self.pathfinder.seed(config.sim_cfg.random_seed)

    def reconfigure(self, config: Configuration) -> None:
        self._sanitize_config(config)

        if self.config != config:
            self.__set_from_config(config)
            self.config = config

    def __set_from_config(self, config: Configuration):
        self._config_backend(config)
        self._config_agents(config)
        self._config_pathfinder(config)
        self.frustum_culling = config.sim_cfg.frustum_culling

        for i in range(len(self.agents)):
            self.agents[i].controls.move_filter_fn = self.step_filter

        self._default_agent = self.get_agent(config.sim_cfg.default_agent_id)

        agent_cfg = config.agents[config.sim_cfg.default_agent_id]
        self._sensors = {}
        for spec in agent_cfg.sensor_specifications:
            self._sensors[spec.uuid] = Sensor(sim=self,
                                              agent=self._default_agent,
                                              sensor_id=spec.uuid)

        for i in range(len(self.agents)):
            self.initialize_agent(i)

    def get_agent(self, agent_id: int) -> Agent:
        return self.agents[agent_id]

    def initialize_agent(self,
                         agent_id: int,
                         initial_state: Optional[AgentState] = None) -> Agent:
        agent = self.get_agent(agent_id=agent_id)
        if initial_state is None:
            initial_state = AgentState()
            if self.pathfinder.is_loaded:
                initial_state.position = self.pathfinder.get_random_navigable_point(
                )
                initial_state.rotation = quat_from_angle_axis(
                    self.random.uniform_float(0, 2.0 * np.pi),
                    np.array([0, 1, 0]))

        agent.set_state(initial_state, is_initial=True)
        self._last_state = agent.state
        return agent

    def get_sensor_observations(self) -> Dict[str, Union[ndarray, "Tensor"]]:
        for _, sensor in self._sensors.items():
            sensor.draw_observation()

        observations = {}
        for sensor_uuid, sensor in self._sensors.items():
            observations[sensor_uuid] = sensor.get_observation()

        return observations

    def last_state(self):
        return self._last_state

    def step(
            self,
            action: str,
            dt: float = 1.0 / 60.0
    ) -> Dict[str, Union[bool, ndarray, "Tensor"]]:
        self._num_total_frames += 1
        collided = self._default_agent.act(action)
        self._last_state = self._default_agent.get_state()

        # step physics by dt
        step_start_Time = time.time()
        super().step_world(dt)
        self._previous_step_time = time.time() - step_start_Time

        observations = self.get_sensor_observations()
        # Whether or not the action taken resulted in a collision
        observations["collided"] = collided

        return observations

    def make_greedy_follower(
        self,
        agent_id: int = 0,
        goal_radius: float = None,
        *,
        stop_key: Optional[Any] = None,
        forward_key: Optional[Any] = None,
        left_key: Optional[Any] = None,
        right_key: Optional[Any] = None,
        fix_thrashing: bool = True,
        thrashing_threshold: int = 16,
    ):
        return GreedyGeodesicFollower(
            self.pathfinder,
            self.get_agent(agent_id),
            goal_radius,
            stop_key=stop_key,
            forward_key=forward_key,
            left_key=left_key,
            right_key=right_key,
            fix_thrashing=fix_thrashing,
            thrashing_threshold=thrashing_threshold,
        )

    def step_filter(self, start_pos: Vector3, end_pos: Vector3) -> Vector3:
        r"""Computes a valid navigable end point given a target translation on the NavMesh.
        Uses the configured sliding flag.

        :param start_pos: The valid initial position of a translation.
        :param end_pos: The target end position of a translation.
        """
        if self.pathfinder.is_loaded:
            if self.config.sim_cfg.allow_sliding:
                end_pos = self.pathfinder.try_step(start_pos, end_pos)
            else:
                end_pos = self.pathfinder.try_step_no_sliding(
                    start_pos, end_pos)

        return end_pos

    def __del__(self) -> None:
        self.close()

    def step_physics(self, dt: float, scene_id: int = 0) -> None:
        self.step_world(dt)
コード例 #4
0
class Simulator:
    r"""The core class of habitat-sim

    :property config: configuration for the simulator

    The simulator ties together the backend, the agent, controls functions,
    and collision checking/pathfinding.
    """

    config: Configuration
    agents: List[Agent] = attr.ib(factory=list, init=False)
    pathfinder: PathFinder = attr.ib(default=None, init=False)
    _sim: SimulatorBackend = attr.ib(default=None, init=False)
    _num_total_frames: int = attr.ib(default=0, init=False)
    _default_agent: Agent = attr.ib(init=False, default=None)
    _sensors: Dict = attr.ib(factory=dict, init=False)
    _previous_step_time = 0.0  # track the compute time of each step

    def __attrs_post_init__(self):
        config = self.config
        self.config = None
        self.reconfigure(config)

    def close(self):
        for sensor in self._sensors.values():
            sensor.close()
            del sensor

        self._sensors = {}

        for agent in self.agents:
            agent.close()
            del agent

        self.agents = []

        del self._default_agent
        self._default_agent = None

        del self._sim
        self._sim = None

        self.config = None

    def seed(self, new_seed):
        self._sim.seed(new_seed)
        self.pathfinder.seed(new_seed)

    def reset(self):
        self._sim.reset()
        for i in range(len(self.agents)):
            self.reset_agent(i)

        return self.get_sensor_observations()

    def reset_agent(self, agent_id):
        agent = self.get_agent(agent_id)
        initial_agent_state = agent.initial_state
        if initial_agent_state is None:
            raise RuntimeError(
                "reset called before agent was given an initial state")

        self.initialize_agent(agent_id, initial_agent_state)

    def _config_backend(self, config: Configuration):
        if self._sim is None:
            self._sim = SimulatorBackend(config.sim_cfg)
        else:
            self._sim.reconfigure(config.sim_cfg)

    def _config_agents(self, config: Configuration):
        if self.config is not None and self.config.agents == config.agents:
            return

        self.agents = [
            Agent(
                self._sim.get_active_scene_graph().get_root_node().
                create_child(), cfg) for cfg in config.agents
        ]

    def _config_pathfinder(self, config: Configuration):
        if "navmesh" in config.sim_cfg.scene.filepaths:
            navmesh_filenname = config.sim_cfg.scene.filepaths["navmesh"]
        else:
            scene_basename = osp.basename(config.sim_cfg.scene.id)
            # "mesh.ply" is identified as a replica model, whose navmesh
            # is named as "mesh_semantic.navmesh" and is placed in the
            # subfolder called "habitat" (a level deeper than the "mesh.ply")
            if scene_basename == "mesh.ply":
                scene_dir = osp.dirname(config.sim_cfg.scene.id)
                navmesh_filenname = osp.join(scene_dir, "habitat",
                                             "mesh_semantic.navmesh")
            else:
                navmesh_filenname = (osp.splitext(config.sim_cfg.scene.id)[0] +
                                     ".navmesh")

        self.pathfinder = PathFinder()
        if osp.exists(navmesh_filenname):
            self.pathfinder.load_nav_mesh(navmesh_filenname)
            logger.info(f"Loaded navmesh {navmesh_filenname}")
        else:
            logger.warning(
                f"Could not find navmesh {navmesh_filenname}, no collision checking will be done"
            )

        agent_legacy_config = AgentConfiguration()
        default_agent_config = config.agents[config.sim_cfg.default_agent_id]
        if not np.isclose(agent_legacy_config.radius,
                          default_agent_config.radius) or not np.isclose(
                              agent_legacy_config.height,
                              default_agent_config.height):
            logger.info(
                f"Recomputing navmesh for agent's height {default_agent_config.height} and radius"
                f" {default_agent_config.radius}.")
            navmesh_settings = NavMeshSettings()
            navmesh_settings.set_defaults()
            navmesh_settings.agent_radius = default_agent_config.radius
            navmesh_settings.agent_height = default_agent_config.height
            self.recompute_navmesh(self.pathfinder, navmesh_settings)

    def reconfigure(self, config: Configuration):
        assert len(config.agents) > 0

        config.sim_cfg.create_renderer = any(
            map(lambda cfg: len(cfg.sensor_specifications) > 0, config.agents))
        config.sim_cfg.load_semantic_mesh = any(
            map(
                lambda cfg: any(
                    map(
                        lambda sens_spec: sens_spec.sensor_type == SensorType.
                        SEMANTIC,
                        cfg.sensor_specifications,
                    )),
                config.agents,
            ))

        if self.config == config:
            return

        # NB: Configure backend last as this gives more time for python's GC
        # to delete any previous instances of the simulator
        # TODO: can't do the above, sorry -- the Agent constructor needs access
        # to self._sim.get_active_scene_graph()
        self._config_backend(config)
        self._config_agents(config)
        self._config_pathfinder(config)
        self._sim.frustum_culling = config.sim_cfg.frustum_culling
        for i in range(len(self.agents)):
            self.agents[i].controls.move_filter_fn = self._step_filter

        self._default_agent = self.get_agent(config.sim_cfg.default_agent_id)

        agent_cfg = config.agents[config.sim_cfg.default_agent_id]
        self._sensors = {}
        for spec in agent_cfg.sensor_specifications:
            self._sensors[spec.uuid] = Sensor(sim=self._sim,
                                              agent=self._default_agent,
                                              sensor_id=spec.uuid)

        for i in range(len(self.agents)):
            self.initialize_agent(i)

        self.config = config

    def get_agent(self, agent_id):
        return self.agents[agent_id]

    def initialize_agent(self, agent_id, initial_state=None):
        agent = self.get_agent(agent_id=agent_id)
        if initial_state is None:
            initial_state = AgentState()
            if self.pathfinder.is_loaded:
                initial_state.position = self.pathfinder.get_random_navigable_point(
                )
                initial_state.rotation = quat_from_angle_axis(
                    np.random.uniform(0, 2.0 * np.pi), np.array([0, 1, 0]))

        agent.set_state(initial_state, is_initial=True)
        self._last_state = agent.state
        return agent

    def sample_random_agent_state(self, state_to_return):
        return self._sim.sample_random_agent_state(state_to_return)

    @property
    def semantic_scene(self):
        r"""The semantic scene graph

        .. note-warning::

            Not avaliable for all datasets
        """

        return self._sim.semantic_scene

    def get_sensor_observations(self):
        for _, sensor in self._sensors.items():
            sensor.draw_observation()

        observations = {}
        for sensor_uuid, sensor in self._sensors.items():
            observations[sensor_uuid] = sensor.get_observation()

        return observations

    def last_state(self):
        return self._last_state

    def step(self, action, dt=1.0 / 60.0):
        self._num_total_frames += 1
        collided = self._default_agent.act(action)
        self._last_state = self._default_agent.get_state()

        # step physics by dt
        step_start_Time = time.time()
        self._sim.step_world(dt)
        _previous_step_time = time.time() - step_start_Time

        observations = self.get_sensor_observations()
        # Whether or not the action taken resulted in a collision
        observations["collided"] = collided

        return observations

    def make_greedy_follower(self,
                             agent_id: int = 0,
                             goal_radius: float = None):
        return GreedyGeodesicFollower(self.pathfinder,
                                      self.get_agent(agent_id), goal_radius)

    def _step_filter(self, start_pos, end_pos):
        if self.pathfinder.is_loaded:
            if self.config.sim_cfg.allow_sliding:
                end_pos = self.pathfinder.try_step(start_pos, end_pos)
            else:
                end_pos = self.pathfinder.try_step_no_sliding(
                    start_pos, end_pos)

        return end_pos

    def __del__(self):
        self.close()

    # --- object template functions ---
    def get_physics_object_library_size(self):
        return self._sim.get_physics_object_library_size()

    def get_object_template(self, template_id):
        return self._sim.get_object_template(template_id)

    def load_object_configs(self, path):
        return self._sim.load_object_configs(path)

    def load_object_template(self, object_template, object_template_handle):
        return self._sim.load_object_template(object_template,
                                              object_template_handle)

    def get_object_initialization_template(self, object_id, scene_id=0):
        return self._sim.get_object_initialization_template(
            object_id, scene_id)

    # --- physics functions ---
    def add_object(
        self,
        object_lib_index,
        attachment_node=None,
        light_setup_key=DEFAULT_LIGHTING_KEY,
    ):
        return self._sim.add_object(object_lib_index, attachment_node,
                                    light_setup_key)

    def remove_object(self,
                      object_id,
                      delete_object_node=True,
                      delete_visual_node=True):
        self._sim.remove_object(object_id, delete_object_node,
                                delete_visual_node)

    def get_existing_object_ids(self, scene_id=0):
        return self._sim.get_existing_object_ids(scene_id)

    def get_object_motion_type(self, object_id, scene_id=0):
        return self._sim.get_object_motion_type(object_id, scene_id)

    def set_object_motion_type(self, motion_type, object_id, scene_id=0):
        return self._sim.set_object_motion_type(motion_type, object_id,
                                                scene_id)

    def set_transformation(self, transform, object_id, scene_id=0):
        self._sim.set_transformation(transform, object_id, scene_id)

    def get_transformation(self, object_id, scene_id=0):
        return self._sim.get_transformation(object_id, scene_id)

    def set_translation(self, translation, object_id, scene_id=0):
        self._sim.set_translation(translation, object_id, scene_id)

    def get_translation(self, object_id, scene_id=0):
        return self._sim.get_translation(object_id, scene_id)

    def set_rotation(self, rotation, object_id, scene_id=0):
        self._sim.set_rotation(rotation, object_id, scene_id)

    def get_rotation(self, object_id, scene_id=0):
        return self._sim.get_rotation(object_id, scene_id)

    def get_object_velocity_control(self, object_id, scene_id=0):
        return self._sim.get_object_velocity_control(object_id, scene_id)

    def set_linear_velocity(self, lin_vel, object_id, scene_id=0):
        self._sim.set_linear_velocity(lin_vel, object_id, scene_id)

    def get_linear_velocity(self, object_id, scene_id=0):
        return self._sim.get_linear_velocity(object_id, scene_id)

    def set_angular_velocity(self, ang_vel, object_id, scene_id=0):
        self._sim.set_angular_velocity(ang_vel, object_id, scene_id)

    def get_angular_velocity(self, object_id, scene_id=0):
        return self._sim.get_angular_velocity(object_id, scene_id)

    def apply_force(self, force, relative_position, object_id, scene_id=0):
        self._sim.apply_force(force, relative_position, object_id, scene_id)

    def apply_torque(self, torque, object_id, scene_id=0):
        self._sim.apply_torque(torque, object_id, scene_id)

    def contact_test(self, object_id, scene_id=0):
        return self._sim.contact_test(object_id, scene_id)

    def step_physics(self, dt, scene_id=0):
        self._sim.step_world(dt)

    def get_world_time(self, scene_id=0):
        return self._sim.get_world_time()

    def get_gravity(self, scene_id=0):
        return self._sim.get_gravity(scene_id)

    def set_gravity(self, gravity, scene_id=0):
        return self._sim.set_gravity(gravity, scene_id)

    def recompute_navmesh(self,
                          pathfinder,
                          navmesh_settings,
                          include_static_objects=False):
        return self._sim.recompute_navmesh(pathfinder, navmesh_settings,
                                           include_static_objects)

    # --- lighting functions ---
    def get_light_setup(self, key=DEFAULT_LIGHTING_KEY):
        return self._sim.get_light_setup(key)

    def set_light_setup(self, light_setup, key=DEFAULT_LIGHTING_KEY):
        self._sim.set_light_setup(light_setup, key)

    def set_object_light_setup(self, object_id, light_setup_key, scene_id=0):
        self._sim.set_object_light_setup(object_id, light_setup_key, scene_id)