miniworld-maze 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of miniworld-maze might be problematic. Click here for more details.
- miniworld_maze/__init__.py +37 -0
- miniworld_maze/core/__init__.py +8 -0
- miniworld_maze/core/constants.py +130 -0
- miniworld_maze/core/miniworld_gymnasium/README.md +1 -0
- miniworld_maze/core/miniworld_gymnasium/__init__.py +4 -0
- miniworld_maze/core/miniworld_gymnasium/base_env.py +52 -0
- miniworld_maze/core/miniworld_gymnasium/entities/__init__.py +7 -0
- miniworld_maze/core/miniworld_gymnasium/entities/agent.py +117 -0
- miniworld_maze/core/miniworld_gymnasium/entities/base_entity.py +135 -0
- miniworld_maze/core/miniworld_gymnasium/entities/objects.py +116 -0
- miniworld_maze/core/miniworld_gymnasium/entity_manager.py +358 -0
- miniworld_maze/core/miniworld_gymnasium/envs/__init__.py +3 -0
- miniworld_maze/core/miniworld_gymnasium/math.py +83 -0
- miniworld_maze/core/miniworld_gymnasium/objmesh.py +294 -0
- miniworld_maze/core/miniworld_gymnasium/occlusion_queries.py +97 -0
- miniworld_maze/core/miniworld_gymnasium/opengl.py +21 -0
- miniworld_maze/core/miniworld_gymnasium/params.py +128 -0
- miniworld_maze/core/miniworld_gymnasium/random.py +105 -0
- miniworld_maze/core/miniworld_gymnasium/rendering/__init__.py +7 -0
- miniworld_maze/core/miniworld_gymnasium/rendering/drawing.py +77 -0
- miniworld_maze/core/miniworld_gymnasium/rendering/framebuffer.py +260 -0
- miniworld_maze/core/miniworld_gymnasium/rendering/texture.py +101 -0
- miniworld_maze/core/miniworld_gymnasium/rendering_engine.py +293 -0
- miniworld_maze/core/miniworld_gymnasium/room.py +330 -0
- miniworld_maze/core/miniworld_gymnasium/texture_utils.py +64 -0
- miniworld_maze/core/miniworld_gymnasium/textures/airduct_grate_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/asphalt_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/beanpaste_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/beige_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/black_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/blueberry_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/brick_wall_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/cardboard_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/cardboard_2.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/cardboard_3.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/cardboard_4.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/ceiling_tile_noborder_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/ceiling_tiles_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/cinder_blocks_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/cobaltgreen_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/concrete_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/concrete_2.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/concrete_3.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/concrete_4.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/concrete_tiles_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/copperred_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/crimson_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/door_doom_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/door_doom_2.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/door_garage_red.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/door_garage_white.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/door_steel_brown.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/door_steel_grey.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/door_steel_red.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/drywall_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/floor_tiles_bw_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/floor_tiles_white.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/grass_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/grass_2.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/lava_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/lava_2.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/lemongrass_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/lightbeige_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/lightcobaltgreen_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/lightgray_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/lightnavyblue_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/lightskyblue_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/lime_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/logo_mila_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/magenta_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/marble_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/marble_2.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/metal_grill_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/metal_grill_2.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/morningglory_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/navyblue_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/oakbrown_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/orange_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/orchid_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/picket_fence_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/adelaide_hanscom1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/adelaide_hanscom1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/alessandro_allori1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/alessandro_allori1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/alessandro_allori2.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/alessandro_allori2.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/alexandre_cabanel1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/alexandre_cabanel1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/alexei_harlamov1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/alexei_harlamov1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/alexey_petrovich_antropov1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/alexey_petrovich_antropov1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/alice_pike_barney1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/alice_pike_barney1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/aman_theodor1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/aman_theodor1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/antonello_messina1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/antonello_messina1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/antonio_herrera_toro1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/antonio_herrera_toro1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/benjamin-constant1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/benjamin-constant1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/benoist_marie-guillemine1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/benoist_marie-guillemine1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/bouguereau_william-adolphe1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/bouguereau_william-adolphe1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/byron1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/byron1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/carl_fredric_breda1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/carl_fredric_breda1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/carl_fredric_breda2.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/carl_fredric_breda2.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/cramacj_lucas1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/cramacj_lucas1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/cranach_lucas2.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/cranach_lucas2.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/cristobal_rojas1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/cristobal_rojas1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/delacroix_eugene_ferdinand_victor1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/delacroix_eugene_ferdinand_victor1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/domenikos_theotokopoulos1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/domenikos_theotokopoulos1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/edmund_blair_leighton1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/edmund_blair_leighton1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/edmund_blair_leighton2.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/edmund_blair_leighton2.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/edwin_longsden_long1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/edwin_longsden_long1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/falero_luis_ricardo1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/falero_luis_ricardo1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/felix_bonfils1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/felix_bonfils1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/francesco_hayez1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/francesco_hayez1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/francisco_goya_lucientes1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/francisco_goya_lucientes1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/francisco_goya_lucientes2.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/francisco_goya_lucientes2.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/francisco_zurbaran1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/francisco_zurbaran1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/franz_von_defregger1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/franz_von_defregger1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/franz_von_defregger2.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/franz_von_defregger2.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/franz_von_defregger3.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/franz_von_defregger3.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/frederic_westin1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/frederic_westin1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/frederic_yates1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/frederic_yates1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/frederick_leighton1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/frederick_leighton1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/gaston_bussiere1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/gaston_bussiere1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/george_henry_hall1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/george_henry_hall1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/giovanni_battista_tiepolo1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/giovanni_battista_tiepolo1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/giovanni_bellini1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/giovanni_bellini1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/hans_holbein1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/hans_holbein1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/hayez_francesco1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/hayez_francesco1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/henryk_siemiradzki1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/henryk_siemiradzki1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/ilja_jefimowitsch_repin1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/ilja_jefimowitsch_repin1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/james_carrol_beckwith1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/james_carrol_beckwith1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-baptiste-camille_corot1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-baptiste-camille_corot1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-baptiste-camille_corot2.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-baptiste-camille_corot2.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-leon_gerome1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-leon_gerome1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-leon_gerome2.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-leon_gerome2.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-leon_gerome3.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-leon_gerome3.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-leon_gerome4.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-leon_gerome4.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/john_william_godward1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/john_william_godward1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/john_william_godward2.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/john_william_godward2.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/john_william_godward3.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/john_william_godward3.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/julije_klovic1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/julije_klovic1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/juriaen_streek1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/juriaen_streek1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/kiprenskij_orest_adamovic1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/kiprenskij_orest_adamovic1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/konstantin_makovsky1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/konstantin_makovsky1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/lefebvre_jules_joseph1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/lefebvre_jules_joseph1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/leon-francois_comerre1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/leon-francois_comerre1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/leopold_loffler1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/leopold_loffler1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/lewis_john_frederick1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/lewis_john_frederick1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/madrazo_garreta_raimundo1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/madrazo_garreta_raimundo1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/marie_bashkirtseff1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/marie_bashkirtseff1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/moritz_kellerhoven1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/moritz_kellerhoven1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/nathaniel_jocelyn1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/nathaniel_jocelyn1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/nikolai_alexandrowitsch_jaroschenko1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/nikolai_alexandrowitsch_jaroschenko1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/nils_johan_olsson_blommer1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/nils_johan_olsson_blommer1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/paolo_veronese1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/paolo_veronese1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/parmigianino1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/parmigianino1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/paul_cesar_helleu1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/paul_cesar_helleu1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/regnault_henri1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/regnault_henri1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/richard_bergh1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/richard_bergh1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/richard_bergh2.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/richard_bergh2.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/robert_dampier1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/robert_dampier1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/robert_lefevre1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/robert_lefevre1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/robert_leopold1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/robert_leopold1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/sichel_nathanael1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/sichel_nathanael1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/svetoslav_roerich1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/svetoslav_roerich1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/velazquez_diego1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/velazquez_diego1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/viktor_vasnetsov1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/viktor_vasnetsov1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/william-adolphe_bouguereau1.license +2 -0
- miniworld_maze/core/miniworld_gymnasium/textures/portraits/william-adolphe_bouguereau1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/realblueberry_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/redbean_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/rock_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/seablue_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/silver_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/skyblue_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/slime_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/stucco_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/sunnyyellow_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/turquoise_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/violet_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/water_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/water_2.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/water_3.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/white_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/wood_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/wood_2.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/textures/wood_planks_1.png +0 -0
- miniworld_maze/core/miniworld_gymnasium/unified_env.py +1364 -0
- miniworld_maze/core/miniworld_gymnasium/utils.py +37 -0
- miniworld_maze/core/miniworld_gymnasium/wrappers.py +2 -0
- miniworld_maze/core/observation_types.py +32 -0
- miniworld_maze/environments/__init__.py +16 -0
- miniworld_maze/environments/base_grid_rooms.py +204 -0
- miniworld_maze/environments/factory.py +155 -0
- miniworld_maze/environments/nine_rooms.py +74 -0
- miniworld_maze/environments/spiral_nine_rooms.py +70 -0
- miniworld_maze/environments/twenty_five_rooms.py +122 -0
- miniworld_maze/tools/__init__.py +5 -0
- miniworld_maze/tools/generate_observations.py +199 -0
- miniworld_maze/wrappers/__init__.py +5 -0
- miniworld_maze/wrappers/image_transforms.py +40 -0
- miniworld_maze-1.0.0.dist-info/METADATA +108 -0
- miniworld_maze-1.0.0.dist-info/RECORD +280 -0
- miniworld_maze-1.0.0.dist-info/WHEEL +4 -0
- miniworld_maze-1.0.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,1364 @@
|
|
|
1
|
+
"""Unified MiniWorld environment class combining CustomEnv and BaseEnv functionality."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
from ctypes import POINTER
|
|
5
|
+
from enum import IntEnum
|
|
6
|
+
|
|
7
|
+
import gymnasium as gym
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pyglet
|
|
10
|
+
from gymnasium import spaces
|
|
11
|
+
from pyglet.gl import *
|
|
12
|
+
|
|
13
|
+
from .entities import *
|
|
14
|
+
from .math import *
|
|
15
|
+
from .objmesh import *
|
|
16
|
+
from .occlusion_queries import OcclusionQueryManager
|
|
17
|
+
from .opengl import *
|
|
18
|
+
from .params import DEFAULT_PARAMS
|
|
19
|
+
from .random import RandGen
|
|
20
|
+
from .room import Room
|
|
21
|
+
|
|
22
|
+
# Optional architectural improvements
|
|
23
|
+
try:
|
|
24
|
+
from .entity_manager import EntityManager
|
|
25
|
+
from .rendering_engine import RenderingEngine
|
|
26
|
+
|
|
27
|
+
ARCHITECTURAL_IMPROVEMENTS_AVAILABLE = True
|
|
28
|
+
except ImportError:
|
|
29
|
+
RenderingEngine = None
|
|
30
|
+
EntityManager = None
|
|
31
|
+
ARCHITECTURAL_IMPROVEMENTS_AVAILABLE = False
|
|
32
|
+
from ..constants import (
|
|
33
|
+
CARRY_POSITION_OFFSET,
|
|
34
|
+
DEFAULT_DISPLAY_WIDTH,
|
|
35
|
+
DEFAULT_WINDOW_HEIGHT,
|
|
36
|
+
DEFAULT_WINDOW_WIDTH,
|
|
37
|
+
EDGE_FACING_THRESHOLD,
|
|
38
|
+
EDGE_TOUCHING_THRESHOLD,
|
|
39
|
+
FAR_CLIPPING_PLANE,
|
|
40
|
+
FONT_SIZE,
|
|
41
|
+
INTERACTION_DISTANCE_MULTIPLIER,
|
|
42
|
+
NEAR_CLIPPING_PLANE,
|
|
43
|
+
OCCLUSION_QUERY_BOX_HEIGHT,
|
|
44
|
+
OCCLUSION_QUERY_BOX_SIZE,
|
|
45
|
+
ORTHOGRAPHIC_DEPTH_RANGE,
|
|
46
|
+
PICKUP_RADIUS_MULTIPLIER,
|
|
47
|
+
PICKUP_REACH_MULTIPLIER,
|
|
48
|
+
PORTAL_CONNECTION_TOLERANCE,
|
|
49
|
+
TEXT_LABEL_WIDTH,
|
|
50
|
+
TEXT_MARGIN_X,
|
|
51
|
+
TEXT_MARGIN_Y,
|
|
52
|
+
TOPDOWN_FRAMEBUFFER_SCALE,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class UnifiedMiniWorldEnv(gym.Env):
|
|
57
|
+
"""
|
|
58
|
+
Unified base class for MiniWorld environments combining CustomEnv and BaseEnv functionality.
|
|
59
|
+
|
|
60
|
+
This class eliminates code duplication by providing a single implementation that supports
|
|
61
|
+
both the enhanced features of CustomMiniWorldEnv and the legacy BaseEnv functionality.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
metadata = {"render.modes": ["human", "rgb_array"], "video.frames_per_second": 30}
|
|
65
|
+
|
|
66
|
+
# Enumeration of possible actions
|
|
67
|
+
class Actions(IntEnum):
|
|
68
|
+
# Turn left or right by a small amount
|
|
69
|
+
turn_left = 0
|
|
70
|
+
turn_right = 1
|
|
71
|
+
|
|
72
|
+
# Move forward or back by a small amount
|
|
73
|
+
move_forward = 2
|
|
74
|
+
move_back = 3
|
|
75
|
+
|
|
76
|
+
# Pick up or drop an object being carried
|
|
77
|
+
pickup = 4
|
|
78
|
+
drop = 5
|
|
79
|
+
|
|
80
|
+
# Toggle/activate an object
|
|
81
|
+
toggle = 6
|
|
82
|
+
|
|
83
|
+
# Done completing task
|
|
84
|
+
done = 7
|
|
85
|
+
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
obs_level=3,
|
|
89
|
+
continuous=False,
|
|
90
|
+
agent_mode="circle",
|
|
91
|
+
max_episode_steps=1500,
|
|
92
|
+
obs_width=80,
|
|
93
|
+
obs_height=80,
|
|
94
|
+
window_width=DEFAULT_WINDOW_WIDTH,
|
|
95
|
+
window_height=DEFAULT_WINDOW_HEIGHT,
|
|
96
|
+
params=DEFAULT_PARAMS,
|
|
97
|
+
domain_rand=False,
|
|
98
|
+
):
|
|
99
|
+
"""
|
|
100
|
+
Initialize unified MiniWorld environment.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
obs_level: Observation level (1=TOP_DOWN_PARTIAL, 2=TOP_DOWN_FULL, 3=FIRST_PERSON)
|
|
104
|
+
continuous: Whether to use continuous actions
|
|
105
|
+
agent_mode: Agent rendering mode ('triangle', 'circle', 'empty')
|
|
106
|
+
max_episode_steps: Maximum steps per episode
|
|
107
|
+
obs_width: Observation width in pixels
|
|
108
|
+
obs_height: Observation height in pixels
|
|
109
|
+
window_width: Window width for human rendering
|
|
110
|
+
window_height: Window height for human rendering
|
|
111
|
+
params: Environment parameters for domain randomization
|
|
112
|
+
domain_rand: Whether to enable domain randomization
|
|
113
|
+
"""
|
|
114
|
+
# Store configuration
|
|
115
|
+
self.obs_level = obs_level
|
|
116
|
+
self.agent_mode = agent_mode
|
|
117
|
+
self.continuous = continuous
|
|
118
|
+
self.max_episode_steps = max_episode_steps
|
|
119
|
+
self.params = params
|
|
120
|
+
self.domain_rand = domain_rand
|
|
121
|
+
|
|
122
|
+
# Setup action space
|
|
123
|
+
self._setup_action_space()
|
|
124
|
+
|
|
125
|
+
# Setup observation space
|
|
126
|
+
self._setup_observation_space(obs_width, obs_height)
|
|
127
|
+
|
|
128
|
+
# Initialize OpenGL context and rendering
|
|
129
|
+
self._initialize_opengl_context()
|
|
130
|
+
|
|
131
|
+
# Setup rendering buffers
|
|
132
|
+
self._setup_rendering_buffers(
|
|
133
|
+
obs_width, obs_height, window_width, window_height
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Initialize UI components
|
|
137
|
+
self._initialize_ui_components(
|
|
138
|
+
window_width, window_height, obs_width, obs_height
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Initialize optional architectural improvements
|
|
142
|
+
self._initialize_architectural_systems()
|
|
143
|
+
|
|
144
|
+
# Finalize initialization
|
|
145
|
+
self._finalize_initialization()
|
|
146
|
+
|
|
147
|
+
def _setup_action_space(self):
|
|
148
|
+
"""Setup action space based on continuous/discrete mode."""
|
|
149
|
+
if not self.continuous:
|
|
150
|
+
# Action enumeration for this environment
|
|
151
|
+
self.actions = UnifiedMiniWorldEnv.Actions
|
|
152
|
+
|
|
153
|
+
# Actions are discrete integer values
|
|
154
|
+
self.action_space = spaces.Discrete(len(self.actions))
|
|
155
|
+
else:
|
|
156
|
+
# Actions are continuous, speed and the difference of direction
|
|
157
|
+
self.action_space = spaces.Box(low=-1, high=1, shape=(2,), dtype=np.float32)
|
|
158
|
+
|
|
159
|
+
def _setup_observation_space(self, obs_width, obs_height):
|
|
160
|
+
"""Setup observation space."""
|
|
161
|
+
# Observations are RGB images with pixels in [0, 255]
|
|
162
|
+
self.observation_space = spaces.Box(
|
|
163
|
+
low=0, high=255, shape=(obs_height, obs_width, 3), dtype=np.uint8
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
self.reward_range = (-math.inf, math.inf)
|
|
167
|
+
|
|
168
|
+
def _initialize_opengl_context(self):
|
|
169
|
+
"""Initialize OpenGL context and window."""
|
|
170
|
+
# Window for displaying the environment to humans
|
|
171
|
+
self.window = None
|
|
172
|
+
|
|
173
|
+
# Invisible window to render into (shadow OpenGL context)
|
|
174
|
+
self.shadow_window = pyglet.window.Window(width=1, height=1, visible=False)
|
|
175
|
+
|
|
176
|
+
# Enable depth testing and backface culling
|
|
177
|
+
glEnable(GL_DEPTH_TEST)
|
|
178
|
+
glEnable(GL_CULL_FACE)
|
|
179
|
+
|
|
180
|
+
def _setup_rendering_buffers(
|
|
181
|
+
self, obs_width, obs_height, window_width, window_height
|
|
182
|
+
):
|
|
183
|
+
"""Setup frame buffers for rendering."""
|
|
184
|
+
# Frame buffer used to render observations
|
|
185
|
+
self.obs_fb = FrameBuffer(obs_width, obs_height, 8)
|
|
186
|
+
|
|
187
|
+
# Frame buffer used for human visualization
|
|
188
|
+
self.vis_fb = FrameBuffer(window_width, window_height, 16)
|
|
189
|
+
|
|
190
|
+
# Top-down frame buffer (created later when needed)
|
|
191
|
+
self.topdown_fb = None
|
|
192
|
+
|
|
193
|
+
def _initialize_ui_components(
|
|
194
|
+
self, window_width, window_height, obs_width, obs_height
|
|
195
|
+
):
|
|
196
|
+
"""Initialize UI components for human rendering."""
|
|
197
|
+
# Compute the observation display size
|
|
198
|
+
self.obs_disp_width = DEFAULT_DISPLAY_WIDTH
|
|
199
|
+
self.obs_disp_height = obs_height * (self.obs_disp_width / obs_width)
|
|
200
|
+
|
|
201
|
+
# For displaying text
|
|
202
|
+
self.text_label = pyglet.text.Label(
|
|
203
|
+
font_name="Arial",
|
|
204
|
+
font_size=FONT_SIZE,
|
|
205
|
+
multiline=True,
|
|
206
|
+
width=TEXT_LABEL_WIDTH,
|
|
207
|
+
x=window_width + TEXT_MARGIN_X,
|
|
208
|
+
y=window_height - (self.obs_disp_height + TEXT_MARGIN_Y),
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def _initialize_architectural_systems(self):
|
|
212
|
+
"""Initialize optional architectural improvements if available."""
|
|
213
|
+
self.use_rendering_engine = False
|
|
214
|
+
self.use_entity_manager = False
|
|
215
|
+
|
|
216
|
+
if ARCHITECTURAL_IMPROVEMENTS_AVAILABLE:
|
|
217
|
+
# Initialize RenderingEngine (optional)
|
|
218
|
+
try:
|
|
219
|
+
self.rendering_engine = RenderingEngine(self.obs_width, self.obs_height)
|
|
220
|
+
self.use_rendering_engine = True
|
|
221
|
+
except Exception:
|
|
222
|
+
# For debugging: print(f"RenderingEngine init failed: {e}")
|
|
223
|
+
self.rendering_engine = None
|
|
224
|
+
self.use_rendering_engine = False
|
|
225
|
+
|
|
226
|
+
# Initialize EntityManager (optional)
|
|
227
|
+
try:
|
|
228
|
+
self.entity_manager = EntityManager()
|
|
229
|
+
self.use_entity_manager = True
|
|
230
|
+
except Exception:
|
|
231
|
+
self.entity_manager = None
|
|
232
|
+
self.use_entity_manager = False
|
|
233
|
+
else:
|
|
234
|
+
self.rendering_engine = None
|
|
235
|
+
self.entity_manager = None
|
|
236
|
+
|
|
237
|
+
def _finalize_initialization(self):
|
|
238
|
+
"""Complete initialization process."""
|
|
239
|
+
# Initialize the state
|
|
240
|
+
self.seed()
|
|
241
|
+
self.reset()
|
|
242
|
+
|
|
243
|
+
def close(self):
|
|
244
|
+
"""Clean up resources."""
|
|
245
|
+
pass
|
|
246
|
+
|
|
247
|
+
def seed(self, seed=None):
|
|
248
|
+
"""Set random seed."""
|
|
249
|
+
self.rand = RandGen(seed)
|
|
250
|
+
return [seed]
|
|
251
|
+
|
|
252
|
+
def reset(self, seed=None, options=None, pos=None):
|
|
253
|
+
"""
|
|
254
|
+
Reset the simulation at the start of a new episode.
|
|
255
|
+
|
|
256
|
+
This also randomizes many environment parameters (domain randomization).
|
|
257
|
+
"""
|
|
258
|
+
# Handle seed for Gymnasium compatibility
|
|
259
|
+
if seed is not None:
|
|
260
|
+
self.seed(seed)
|
|
261
|
+
|
|
262
|
+
# Step count since episode start
|
|
263
|
+
self.step_count = 0
|
|
264
|
+
|
|
265
|
+
# Create the agent
|
|
266
|
+
self.agent = Agent(mode=self.agent_mode)
|
|
267
|
+
|
|
268
|
+
# Set agent in EntityManager if available
|
|
269
|
+
if self.use_entity_manager:
|
|
270
|
+
self.entity_manager.set_agent(self.agent)
|
|
271
|
+
|
|
272
|
+
# List of entities contained
|
|
273
|
+
self.entities = []
|
|
274
|
+
|
|
275
|
+
# Separate lists for performance optimization
|
|
276
|
+
self.static_entities = []
|
|
277
|
+
self.dynamic_entities = []
|
|
278
|
+
|
|
279
|
+
# List of rooms in the world
|
|
280
|
+
self.rooms = []
|
|
281
|
+
|
|
282
|
+
# Wall segments for collision detection
|
|
283
|
+
# Shape is (N, 2, 3)
|
|
284
|
+
self.wall_segs = []
|
|
285
|
+
|
|
286
|
+
# Generate the world
|
|
287
|
+
self._generate_world_layout(pos)
|
|
288
|
+
|
|
289
|
+
# Check if domain randomization is enabled or not
|
|
290
|
+
rand = self.rand if self.domain_rand else None
|
|
291
|
+
|
|
292
|
+
# Randomize elements of the world (domain randomization)
|
|
293
|
+
randomization_params = ["light_pos", "light_color", "light_ambient"]
|
|
294
|
+
|
|
295
|
+
# Add 'black' for custom environments, 'sky_color' for base environments
|
|
296
|
+
if hasattr(self, "_is_custom_env") and self._is_custom_env:
|
|
297
|
+
randomization_params.insert(0, "black")
|
|
298
|
+
else:
|
|
299
|
+
randomization_params.insert(0, "sky_color")
|
|
300
|
+
|
|
301
|
+
self.params.sample_many(rand, self, randomization_params)
|
|
302
|
+
|
|
303
|
+
# Get the max forward step distance
|
|
304
|
+
self.max_forward_step = self.params.get_max("forward_step")
|
|
305
|
+
|
|
306
|
+
# Randomize parameters of the entities
|
|
307
|
+
for ent in self.entities:
|
|
308
|
+
ent.randomize(self.params, rand)
|
|
309
|
+
|
|
310
|
+
# Compute the min and max x, z extents of the whole floorplan
|
|
311
|
+
self.min_x = min([r.min_x for r in self.rooms])
|
|
312
|
+
self.max_x = max([r.max_x for r in self.rooms])
|
|
313
|
+
self.min_z = min([r.min_z for r in self.rooms])
|
|
314
|
+
self.max_z = max([r.max_z for r in self.rooms])
|
|
315
|
+
|
|
316
|
+
# Create top-down frame buffer if needed
|
|
317
|
+
if self.topdown_fb is None:
|
|
318
|
+
width = TOPDOWN_FRAMEBUFFER_SCALE * (int(self.max_x - self.min_x) + 1)
|
|
319
|
+
height = TOPDOWN_FRAMEBUFFER_SCALE * (int(self.max_z - self.min_z) + 1)
|
|
320
|
+
self.topdown_fb = FrameBuffer(width, height, 8)
|
|
321
|
+
|
|
322
|
+
# Generate static data
|
|
323
|
+
if len(self.wall_segs) == 0:
|
|
324
|
+
self._generate_collision_and_rendering_data()
|
|
325
|
+
|
|
326
|
+
# Pre-compile static parts of the environment into a display list
|
|
327
|
+
self._render_static()
|
|
328
|
+
|
|
329
|
+
# Generate the first camera image
|
|
330
|
+
obs = self._generate_observation()
|
|
331
|
+
|
|
332
|
+
# Return first observation with info dict for Gymnasium compatibility
|
|
333
|
+
return obs, {}
|
|
334
|
+
|
|
335
|
+
def _generate_observation(self, render_agent: bool = None):
|
|
336
|
+
"""Generate observation based on current observation level.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
render_agent: Whether to render the agent in the observation.
|
|
340
|
+
If None, uses default behavior based on observation level.
|
|
341
|
+
"""
|
|
342
|
+
# Import ObservationLevel here to avoid circular imports
|
|
343
|
+
from ..observation_types import ObservationLevel
|
|
344
|
+
|
|
345
|
+
if self.obs_level == ObservationLevel.TOP_DOWN_PARTIAL:
|
|
346
|
+
if self.agent_mode == "empty":
|
|
347
|
+
# Agent mode 'empty' always renders without agent
|
|
348
|
+
render_ag = False
|
|
349
|
+
elif render_agent is not None:
|
|
350
|
+
# Use explicit render_agent parameter
|
|
351
|
+
render_ag = render_agent
|
|
352
|
+
else:
|
|
353
|
+
# Default behavior: render agent
|
|
354
|
+
render_ag = True
|
|
355
|
+
return self.render_top_view(POMDP=True, render_ag=render_ag)
|
|
356
|
+
|
|
357
|
+
elif self.obs_level == ObservationLevel.TOP_DOWN_FULL:
|
|
358
|
+
# Use explicit render_agent parameter or default to True
|
|
359
|
+
render_ag = render_agent if render_agent is not None else True
|
|
360
|
+
return self.render_top_view(POMDP=False, render_ag=render_ag)
|
|
361
|
+
|
|
362
|
+
elif self.obs_level == ObservationLevel.FIRST_PERSON:
|
|
363
|
+
# First person view doesn't include the agent anyway
|
|
364
|
+
return self.render_obs()
|
|
365
|
+
|
|
366
|
+
else:
|
|
367
|
+
valid_levels = list(ObservationLevel)
|
|
368
|
+
raise ValueError(
|
|
369
|
+
f"Invalid obs_level {self.obs_level}. Must be one of {valid_levels}"
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
def get_observation(self, render_agent: bool = None):
|
|
373
|
+
"""Public method to generate observation with optional agent rendering control.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
render_agent: Whether to render the agent in the observation.
|
|
377
|
+
If None, uses default behavior based on observation level.
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
np.ndarray: Generated observation image
|
|
381
|
+
"""
|
|
382
|
+
return self._generate_observation(render_agent=render_agent)
|
|
383
|
+
|
|
384
|
+
def _calculate_carried_object_position(self, agent_pos, ent):
|
|
385
|
+
"""Compute the position at which to place an object being carried."""
|
|
386
|
+
dist = self.agent.radius + ent.radius + self.max_forward_step
|
|
387
|
+
pos = agent_pos + self.agent.dir_vec * CARRY_POSITION_OFFSET * dist
|
|
388
|
+
|
|
389
|
+
# Adjust the Y-position so the object is visible while being carried
|
|
390
|
+
y_pos = max(self.agent.cam_height - ent.height - 0.3, 0)
|
|
391
|
+
pos = pos + Y_VEC * y_pos
|
|
392
|
+
|
|
393
|
+
return pos
|
|
394
|
+
|
|
395
|
+
def move_agent(self, fwd_dist, fwd_drift):
|
|
396
|
+
"""Move the agent forward."""
|
|
397
|
+
next_pos = (
|
|
398
|
+
self.agent.pos
|
|
399
|
+
+ self.agent.dir_vec * fwd_dist
|
|
400
|
+
+ self.agent.right_vec * fwd_drift
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
if self.intersect(self.agent, next_pos, self.agent.radius):
|
|
404
|
+
return False
|
|
405
|
+
|
|
406
|
+
carrying = self.agent.carrying
|
|
407
|
+
if carrying:
|
|
408
|
+
next_carrying_pos = self._calculate_carried_object_position(
|
|
409
|
+
next_pos, carrying
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
if self.intersect(carrying, next_carrying_pos, carrying.radius):
|
|
413
|
+
return False
|
|
414
|
+
|
|
415
|
+
carrying.pos = next_carrying_pos
|
|
416
|
+
|
|
417
|
+
self.agent.pos = next_pos
|
|
418
|
+
return True
|
|
419
|
+
|
|
420
|
+
def turn_agent(self, turn_angle):
|
|
421
|
+
"""Turn the agent left or right."""
|
|
422
|
+
turn_angle *= math.pi / 180
|
|
423
|
+
orig_dir = self.agent.dir
|
|
424
|
+
|
|
425
|
+
self.agent.dir += turn_angle
|
|
426
|
+
|
|
427
|
+
if self.intersect(self.agent, self.agent.pos, self.agent.radius):
|
|
428
|
+
self.agent.dir -= turn_angle
|
|
429
|
+
return False
|
|
430
|
+
|
|
431
|
+
carrying = self.agent.carrying
|
|
432
|
+
if carrying:
|
|
433
|
+
pos = self._calculate_carried_object_position(self.agent.pos, carrying)
|
|
434
|
+
|
|
435
|
+
if self.intersect(carrying, pos, carrying.radius):
|
|
436
|
+
self.agent.dir = orig_dir
|
|
437
|
+
return False
|
|
438
|
+
|
|
439
|
+
carrying.pos = pos
|
|
440
|
+
carrying.dir = self.agent.dir
|
|
441
|
+
|
|
442
|
+
return True
|
|
443
|
+
|
|
444
|
+
def turn_and_move_agent(self, fwd_dist, turn_angle):
|
|
445
|
+
"""
|
|
446
|
+
Simultaneously turn and move the agent in a single action.
|
|
447
|
+
|
|
448
|
+
This method is optimized for continuous control where the agent
|
|
449
|
+
needs to change direction while moving forward.
|
|
450
|
+
|
|
451
|
+
Args:
|
|
452
|
+
fwd_dist: Forward movement distance in environment units
|
|
453
|
+
turn_angle: Turn angle in degrees (positive = clockwise)
|
|
454
|
+
|
|
455
|
+
Returns:
|
|
456
|
+
bool: True if movement was successful, False if blocked by collision
|
|
457
|
+
|
|
458
|
+
Note:
|
|
459
|
+
This method modifies agent.pos and agent.dir directly.
|
|
460
|
+
Original direction is restored on collision.
|
|
461
|
+
"""
|
|
462
|
+
orig_dir = self.agent.dir
|
|
463
|
+
self.agent.dir += turn_angle * (math.pi / 180)
|
|
464
|
+
|
|
465
|
+
next_pos = self.agent.pos + self.agent.dir_vec * fwd_dist
|
|
466
|
+
|
|
467
|
+
if self.intersect(self.agent, next_pos, self.agent.radius):
|
|
468
|
+
self.agent.dir = orig_dir
|
|
469
|
+
return False
|
|
470
|
+
else:
|
|
471
|
+
self.agent.pos = next_pos
|
|
472
|
+
return True
|
|
473
|
+
|
|
474
|
+
def pos_agent(self, fwd_dist, angle):
|
|
475
|
+
"""
|
|
476
|
+
Position the agent at a specific distance and angle.
|
|
477
|
+
|
|
478
|
+
This method sets the agent's direction to the specified angle
|
|
479
|
+
and moves forward by the specified distance.
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
fwd_dist: Forward movement distance in environment units
|
|
483
|
+
angle: Absolute angle in degrees (0 = facing positive Z)
|
|
484
|
+
|
|
485
|
+
Returns:
|
|
486
|
+
bool: True if positioning was successful, False if blocked
|
|
487
|
+
"""
|
|
488
|
+
self.agent.dir = angle * (math.pi / 180)
|
|
489
|
+
next_pos = self.agent.pos + self.agent.dir_vec * fwd_dist
|
|
490
|
+
|
|
491
|
+
if self.intersect(self.agent, next_pos, self.agent.radius):
|
|
492
|
+
return False
|
|
493
|
+
else:
|
|
494
|
+
self.agent.pos = next_pos
|
|
495
|
+
return True
|
|
496
|
+
|
|
497
|
+
def step(self, action):
|
|
498
|
+
"""Perform one action and update the simulation."""
|
|
499
|
+
self.step_count += 1
|
|
500
|
+
|
|
501
|
+
# Process the action based on environment mode
|
|
502
|
+
self._process_action(action)
|
|
503
|
+
|
|
504
|
+
# Generate observation
|
|
505
|
+
observation = self._generate_observation()
|
|
506
|
+
|
|
507
|
+
# Calculate step results
|
|
508
|
+
reward, terminated, info = self._calculate_step_results(observation)
|
|
509
|
+
|
|
510
|
+
return observation, reward, terminated, False, info
|
|
511
|
+
|
|
512
|
+
def _process_action(self, action):
|
|
513
|
+
"""Process action based on continuous/discrete mode."""
|
|
514
|
+
if self.continuous:
|
|
515
|
+
self._handle_continuous_action(action)
|
|
516
|
+
else:
|
|
517
|
+
self._handle_discrete_action(action)
|
|
518
|
+
|
|
519
|
+
def _handle_continuous_action(self, action):
|
|
520
|
+
"""Handle continuous action processing."""
|
|
521
|
+
if self.agent.mode == "circle":
|
|
522
|
+
self.pos_agent(action[0], 180 * action[1])
|
|
523
|
+
else:
|
|
524
|
+
self.turn_and_move_agent(action[0], 15 * action[1])
|
|
525
|
+
|
|
526
|
+
def _handle_discrete_action(self, action):
|
|
527
|
+
"""Handle discrete action processing."""
|
|
528
|
+
rand = self.rand if self.domain_rand else None
|
|
529
|
+
fwd_step = self.params.sample(rand, "forward_step")
|
|
530
|
+
fwd_drift = self.params.sample(rand, "forward_drift")
|
|
531
|
+
turn_step = self.params.sample(rand, "turn_step")
|
|
532
|
+
|
|
533
|
+
if action == self.actions.move_forward:
|
|
534
|
+
self.move_agent(fwd_step, fwd_drift)
|
|
535
|
+
|
|
536
|
+
elif action == self.actions.move_back:
|
|
537
|
+
self.move_agent(-fwd_step, fwd_drift)
|
|
538
|
+
|
|
539
|
+
elif action == self.actions.turn_left:
|
|
540
|
+
self.turn_agent(turn_step)
|
|
541
|
+
|
|
542
|
+
elif action == self.actions.turn_right:
|
|
543
|
+
self.turn_agent(-turn_step)
|
|
544
|
+
|
|
545
|
+
# Pick up an object
|
|
546
|
+
elif action == self.actions.pickup:
|
|
547
|
+
test_pos = (
|
|
548
|
+
self.agent.pos
|
|
549
|
+
+ self.agent.dir_vec * PICKUP_REACH_MULTIPLIER * self.agent.radius
|
|
550
|
+
)
|
|
551
|
+
ent = self.intersect(
|
|
552
|
+
self.agent, test_pos, PICKUP_RADIUS_MULTIPLIER * self.agent.radius
|
|
553
|
+
)
|
|
554
|
+
if not self.agent.carrying:
|
|
555
|
+
if isinstance(ent, Entity):
|
|
556
|
+
if not ent.is_static:
|
|
557
|
+
self.agent.carrying = ent
|
|
558
|
+
|
|
559
|
+
# Drop an object being carried
|
|
560
|
+
elif action == self.actions.drop:
|
|
561
|
+
if self.agent.carrying:
|
|
562
|
+
self.agent.carrying.pos[1] = 0
|
|
563
|
+
self.agent.carrying = None
|
|
564
|
+
|
|
565
|
+
# Update carried object position
|
|
566
|
+
if self.agent.carrying:
|
|
567
|
+
ent_pos = self._calculate_carried_object_position(
|
|
568
|
+
self.agent.pos, self.agent.carrying
|
|
569
|
+
)
|
|
570
|
+
self.agent.carrying.pos = ent_pos
|
|
571
|
+
self.agent.carrying.dir = self.agent.dir
|
|
572
|
+
|
|
573
|
+
def _calculate_step_results(self, observation):
|
|
574
|
+
"""Calculate reward, termination, and info for step."""
|
|
575
|
+
# Generate topdown view for info if needed
|
|
576
|
+
topdown = None
|
|
577
|
+
if self.obs_level != 2: # Not TOP_DOWN_FULL
|
|
578
|
+
topdown = self.render_top_view(POMDP=False, frame_buffer=self.topdown_fb)
|
|
579
|
+
|
|
580
|
+
# Check termination
|
|
581
|
+
if self.step_count >= self.max_episode_steps:
|
|
582
|
+
terminated = True
|
|
583
|
+
reward = 0
|
|
584
|
+
info = {
|
|
585
|
+
"pos": self.agent.pos,
|
|
586
|
+
"mdp_view": topdown if topdown is not None else observation,
|
|
587
|
+
}
|
|
588
|
+
else:
|
|
589
|
+
reward = 0
|
|
590
|
+
terminated = False
|
|
591
|
+
info = {
|
|
592
|
+
"pos": self.agent.pos,
|
|
593
|
+
"mdp_view": topdown if topdown is not None else observation,
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
return reward, terminated, info
|
|
597
|
+
|
|
598
|
+
def add_rect_room(self, min_x, max_x, min_z, max_z, **kwargs):
|
|
599
|
+
"""Create a rectangular room."""
|
|
600
|
+
# 2D outline coordinates of the room,
|
|
601
|
+
# listed in counter-clockwise order when viewed from the top
|
|
602
|
+
outline = np.array(
|
|
603
|
+
[
|
|
604
|
+
# East wall
|
|
605
|
+
[max_x, max_z],
|
|
606
|
+
# North wall
|
|
607
|
+
[max_x, min_z],
|
|
608
|
+
# West wall
|
|
609
|
+
[min_x, min_z],
|
|
610
|
+
# South wall
|
|
611
|
+
[min_x, max_z],
|
|
612
|
+
]
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
return self.add_room(outline=outline, **kwargs)
|
|
616
|
+
|
|
617
|
+
def add_room(self, **kwargs):
|
|
618
|
+
"""Create a new room."""
|
|
619
|
+
if len(self.wall_segs) != 0:
|
|
620
|
+
raise RuntimeError("Cannot add rooms after static data is generated")
|
|
621
|
+
|
|
622
|
+
room = Room(**kwargs)
|
|
623
|
+
self.rooms.append(room)
|
|
624
|
+
|
|
625
|
+
return room
|
|
626
|
+
|
|
627
|
+
def connect_rooms(
|
|
628
|
+
self, room_a, room_b, min_x=None, max_x=None, min_z=None, max_z=None, max_y=None
|
|
629
|
+
):
|
|
630
|
+
"""Connect two rooms along facing edges."""
|
|
631
|
+
edge_pair = self._find_facing_edges(room_a, room_b)
|
|
632
|
+
portal_coords = self._calculate_portal_coordinates(
|
|
633
|
+
room_a, room_b, edge_pair, min_x, max_x, min_z, max_z, max_y
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
if self._portals_directly_connected(portal_coords):
|
|
637
|
+
return
|
|
638
|
+
|
|
639
|
+
connecting_room = self._create_connecting_room(portal_coords, room_a, max_y)
|
|
640
|
+
self._add_portals_to_connecting_room(connecting_room, portal_coords)
|
|
641
|
+
|
|
642
|
+
def _find_facing_edges(self, room_a, room_b):
|
|
643
|
+
"""Extract the complex edge-finding logic."""
|
|
644
|
+
for idx_a in range(room_a.num_walls):
|
|
645
|
+
norm_a = room_a.edge_norms[idx_a]
|
|
646
|
+
|
|
647
|
+
for idx_b in range(room_b.num_walls):
|
|
648
|
+
norm_b = room_b.edge_norms[idx_b]
|
|
649
|
+
|
|
650
|
+
# Reject edges that are not facing each other
|
|
651
|
+
if np.dot(norm_a, norm_b) > EDGE_FACING_THRESHOLD:
|
|
652
|
+
continue
|
|
653
|
+
|
|
654
|
+
dir = room_b.outline[idx_b] - room_a.outline[idx_a]
|
|
655
|
+
|
|
656
|
+
# Reject edges that are not touching
|
|
657
|
+
if np.dot(norm_a, dir) > EDGE_TOUCHING_THRESHOLD:
|
|
658
|
+
continue
|
|
659
|
+
|
|
660
|
+
return idx_a, idx_b
|
|
661
|
+
|
|
662
|
+
return None, None
|
|
663
|
+
|
|
664
|
+
def _calculate_portal_coordinates(
|
|
665
|
+
self, room_a, room_b, edge_pair, min_x, max_x, min_z, max_z, max_y
|
|
666
|
+
):
|
|
667
|
+
"""Extract portal coordinate calculations."""
|
|
668
|
+
idx_a, idx_b = edge_pair
|
|
669
|
+
|
|
670
|
+
if idx_a is None:
|
|
671
|
+
raise ValueError(f"No matching edges found between {room_a} and {room_b}")
|
|
672
|
+
|
|
673
|
+
start_a, end_a = room_a.add_portal(
|
|
674
|
+
edge=idx_a, min_x=min_x, max_x=max_x, min_z=min_z, max_z=max_z, max_y=max_y
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
start_b, end_b = room_b.add_portal(
|
|
678
|
+
edge=idx_b, min_x=min_x, max_x=max_x, min_z=min_z, max_z=max_z, max_y=max_y
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
a = room_a.outline[idx_a] + room_a.edge_dirs[idx_a] * start_a
|
|
682
|
+
b = room_a.outline[idx_a] + room_a.edge_dirs[idx_a] * end_a
|
|
683
|
+
c = room_b.outline[idx_b] + room_b.edge_dirs[idx_b] * start_b
|
|
684
|
+
d = room_b.outline[idx_b] + room_b.edge_dirs[idx_b] * end_b
|
|
685
|
+
|
|
686
|
+
return {
|
|
687
|
+
"a": a,
|
|
688
|
+
"b": b,
|
|
689
|
+
"c": c,
|
|
690
|
+
"d": d,
|
|
691
|
+
"len_a": np.linalg.norm(b - a),
|
|
692
|
+
"len_b": np.linalg.norm(d - c),
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
def _portals_directly_connected(self, portal_coords):
|
|
696
|
+
"""Check if portals are directly connected."""
|
|
697
|
+
return (
|
|
698
|
+
np.linalg.norm(portal_coords["a"] - portal_coords["d"])
|
|
699
|
+
< PORTAL_CONNECTION_TOLERANCE
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
def _create_connecting_room(self, portal_coords, room_a, max_y):
|
|
703
|
+
"""Create connecting room between portals."""
|
|
704
|
+
# Room outline points must be specified in counter-clockwise order
|
|
705
|
+
outline = np.stack(
|
|
706
|
+
[
|
|
707
|
+
portal_coords["c"],
|
|
708
|
+
portal_coords["b"],
|
|
709
|
+
portal_coords["a"],
|
|
710
|
+
portal_coords["d"],
|
|
711
|
+
]
|
|
712
|
+
)
|
|
713
|
+
outline = np.stack([outline[:, 0], outline[:, 2]], axis=1)
|
|
714
|
+
|
|
715
|
+
max_y = max_y if max_y is not None else room_a.wall_height
|
|
716
|
+
|
|
717
|
+
room = Room(
|
|
718
|
+
outline,
|
|
719
|
+
wall_height=max_y,
|
|
720
|
+
wall_tex=room_a.wall_tex_name,
|
|
721
|
+
floor_tex=room_a.floor_tex_name,
|
|
722
|
+
ceil_tex=room_a.ceil_tex_name,
|
|
723
|
+
no_ceiling=room_a.no_ceiling,
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
self.rooms.append(room)
|
|
727
|
+
return room
|
|
728
|
+
|
|
729
|
+
def _add_portals_to_connecting_room(self, connecting_room, portal_coords):
|
|
730
|
+
"""Add portals to the connecting room."""
|
|
731
|
+
connecting_room.add_portal(1, start_pos=0, end_pos=portal_coords["len_a"])
|
|
732
|
+
connecting_room.add_portal(3, start_pos=0, end_pos=portal_coords["len_b"])
|
|
733
|
+
|
|
734
|
+
def place_entity(
|
|
735
|
+
self,
|
|
736
|
+
ent,
|
|
737
|
+
room=None,
|
|
738
|
+
pos=None,
|
|
739
|
+
dir=None,
|
|
740
|
+
min_x=None,
|
|
741
|
+
max_x=None,
|
|
742
|
+
min_z=None,
|
|
743
|
+
max_z=None,
|
|
744
|
+
):
|
|
745
|
+
"""
|
|
746
|
+
Place an entity/object in the world.
|
|
747
|
+
Find a position that doesn't intersect with any other object.
|
|
748
|
+
"""
|
|
749
|
+
if len(self.rooms) == 0:
|
|
750
|
+
raise ValueError("Must create rooms before calling place_entity")
|
|
751
|
+
if ent.radius is None:
|
|
752
|
+
raise ValueError("Entity must have physical size (radius) defined")
|
|
753
|
+
|
|
754
|
+
# Generate collision detection data
|
|
755
|
+
if len(self.wall_segs) == 0:
|
|
756
|
+
self._generate_collision_and_rendering_data()
|
|
757
|
+
|
|
758
|
+
# If an exact position is specified
|
|
759
|
+
if pos is not None:
|
|
760
|
+
ent.dir = dir if dir is not None else self.rand.float(-math.pi, math.pi)
|
|
761
|
+
ent.pos = pos
|
|
762
|
+
self.entities.append(ent)
|
|
763
|
+
|
|
764
|
+
# Add to EntityManager if available
|
|
765
|
+
if self.use_entity_manager:
|
|
766
|
+
self.entity_manager.add_entity(ent, pos)
|
|
767
|
+
|
|
768
|
+
# Add to appropriate performance list
|
|
769
|
+
if ent.is_static:
|
|
770
|
+
self.static_entities.append(ent)
|
|
771
|
+
else:
|
|
772
|
+
self.dynamic_entities.append(ent)
|
|
773
|
+
|
|
774
|
+
return ent
|
|
775
|
+
|
|
776
|
+
# Keep retrying until we find a suitable position
|
|
777
|
+
while True:
|
|
778
|
+
# Pick a room, sample rooms proportionally to floor surface area
|
|
779
|
+
selected_room = (
|
|
780
|
+
room if room else self.rand.choice(self.rooms, probs=self.room_probs)
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
# Choose a random point within the square bounding box of the room
|
|
784
|
+
low_x = selected_room.min_x if min_x is None else min_x
|
|
785
|
+
high_x = selected_room.max_x if max_x is None else max_x
|
|
786
|
+
low_z = selected_room.min_z if min_z is None else min_z
|
|
787
|
+
high_z = selected_room.max_z if max_z is None else max_z
|
|
788
|
+
|
|
789
|
+
pos = self.rand.float(
|
|
790
|
+
low=[low_x + ent.radius, 0, low_z + ent.radius],
|
|
791
|
+
high=[high_x - ent.radius, 0, high_z - ent.radius],
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
# Make sure the position is within the room's outline
|
|
795
|
+
if not selected_room.point_inside(pos):
|
|
796
|
+
continue
|
|
797
|
+
|
|
798
|
+
# Pick a direction
|
|
799
|
+
direction = dir if dir is not None else self.rand.float(-math.pi, math.pi)
|
|
800
|
+
|
|
801
|
+
ent.pos = pos
|
|
802
|
+
ent.dir = direction
|
|
803
|
+
|
|
804
|
+
# Make sure the position doesn't intersect with any walls
|
|
805
|
+
if self.intersect(ent, pos, ent.radius):
|
|
806
|
+
continue
|
|
807
|
+
|
|
808
|
+
break
|
|
809
|
+
|
|
810
|
+
self.entities.append(ent)
|
|
811
|
+
|
|
812
|
+
# Add to EntityManager if available
|
|
813
|
+
if self.use_entity_manager:
|
|
814
|
+
self.entity_manager.add_entity(ent, ent.pos)
|
|
815
|
+
|
|
816
|
+
# Add to appropriate performance list
|
|
817
|
+
if ent.is_static:
|
|
818
|
+
self.static_entities.append(ent)
|
|
819
|
+
else:
|
|
820
|
+
self.dynamic_entities.append(ent)
|
|
821
|
+
|
|
822
|
+
return ent
|
|
823
|
+
|
|
824
|
+
def place_agent(
|
|
825
|
+
self,
|
|
826
|
+
room=None,
|
|
827
|
+
pos=None,
|
|
828
|
+
dir=None,
|
|
829
|
+
min_x=None,
|
|
830
|
+
max_x=None,
|
|
831
|
+
min_z=None,
|
|
832
|
+
max_z=None,
|
|
833
|
+
):
|
|
834
|
+
"""
|
|
835
|
+
Place the agent in the environment at a random position and orientation.
|
|
836
|
+
"""
|
|
837
|
+
return self.place_entity(
|
|
838
|
+
self.agent,
|
|
839
|
+
room=room,
|
|
840
|
+
pos=pos,
|
|
841
|
+
dir=dir,
|
|
842
|
+
min_x=min_x,
|
|
843
|
+
max_x=max_x,
|
|
844
|
+
min_z=min_z,
|
|
845
|
+
max_z=max_z,
|
|
846
|
+
)
|
|
847
|
+
|
|
848
|
+
def intersect(self, ent, pos, radius):
|
|
849
|
+
"""Check if an entity intersects with the world."""
|
|
850
|
+
# Ignore the Y position
|
|
851
|
+
entity_x, _, entity_z = pos
|
|
852
|
+
pos = np.array([entity_x, 0, entity_z])
|
|
853
|
+
|
|
854
|
+
# Check for intersection with walls
|
|
855
|
+
if intersect_circle_segs(pos, radius, self.wall_segs):
|
|
856
|
+
return True
|
|
857
|
+
|
|
858
|
+
# Check for entity intersection
|
|
859
|
+
for other_entity in self.entities:
|
|
860
|
+
# Entities can't intersect with themselves
|
|
861
|
+
if other_entity is ent:
|
|
862
|
+
continue
|
|
863
|
+
|
|
864
|
+
other_x, _, other_z = other_entity.pos
|
|
865
|
+
other_pos = np.array([other_x, 0, other_z])
|
|
866
|
+
|
|
867
|
+
distance = 0
|
|
868
|
+
if ent.trable or other_entity.trable:
|
|
869
|
+
distance = 10000000
|
|
870
|
+
else:
|
|
871
|
+
distance = np.linalg.norm(other_pos - pos)
|
|
872
|
+
|
|
873
|
+
if distance < radius + other_entity.radius:
|
|
874
|
+
return other_entity
|
|
875
|
+
|
|
876
|
+
return None
|
|
877
|
+
|
|
878
|
+
def is_within_interaction_distance(self, ent0, ent1=None):
|
|
879
|
+
"""
|
|
880
|
+
Test if two entities are within interaction distance.
|
|
881
|
+
Used for "go to" or "put next" type tasks.
|
|
882
|
+
"""
|
|
883
|
+
if ent1 is None:
|
|
884
|
+
ent1 = self.agent
|
|
885
|
+
|
|
886
|
+
distance = np.linalg.norm(ent0.pos - ent1.pos)
|
|
887
|
+
threshold = (
|
|
888
|
+
ent0.radius
|
|
889
|
+
+ ent1.radius
|
|
890
|
+
+ INTERACTION_DISTANCE_MULTIPLIER * self.max_forward_step
|
|
891
|
+
)
|
|
892
|
+
return distance < threshold
|
|
893
|
+
|
|
894
|
+
def _load_tex(self, tex_name):
|
|
895
|
+
"""Load a texture, with or without domain randomization."""
|
|
896
|
+
rand = self.rand if self.params.sample(self.rand, "tex_rand") else None
|
|
897
|
+
return Texture.get(tex_name, rand)
|
|
898
|
+
|
|
899
|
+
def _generate_collision_and_rendering_data(self):
|
|
900
|
+
"""Generate static data needed for rendering and collision detection."""
|
|
901
|
+
# Generate the static data for each room
|
|
902
|
+
for room in self.rooms:
|
|
903
|
+
room._gen_static_data(self.params, self.rand if self.domain_rand else None)
|
|
904
|
+
|
|
905
|
+
# Concatenate the wall segments
|
|
906
|
+
self.wall_segs = np.concatenate([r.wall_segs for r in self.rooms])
|
|
907
|
+
|
|
908
|
+
# Room selection probabilities
|
|
909
|
+
self.room_probs = np.array([r.area for r in self.rooms], dtype=float)
|
|
910
|
+
self.room_probs /= np.sum(self.room_probs)
|
|
911
|
+
|
|
912
|
+
def _generate_world_layout(self, pos=None):
|
|
913
|
+
"""Generate the world layout. Derived classes must implement this method."""
|
|
914
|
+
raise NotImplementedError
|
|
915
|
+
|
|
916
|
+
def _reward(self):
|
|
917
|
+
"""Default sparse reward computation."""
|
|
918
|
+
return 1.0 - 0.2 * (self.step_count / self.max_episode_steps)
|
|
919
|
+
|
|
920
|
+
def _render_static(self):
|
|
921
|
+
"""
|
|
922
|
+
Render the static elements of the scene into a display list.
|
|
923
|
+
Called once at the beginning of each episode.
|
|
924
|
+
"""
|
|
925
|
+
# Manage OpenGL display list for static rendering
|
|
926
|
+
# Note: Could be improved with automatic display list management
|
|
927
|
+
glDeleteLists(1, 1)
|
|
928
|
+
glNewList(1, GL_COMPILE)
|
|
929
|
+
|
|
930
|
+
# Light position
|
|
931
|
+
glLightfv(GL_LIGHT0, GL_POSITION, (GLfloat * 4)(*self.light_pos + [1]))
|
|
932
|
+
|
|
933
|
+
# Background/minimum light level
|
|
934
|
+
glLightfv(GL_LIGHT0, GL_AMBIENT, (GLfloat * 4)(*self.light_ambient))
|
|
935
|
+
|
|
936
|
+
# Diffuse light color
|
|
937
|
+
glLightfv(GL_LIGHT0, GL_DIFFUSE, (GLfloat * 4)(*self.light_color))
|
|
938
|
+
|
|
939
|
+
glEnable(GL_LIGHTING)
|
|
940
|
+
glEnable(GL_LIGHT0)
|
|
941
|
+
|
|
942
|
+
glShadeModel(GL_SMOOTH)
|
|
943
|
+
glEnable(GL_COLOR_MATERIAL)
|
|
944
|
+
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
|
|
945
|
+
|
|
946
|
+
# Render the rooms
|
|
947
|
+
glEnable(GL_TEXTURE_2D)
|
|
948
|
+
for room in self.rooms:
|
|
949
|
+
room._render()
|
|
950
|
+
|
|
951
|
+
# Render the static entities (performance optimization)
|
|
952
|
+
for ent in self.static_entities:
|
|
953
|
+
ent.render()
|
|
954
|
+
|
|
955
|
+
glEndList()
|
|
956
|
+
|
|
957
|
+
def _render_world(self, frame_buffer, render_agent, view_bounds=None):
|
|
958
|
+
"""
|
|
959
|
+
Render the world from a given camera position into a frame buffer,
|
|
960
|
+
and produce a numpy image array as output.
|
|
961
|
+
"""
|
|
962
|
+
# Call the display list for the static parts of the environment
|
|
963
|
+
glCallList(1)
|
|
964
|
+
|
|
965
|
+
# Render dynamic entities only (performance optimization)
|
|
966
|
+
for ent in self.dynamic_entities:
|
|
967
|
+
if ent is not self.agent:
|
|
968
|
+
# Frustum culling for POMDP mode (skip entities outside view bounds)
|
|
969
|
+
if view_bounds is not None:
|
|
970
|
+
min_x, max_x, min_z, max_z = view_bounds
|
|
971
|
+
if (
|
|
972
|
+
ent.pos[0] < min_x - ent.radius
|
|
973
|
+
or ent.pos[0] > max_x + ent.radius
|
|
974
|
+
or ent.pos[2] < min_z - ent.radius
|
|
975
|
+
or ent.pos[2] > max_z + ent.radius
|
|
976
|
+
):
|
|
977
|
+
continue
|
|
978
|
+
|
|
979
|
+
ent.render()
|
|
980
|
+
|
|
981
|
+
if render_agent:
|
|
982
|
+
self.agent.render()
|
|
983
|
+
|
|
984
|
+
# Resolve the rendered image into a numpy array
|
|
985
|
+
img = frame_buffer.resolve()
|
|
986
|
+
return img
|
|
987
|
+
|
|
988
|
+
def render_top_view(self, frame_buffer=None, POMDP=False, render_ag=True):
|
|
989
|
+
"""Render a top view of the whole map (from above)."""
|
|
990
|
+
if not isinstance(POMDP, bool):
|
|
991
|
+
raise TypeError(f"POMDP parameter must be boolean, got {type(POMDP)}")
|
|
992
|
+
|
|
993
|
+
frame_buffer = frame_buffer or self.obs_fb
|
|
994
|
+
|
|
995
|
+
self._prepare_top_view_rendering(frame_buffer)
|
|
996
|
+
scene_extents = self._calculate_scene_extents(POMDP)
|
|
997
|
+
self._setup_top_view_camera(scene_extents, frame_buffer)
|
|
998
|
+
|
|
999
|
+
return self._render_world(
|
|
1000
|
+
frame_buffer,
|
|
1001
|
+
render_agent=render_ag,
|
|
1002
|
+
view_bounds=scene_extents if POMDP else None,
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
def _prepare_top_view_rendering(self, frame_buffer):
|
|
1006
|
+
"""Prepare top view rendering setup."""
|
|
1007
|
+
|
|
1008
|
+
# Switch to the default OpenGL context
|
|
1009
|
+
# This is necessary on Linux Nvidia drivers
|
|
1010
|
+
self.shadow_window.switch_to()
|
|
1011
|
+
|
|
1012
|
+
# Bind the frame buffer before rendering into it
|
|
1013
|
+
frame_buffer.bind()
|
|
1014
|
+
|
|
1015
|
+
# Clear the color and depth buffers
|
|
1016
|
+
background_color = self.black if hasattr(self, "black") else self.sky_color
|
|
1017
|
+
glClearColor(*background_color, 1.0)
|
|
1018
|
+
glClearDepth(1.0)
|
|
1019
|
+
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
|
1020
|
+
|
|
1021
|
+
def _calculate_scene_extents(self, POMDP):
|
|
1022
|
+
"""Calculate scene extents for rendering."""
|
|
1023
|
+
if POMDP:
|
|
1024
|
+
# Import constants for POMDP view radius
|
|
1025
|
+
from ..constants import POMDP_VIEW_RADIUS
|
|
1026
|
+
|
|
1027
|
+
agent_x, _, agent_z = self.agent.pos
|
|
1028
|
+
min_x = agent_x - POMDP_VIEW_RADIUS
|
|
1029
|
+
max_x = agent_x + POMDP_VIEW_RADIUS
|
|
1030
|
+
min_z = agent_z - POMDP_VIEW_RADIUS
|
|
1031
|
+
max_z = agent_z + POMDP_VIEW_RADIUS
|
|
1032
|
+
else:
|
|
1033
|
+
min_x = self.min_x - 1
|
|
1034
|
+
max_x = self.max_x + 1
|
|
1035
|
+
min_z = self.min_z - 1
|
|
1036
|
+
max_z = self.max_z + 1
|
|
1037
|
+
|
|
1038
|
+
return (min_x, max_x, min_z, max_z)
|
|
1039
|
+
|
|
1040
|
+
def _setup_top_view_camera(self, scene_extents, frame_buffer):
|
|
1041
|
+
"""Setup camera for top view rendering."""
|
|
1042
|
+
min_x, max_x, min_z, max_z = scene_extents
|
|
1043
|
+
|
|
1044
|
+
width = max_x - min_x
|
|
1045
|
+
height = max_z - min_z
|
|
1046
|
+
aspect = width / height
|
|
1047
|
+
framebuffer_aspect_ratio = frame_buffer.width / frame_buffer.height
|
|
1048
|
+
|
|
1049
|
+
# Adjust the aspect extents to match the frame buffer aspect
|
|
1050
|
+
if aspect > framebuffer_aspect_ratio:
|
|
1051
|
+
# Want to add to denom, add to height
|
|
1052
|
+
new_h = width / framebuffer_aspect_ratio
|
|
1053
|
+
h_diff = new_h - height
|
|
1054
|
+
min_z -= h_diff / 2
|
|
1055
|
+
max_z += h_diff / 2
|
|
1056
|
+
elif aspect < framebuffer_aspect_ratio:
|
|
1057
|
+
# Want to add to num, add to width
|
|
1058
|
+
new_w = height * framebuffer_aspect_ratio
|
|
1059
|
+
w_diff = new_w - width
|
|
1060
|
+
min_x -= w_diff / 2
|
|
1061
|
+
max_x += w_diff / 2
|
|
1062
|
+
|
|
1063
|
+
# Set the projection matrix
|
|
1064
|
+
glMatrixMode(GL_PROJECTION)
|
|
1065
|
+
glLoadIdentity()
|
|
1066
|
+
glOrtho(
|
|
1067
|
+
min_x,
|
|
1068
|
+
max_x,
|
|
1069
|
+
-max_z,
|
|
1070
|
+
-min_z,
|
|
1071
|
+
-ORTHOGRAPHIC_DEPTH_RANGE,
|
|
1072
|
+
ORTHOGRAPHIC_DEPTH_RANGE,
|
|
1073
|
+
)
|
|
1074
|
+
|
|
1075
|
+
# Setup the camera
|
|
1076
|
+
# Y maps to +Z, Z maps to +Y
|
|
1077
|
+
glMatrixMode(GL_MODELVIEW)
|
|
1078
|
+
glLoadIdentity()
|
|
1079
|
+
m = [
|
|
1080
|
+
1,
|
|
1081
|
+
0,
|
|
1082
|
+
0,
|
|
1083
|
+
0,
|
|
1084
|
+
0,
|
|
1085
|
+
0,
|
|
1086
|
+
1,
|
|
1087
|
+
0,
|
|
1088
|
+
0,
|
|
1089
|
+
-1,
|
|
1090
|
+
0,
|
|
1091
|
+
0,
|
|
1092
|
+
0,
|
|
1093
|
+
0,
|
|
1094
|
+
0,
|
|
1095
|
+
1,
|
|
1096
|
+
]
|
|
1097
|
+
glLoadMatrixf((GLfloat * len(m))(*m))
|
|
1098
|
+
|
|
1099
|
+
return (min_x, max_x, min_z, max_z)
|
|
1100
|
+
|
|
1101
|
+
def render_obs(self, frame_buffer=None):
|
|
1102
|
+
"""Render an observation from the point of view of the agent."""
|
|
1103
|
+
if frame_buffer is None:
|
|
1104
|
+
frame_buffer = self.obs_fb
|
|
1105
|
+
|
|
1106
|
+
# Switch to the default OpenGL context
|
|
1107
|
+
# This is necessary on Linux Nvidia drivers
|
|
1108
|
+
self.shadow_window.switch_to()
|
|
1109
|
+
|
|
1110
|
+
# Bind the frame buffer before rendering into it
|
|
1111
|
+
frame_buffer.bind()
|
|
1112
|
+
|
|
1113
|
+
# Clear the color and depth buffers
|
|
1114
|
+
background_color = self.black if hasattr(self, "black") else self.sky_color
|
|
1115
|
+
glClearColor(*background_color, 1.0)
|
|
1116
|
+
glClearDepth(1.0)
|
|
1117
|
+
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
|
1118
|
+
|
|
1119
|
+
# Set the projection matrix
|
|
1120
|
+
glMatrixMode(GL_PROJECTION)
|
|
1121
|
+
glLoadIdentity()
|
|
1122
|
+
gluPerspective(
|
|
1123
|
+
self.agent.cam_fov_y,
|
|
1124
|
+
frame_buffer.width / float(frame_buffer.height),
|
|
1125
|
+
NEAR_CLIPPING_PLANE,
|
|
1126
|
+
FAR_CLIPPING_PLANE,
|
|
1127
|
+
)
|
|
1128
|
+
|
|
1129
|
+
# Setup the camera
|
|
1130
|
+
glMatrixMode(GL_MODELVIEW)
|
|
1131
|
+
glLoadIdentity()
|
|
1132
|
+
gluLookAt(
|
|
1133
|
+
# Eye position
|
|
1134
|
+
*self.agent.cam_pos,
|
|
1135
|
+
# Target
|
|
1136
|
+
*(self.agent.cam_pos + self.agent.cam_dir),
|
|
1137
|
+
# Up vector
|
|
1138
|
+
0,
|
|
1139
|
+
1.0,
|
|
1140
|
+
0.0,
|
|
1141
|
+
)
|
|
1142
|
+
|
|
1143
|
+
return self._render_world(frame_buffer, render_agent=False)
|
|
1144
|
+
|
|
1145
|
+
def render_depth(self, frame_buffer=None):
|
|
1146
|
+
"""
|
|
1147
|
+
Produce a depth map.
|
|
1148
|
+
Values are floating-point, map shape is (H,W,1)
|
|
1149
|
+
Distances are in meters from the observer.
|
|
1150
|
+
"""
|
|
1151
|
+
if frame_buffer is None:
|
|
1152
|
+
frame_buffer = self.obs_fb
|
|
1153
|
+
|
|
1154
|
+
# Render the world
|
|
1155
|
+
self.render_obs(frame_buffer)
|
|
1156
|
+
|
|
1157
|
+
return frame_buffer.get_depth_map(NEAR_CLIPPING_PLANE, FAR_CLIPPING_PLANE)
|
|
1158
|
+
|
|
1159
|
+
def get_visible_ents(self):
|
|
1160
|
+
"""
|
|
1161
|
+
Get entities visible to agent using occlusion queries.
|
|
1162
|
+
|
|
1163
|
+
Returns:
|
|
1164
|
+
set: Set of Entity objects that are visible to the agent
|
|
1165
|
+
"""
|
|
1166
|
+
with OcclusionQueryManager(self.entities) as query_manager:
|
|
1167
|
+
self._setup_visibility_rendering()
|
|
1168
|
+
return self._perform_occlusion_queries(query_manager)
|
|
1169
|
+
|
|
1170
|
+
def _setup_visibility_rendering(self):
|
|
1171
|
+
"""Setup rendering for visibility queries."""
|
|
1172
|
+
# Switch to the default OpenGL context
|
|
1173
|
+
self.shadow_window.switch_to()
|
|
1174
|
+
|
|
1175
|
+
# Use the small observation frame buffer
|
|
1176
|
+
frame_buffer = self.obs_fb
|
|
1177
|
+
frame_buffer.bind()
|
|
1178
|
+
|
|
1179
|
+
# Clear the color and depth buffers
|
|
1180
|
+
background_color = self.black if hasattr(self, "black") else self.sky_color
|
|
1181
|
+
glClearColor(*background_color, 1.0)
|
|
1182
|
+
glClearDepth(1.0)
|
|
1183
|
+
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
|
1184
|
+
|
|
1185
|
+
# Set the projection matrix
|
|
1186
|
+
glMatrixMode(GL_PROJECTION)
|
|
1187
|
+
glLoadIdentity()
|
|
1188
|
+
gluPerspective(
|
|
1189
|
+
self.agent.cam_fov_y,
|
|
1190
|
+
frame_buffer.width / float(frame_buffer.height),
|
|
1191
|
+
NEAR_CLIPPING_PLANE,
|
|
1192
|
+
FAR_CLIPPING_PLANE,
|
|
1193
|
+
)
|
|
1194
|
+
|
|
1195
|
+
# Setup the camera
|
|
1196
|
+
glMatrixMode(GL_MODELVIEW)
|
|
1197
|
+
glLoadIdentity()
|
|
1198
|
+
gluLookAt(
|
|
1199
|
+
# Eye position
|
|
1200
|
+
*self.agent.cam_pos,
|
|
1201
|
+
# Target
|
|
1202
|
+
*(self.agent.cam_pos + self.agent.cam_dir),
|
|
1203
|
+
# Up vector
|
|
1204
|
+
0,
|
|
1205
|
+
1.0,
|
|
1206
|
+
0.0,
|
|
1207
|
+
)
|
|
1208
|
+
|
|
1209
|
+
# Render the rooms, without texturing
|
|
1210
|
+
glDisable(GL_TEXTURE_2D)
|
|
1211
|
+
for room in self.rooms:
|
|
1212
|
+
room._render()
|
|
1213
|
+
|
|
1214
|
+
def _perform_occlusion_queries(self, query_manager: OcclusionQueryManager):
|
|
1215
|
+
"""
|
|
1216
|
+
Perform occlusion queries on all entities.
|
|
1217
|
+
|
|
1218
|
+
Args:
|
|
1219
|
+
query_manager: Initialized occlusion query manager
|
|
1220
|
+
|
|
1221
|
+
Returns:
|
|
1222
|
+
set: Set of visible entities
|
|
1223
|
+
"""
|
|
1224
|
+
# Render occlusion query boxes for each entity
|
|
1225
|
+
for entity_index, entity in enumerate(self.entities):
|
|
1226
|
+
if entity is self.agent:
|
|
1227
|
+
continue
|
|
1228
|
+
|
|
1229
|
+
query_manager.begin_query(entity_index)
|
|
1230
|
+
|
|
1231
|
+
# Draw a small box at the entity's position for occlusion testing
|
|
1232
|
+
pos = entity.pos
|
|
1233
|
+
drawBox(
|
|
1234
|
+
x_min=pos[0] - OCCLUSION_QUERY_BOX_SIZE,
|
|
1235
|
+
x_max=pos[0] + OCCLUSION_QUERY_BOX_SIZE,
|
|
1236
|
+
y_min=pos[1],
|
|
1237
|
+
y_max=pos[1] + OCCLUSION_QUERY_BOX_HEIGHT,
|
|
1238
|
+
z_min=pos[2] - OCCLUSION_QUERY_BOX_SIZE,
|
|
1239
|
+
z_max=pos[2] + OCCLUSION_QUERY_BOX_SIZE,
|
|
1240
|
+
)
|
|
1241
|
+
|
|
1242
|
+
query_manager.end_query()
|
|
1243
|
+
|
|
1244
|
+
# Get results using the query manager
|
|
1245
|
+
return query_manager.get_visible_entities(self.agent)
|
|
1246
|
+
|
|
1247
|
+
def render(self, mode="human", close=False, view="agent"):
|
|
1248
|
+
"""Render the environment for human viewing."""
|
|
1249
|
+
if close:
|
|
1250
|
+
return self._close_rendering()
|
|
1251
|
+
|
|
1252
|
+
rendered_image = self._render_scene(view)
|
|
1253
|
+
|
|
1254
|
+
if mode == "rgb_array":
|
|
1255
|
+
return rendered_image
|
|
1256
|
+
|
|
1257
|
+
return self._display_human_view(rendered_image, mode)
|
|
1258
|
+
|
|
1259
|
+
def _close_rendering(self):
|
|
1260
|
+
"""Handle rendering cleanup."""
|
|
1261
|
+
if self.window:
|
|
1262
|
+
self.window.close()
|
|
1263
|
+
|
|
1264
|
+
def _render_scene(self, view):
|
|
1265
|
+
"""Handle core scene rendering logic."""
|
|
1266
|
+
# Render the human-view image
|
|
1267
|
+
if view not in ["agent", "top"]:
|
|
1268
|
+
raise ValueError(f"Invalid view '{view}'. Must be 'agent' or 'top'")
|
|
1269
|
+
if view == "agent":
|
|
1270
|
+
img = self.render_obs(self.vis_fb)
|
|
1271
|
+
else:
|
|
1272
|
+
# Import ObservationLevel here to avoid circular imports
|
|
1273
|
+
from ..observation_types import ObservationLevel
|
|
1274
|
+
|
|
1275
|
+
if self.obs_level == ObservationLevel.TOP_DOWN_PARTIAL:
|
|
1276
|
+
img = self.render_top_view(self.vis_fb, POMDP=True)
|
|
1277
|
+
else:
|
|
1278
|
+
img = self.render_top_view(self.vis_fb, POMDP=False)
|
|
1279
|
+
|
|
1280
|
+
return img
|
|
1281
|
+
|
|
1282
|
+
def _display_human_view(self, img, mode):
|
|
1283
|
+
"""Handle human visualization display."""
|
|
1284
|
+
img_width = img.shape[1]
|
|
1285
|
+
img_height = img.shape[0]
|
|
1286
|
+
|
|
1287
|
+
# Render the agent's view
|
|
1288
|
+
obs = self.render_obs()
|
|
1289
|
+
obs_width = obs.shape[1]
|
|
1290
|
+
obs_height = obs.shape[0]
|
|
1291
|
+
|
|
1292
|
+
window_width = img_width + self.obs_disp_width
|
|
1293
|
+
window_height = img_height
|
|
1294
|
+
|
|
1295
|
+
if self.window is None:
|
|
1296
|
+
config = pyglet.gl.Config(double_buffer=True)
|
|
1297
|
+
self.window = pyglet.window.Window(
|
|
1298
|
+
width=window_width, height=window_height, resizable=False, config=config
|
|
1299
|
+
)
|
|
1300
|
+
|
|
1301
|
+
self.window.clear()
|
|
1302
|
+
self.window.switch_to()
|
|
1303
|
+
|
|
1304
|
+
# Bind the default frame buffer
|
|
1305
|
+
glBindFramebuffer(GL_FRAMEBUFFER, 0)
|
|
1306
|
+
|
|
1307
|
+
# Clear the color and depth buffers
|
|
1308
|
+
glClearColor(0, 0, 0, 1.0)
|
|
1309
|
+
glClearDepth(1.0)
|
|
1310
|
+
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
|
1311
|
+
|
|
1312
|
+
# Setup orthogonal projection
|
|
1313
|
+
glMatrixMode(GL_PROJECTION)
|
|
1314
|
+
glLoadIdentity()
|
|
1315
|
+
glMatrixMode(GL_MODELVIEW)
|
|
1316
|
+
glLoadIdentity()
|
|
1317
|
+
glOrtho(0, window_width, 0, window_height, 0, 10)
|
|
1318
|
+
|
|
1319
|
+
# Draw the human render to the rendering window
|
|
1320
|
+
img_flip = np.ascontiguousarray(np.flip(img, axis=0))
|
|
1321
|
+
img_data = pyglet.image.ImageData(
|
|
1322
|
+
img_width,
|
|
1323
|
+
img_height,
|
|
1324
|
+
"RGB",
|
|
1325
|
+
img_flip.ctypes.data_as(POINTER(GLubyte)),
|
|
1326
|
+
pitch=img_width * 3,
|
|
1327
|
+
)
|
|
1328
|
+
img_data.blit(0, 0, 0, width=img_width, height=img_height)
|
|
1329
|
+
|
|
1330
|
+
# Draw the observation
|
|
1331
|
+
obs = np.ascontiguousarray(np.flip(obs, axis=0))
|
|
1332
|
+
obs_data = pyglet.image.ImageData(
|
|
1333
|
+
obs_width,
|
|
1334
|
+
obs_height,
|
|
1335
|
+
"RGB",
|
|
1336
|
+
obs.ctypes.data_as(POINTER(GLubyte)),
|
|
1337
|
+
pitch=obs_width * 3,
|
|
1338
|
+
)
|
|
1339
|
+
obs_data.blit(
|
|
1340
|
+
img_width,
|
|
1341
|
+
img_height - self.obs_disp_height,
|
|
1342
|
+
0,
|
|
1343
|
+
width=self.obs_disp_width,
|
|
1344
|
+
height=self.obs_disp_height,
|
|
1345
|
+
)
|
|
1346
|
+
|
|
1347
|
+
# Draw the text label in the window
|
|
1348
|
+
self.text_label.text = "pos: (%.2f, %.2f, %.2f)\nangle: %d\nsteps: %d" % (
|
|
1349
|
+
*self.agent.pos,
|
|
1350
|
+
int(self.agent.dir * 180 / math.pi) % 360,
|
|
1351
|
+
self.step_count,
|
|
1352
|
+
)
|
|
1353
|
+
self.text_label.draw()
|
|
1354
|
+
|
|
1355
|
+
# Force execution of queued commands
|
|
1356
|
+
glFlush()
|
|
1357
|
+
|
|
1358
|
+
# If we are not running the Pyglet event loop,
|
|
1359
|
+
# we have to manually flip the buffers and dispatch events
|
|
1360
|
+
if mode == "human":
|
|
1361
|
+
self.window.flip()
|
|
1362
|
+
self.window.dispatch_events()
|
|
1363
|
+
|
|
1364
|
+
return img
|