text: import os
  import numpy as np
  from gym import spaces
  import pybullet as p
  
  from gym_pybullet_drones.envs.BaseAviary import DroneModel, Physics, BaseAviary
  from gym_pybullet_drones.envs.single_agent_rl.BaseSingleAgentAviary import ActionType, ObservationType, BaseSingleAgentAviary
  
  
  class FlyThruGateAviary(BaseSingleAgentAviary):
  
      ####################################################################################################
      #### Initialize the environment ####################################################################
      ####################################################################################################
      #### Arguments #####################################################################################
      #### - drone_model (DroneModel)         desired drone type (associated to an .urdf file) ###########
      #### - num_drones (int)                 desired number of drones in the aviary #####################
      #### - neighbourhood_radius (float)     used to compute the drones' adjacency matrix, in meters ####
      #### - initial_xyzs ((3,1) array)       initial XYZ position of the drones #########################
      #### - initial_rpys ((3,1) array)       initial orientations of the drones (radians) ###############
      #### - physics (Physics)                desired implementation of physics/dynamics #################
      #### - freq (int)                       the frequency (Hz) at which the physics engine advances ####
      #### - aggregate_phy_steps (int)        number of physics updates within one call of .step() #######
      #### - gui (bool)                       whether to use PyBullet's GUI ##############################
      #### - record (bool)                    whether to save a video of the simulation ##################
      #### - obstacles (bool)                 whether to add obstacles to the simulation #################
      #### - user_debug_gui (bool)            whether to draw the drones' axes and the GUI sliders #######
      #### ...
      #### ...
      ####################################################################################################
      def __init__(self,
                   drone_model: DroneModel=DroneModel.CF2X,
                   initial_xyzs=None,
                   initial_rpys=None,
                   physics: Physics=Physics.PYB,
                   freq: int=240,
                   aggregate_phy_steps: int=1,
                   gui=False,
                   record=False, 
                   obs: ObservationType=ObservationType.KIN,
                   act: ActionType=ActionType.RPM
                   ):
          super().__init__(drone_model=drone_model,
                           initial_xyzs=initial_xyzs,
                           initial_rpys=initial_rpys,
                           physics=physics,
                           freq=freq,
                           aggregate_phy_steps=aggregate_phy_steps,
                           gui=gui,
                           record=record,
                           obs=obs,
                           act=act
                           )
  
      ####################################################################################################
      #### Add obstacles to the environment from .urdf files #############################################
      ####################################################################################################
      def _addObstacles(self):
          super()._addObstacles()
          p.loadURDF(os.path.dirname(os.path.abspath(__file__))+"/../../assets/architrave.urdf",
                     [0, -1, .55],
                     p.getQuaternionFromEuler([0, 0, 0]),
                     physicsClientId=self.CLIENT
                     )
          for i in range(10): 
              p.loadURDF("cube_small.urdf",
                         [-.3, -1, .02+i*0.05],
                         p.getQuaternionFromEuler([0, 0, 0]),
                         physicsClientId=self.CLIENT
                         )
              p.loadURDF("cube_small.urdf",
                         [.3, -1, .02+i*0.05],
                         p.getQuaternionFromEuler([0,0,0]),
                         physicsClientId=self.CLIENT
                         )
  
      ####################################################################################################
      #### Compute the current reward value(s) ###########################################################
      ####################################################################################################
      #### Returns #######################################################################################
      #### - reward (..)                      the reward(s) associated to the current obs/state ##########
      ####################################################################################################
      def _computeReward(self):
          state = self._getDroneStateVector(0)
          norm_ep_time = (self.step_counter/self.SIM_FREQ) / self.EPISODE_LEN_SEC
          return -10 * np.linalg.norm(np.array([0, -2*norm_ep_time, 0.75])-state[0:3])**2
  
      ####################################################################################################
      #### Compute the current done value(s) #############################################################
      ####################################################################################################
      #### Returns #######################################################################################
      #### - done (..)                        the done value(s) associated to the current obs/state ######
      ####################################################################################################
      def _computeDone(self):
          if self.step_counter/self.SIM_FREQ > self.EPISODE_LEN_SEC:
              return True
          else:
              return False
  
      ####################################################################################################
      #### Compute the current info dict(s) ##############################################################
      ####################################################################################################
      #### Returns #######################################################################################
      #### - info (..)                        the info dict(s) associated to the current obs/state #######
      ####################################################################################################
      def _computeInfo(self):
          return {"answer": 42} #### Calculated by the Deep Thought supercomputer in 7.5M years
  
      ####################################################################################################
      #### Normalize the 20 values in the simulation state to the [-1,1] range ###########################
      ####################################################################################################
      #### Arguments #####################################################################################
      #### - state ((20,1) array)             raw simulation state #######################################
      ####################################################################################################
      #### Returns #######################################################################################
      #### - normalized state ((20,1) array)  clipped and normalized simulation state ####################
      ####################################################################################################
      def _clipAndNormalizeState(self,
                                 state
                                 ):
          MAX_LIN_VEL_XY = 3 
          MAX_LIN_VEL_Z = 1
  
          MAX_XY = MAX_LIN_VEL_XY*self.EPISODE_LEN_SEC
          MAX_Z = MAX_LIN_VEL_Z*self.EPISODE_LEN_SEC
  
          MAX_PITCH_ROLL = np.pi # Full range
          MAX_PITCH_ROLL_VEL = 6*np.pi
          MAX_YAW_VEL = 3*np.pi
  
          clipped_pos_xy = np.clip(state[0:2], -MAX_XY, MAX_XY)
          clipped_pos_z = np.clip(state[2], 0, MAX_Z)
          clipped_rp = np.clip(state[7:9], -MAX_PITCH_ROLL, MAX_PITCH_ROLL)
          clipped_vel_xy = np.clip(state[10:12], -MAX_LIN_VEL_XY, MAX_LIN_VEL_XY)
          clipped_vel_z = np.clip(state[12], -MAX_LIN_VEL_Z, MAX_LIN_VEL_Z)
  
          clipped_ang_vel_rp = np.clip(state[13:15], -MAX_PITCH_ROLL_VEL, MAX_PITCH_ROLL_VEL)
          clipped_ang_vel_y = np.clip(state[15], -MAX_YAW_VEL, MAX_YAW_VEL)
  
          if self.GUI:
              self._clipAndNormalizeStateWarning(state,
                                                 clipped_pos_xy,
                                                 clipped_pos_z,
                                                 clipped_rp,
                                                 clipped_vel_xy,
                                                 clipped_vel_z,
                                                 clipped_ang_vel_rp,
                                                 clipped_ang_vel_y
                                                 )
  
          normalized_pos_xy = clipped_pos_xy / MAX_XY
          normalized_pos_z = clipped_pos_z / MAX_Z
          normalized_rp = clipped_rp / MAX_PITCH_ROLL
          normalized_y = state[9] / np.pi # No reason to clip
          normalized_vel_xy = clipped_vel_xy / MAX_LIN_VEL_XY
          normalized_vel_z = clipped_vel_z / MAX_LIN_VEL_XY
          normalized_ang_vel_rp = clipped_ang_vel_rp / MAX_PITCH_ROLL_VEL
          normalized_ang_vel_y = clipped_ang_vel_y / MAX_YAW_VEL
  
          norm_and_clipped = np.hstack([normalized_pos_xy,
                                        normalized_pos_z,
                                        state[3:7],
                                        normalized_rp,
                                        normalized_y,
                                        normalized_vel_xy,
                                        normalized_vel_z,
                                        normalized_ang_vel_rp,
                                        normalized_ang_vel_y,
                                        state[16:20]
                                        ]).reshape(20,)
  
          return norm_and_clipped
  
      ####################################################################################################
      #### Print a warning if any of the 20 values in a state vector is out of the normalization range ###
      ####################################################################################################
      def _clipAndNormalizeStateWarning(self,
                                        state,
                                        clipped_pos_xy,
                                        clipped_pos_z,
                                        clipped_rp,
                                        clipped_vel_xy,
                                        clipped_vel_z,
                                        clipped_ang_vel_rp,
                                        clipped_ang_vel_y
                                        ):
          if not(clipped_pos_xy == np.array(state[0:2])).all():
              print("[WARNING] it", self.step_counter, "in FlyThruGateAviary._clipAndNormalizeState(), clipped xy position [{:.2f} {:.2f}]".format(state[0], state[1]))
          if not(clipped_pos_z == np.array(state[2])).all():
              print("[WARNING] it", self.step_counter, "in FlyThruGateAviary._clipAndNormalizeState(), clipped z position [{:.2f}]".format(state[2]))
          if not(clipped_rp == np.array(state[7:9])).all():
              print("[WARNING] it", self.step_counter, "in FlyThruGateAviary._clipAndNormalizeState(), clipped roll/pitch [{:.2f} {:.2f}]".format(state[7], state[8]))
          if not(clipped_vel_xy == np.array(state[10:12])).all():
              print("[WARNING] it", self.step_counter, "in FlyThruGateAviary._clipAndNormalizeState(), clipped xy velocity [{:.2f} {:.2f}]".format(state[10], state[11]))
          if not(clipped_vel_z == np.array(state[12])).all():
              print("[WARNING] it", self.step_counter, "in FlyThruGateAviary._clipAndNormalizeState(), clipped z velocity [{:.2f}]".format(state[12]))
          if not(clipped_ang_vel_rp == np.array(state[13:15])).all():
              print("[WARNING] it", self.step_counter, "in FlyThruGateAviary._clipAndNormalizeState(), clipped angular velocity [{:.2f} {:.2f}]".format(state[13], state[14]))
          if not(clipped_ang_vel_y == np.array(state[15])):
              print("[WARNING] it", self.step_counter, "in FlyThruGateAviary._clipAndNormalizeState(), clipped angular velocity [{:.2f}]".format(state[15]))
  