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.

Files changed (280) hide show
  1. miniworld_maze/__init__.py +37 -0
  2. miniworld_maze/core/__init__.py +8 -0
  3. miniworld_maze/core/constants.py +130 -0
  4. miniworld_maze/core/miniworld_gymnasium/README.md +1 -0
  5. miniworld_maze/core/miniworld_gymnasium/__init__.py +4 -0
  6. miniworld_maze/core/miniworld_gymnasium/base_env.py +52 -0
  7. miniworld_maze/core/miniworld_gymnasium/entities/__init__.py +7 -0
  8. miniworld_maze/core/miniworld_gymnasium/entities/agent.py +117 -0
  9. miniworld_maze/core/miniworld_gymnasium/entities/base_entity.py +135 -0
  10. miniworld_maze/core/miniworld_gymnasium/entities/objects.py +116 -0
  11. miniworld_maze/core/miniworld_gymnasium/entity_manager.py +358 -0
  12. miniworld_maze/core/miniworld_gymnasium/envs/__init__.py +3 -0
  13. miniworld_maze/core/miniworld_gymnasium/math.py +83 -0
  14. miniworld_maze/core/miniworld_gymnasium/objmesh.py +294 -0
  15. miniworld_maze/core/miniworld_gymnasium/occlusion_queries.py +97 -0
  16. miniworld_maze/core/miniworld_gymnasium/opengl.py +21 -0
  17. miniworld_maze/core/miniworld_gymnasium/params.py +128 -0
  18. miniworld_maze/core/miniworld_gymnasium/random.py +105 -0
  19. miniworld_maze/core/miniworld_gymnasium/rendering/__init__.py +7 -0
  20. miniworld_maze/core/miniworld_gymnasium/rendering/drawing.py +77 -0
  21. miniworld_maze/core/miniworld_gymnasium/rendering/framebuffer.py +260 -0
  22. miniworld_maze/core/miniworld_gymnasium/rendering/texture.py +101 -0
  23. miniworld_maze/core/miniworld_gymnasium/rendering_engine.py +293 -0
  24. miniworld_maze/core/miniworld_gymnasium/room.py +330 -0
  25. miniworld_maze/core/miniworld_gymnasium/texture_utils.py +64 -0
  26. miniworld_maze/core/miniworld_gymnasium/textures/airduct_grate_1.png +0 -0
  27. miniworld_maze/core/miniworld_gymnasium/textures/asphalt_1.png +0 -0
  28. miniworld_maze/core/miniworld_gymnasium/textures/beanpaste_1.png +0 -0
  29. miniworld_maze/core/miniworld_gymnasium/textures/beige_1.png +0 -0
  30. miniworld_maze/core/miniworld_gymnasium/textures/black_1.png +0 -0
  31. miniworld_maze/core/miniworld_gymnasium/textures/blueberry_1.png +0 -0
  32. miniworld_maze/core/miniworld_gymnasium/textures/brick_wall_1.png +0 -0
  33. miniworld_maze/core/miniworld_gymnasium/textures/cardboard_1.png +0 -0
  34. miniworld_maze/core/miniworld_gymnasium/textures/cardboard_2.png +0 -0
  35. miniworld_maze/core/miniworld_gymnasium/textures/cardboard_3.png +0 -0
  36. miniworld_maze/core/miniworld_gymnasium/textures/cardboard_4.png +0 -0
  37. miniworld_maze/core/miniworld_gymnasium/textures/ceiling_tile_noborder_1.png +0 -0
  38. miniworld_maze/core/miniworld_gymnasium/textures/ceiling_tiles_1.png +0 -0
  39. miniworld_maze/core/miniworld_gymnasium/textures/cinder_blocks_1.png +0 -0
  40. miniworld_maze/core/miniworld_gymnasium/textures/cobaltgreen_1.png +0 -0
  41. miniworld_maze/core/miniworld_gymnasium/textures/concrete_1.png +0 -0
  42. miniworld_maze/core/miniworld_gymnasium/textures/concrete_2.png +0 -0
  43. miniworld_maze/core/miniworld_gymnasium/textures/concrete_3.png +0 -0
  44. miniworld_maze/core/miniworld_gymnasium/textures/concrete_4.png +0 -0
  45. miniworld_maze/core/miniworld_gymnasium/textures/concrete_tiles_1.png +0 -0
  46. miniworld_maze/core/miniworld_gymnasium/textures/copperred_1.png +0 -0
  47. miniworld_maze/core/miniworld_gymnasium/textures/crimson_1.png +0 -0
  48. miniworld_maze/core/miniworld_gymnasium/textures/door_doom_1.png +0 -0
  49. miniworld_maze/core/miniworld_gymnasium/textures/door_doom_2.png +0 -0
  50. miniworld_maze/core/miniworld_gymnasium/textures/door_garage_red.png +0 -0
  51. miniworld_maze/core/miniworld_gymnasium/textures/door_garage_white.png +0 -0
  52. miniworld_maze/core/miniworld_gymnasium/textures/door_steel_brown.png +0 -0
  53. miniworld_maze/core/miniworld_gymnasium/textures/door_steel_grey.png +0 -0
  54. miniworld_maze/core/miniworld_gymnasium/textures/door_steel_red.png +0 -0
  55. miniworld_maze/core/miniworld_gymnasium/textures/drywall_1.png +0 -0
  56. miniworld_maze/core/miniworld_gymnasium/textures/floor_tiles_bw_1.png +0 -0
  57. miniworld_maze/core/miniworld_gymnasium/textures/floor_tiles_white.png +0 -0
  58. miniworld_maze/core/miniworld_gymnasium/textures/grass_1.png +0 -0
  59. miniworld_maze/core/miniworld_gymnasium/textures/grass_2.png +0 -0
  60. miniworld_maze/core/miniworld_gymnasium/textures/lava_1.png +0 -0
  61. miniworld_maze/core/miniworld_gymnasium/textures/lava_2.png +0 -0
  62. miniworld_maze/core/miniworld_gymnasium/textures/lemongrass_1.png +0 -0
  63. miniworld_maze/core/miniworld_gymnasium/textures/lightbeige_1.png +0 -0
  64. miniworld_maze/core/miniworld_gymnasium/textures/lightcobaltgreen_1.png +0 -0
  65. miniworld_maze/core/miniworld_gymnasium/textures/lightgray_1.png +0 -0
  66. miniworld_maze/core/miniworld_gymnasium/textures/lightnavyblue_1.png +0 -0
  67. miniworld_maze/core/miniworld_gymnasium/textures/lightskyblue_1.png +0 -0
  68. miniworld_maze/core/miniworld_gymnasium/textures/lime_1.png +0 -0
  69. miniworld_maze/core/miniworld_gymnasium/textures/logo_mila_1.png +0 -0
  70. miniworld_maze/core/miniworld_gymnasium/textures/magenta_1.png +0 -0
  71. miniworld_maze/core/miniworld_gymnasium/textures/marble_1.png +0 -0
  72. miniworld_maze/core/miniworld_gymnasium/textures/marble_2.png +0 -0
  73. miniworld_maze/core/miniworld_gymnasium/textures/metal_grill_1.png +0 -0
  74. miniworld_maze/core/miniworld_gymnasium/textures/metal_grill_2.png +0 -0
  75. miniworld_maze/core/miniworld_gymnasium/textures/morningglory_1.png +0 -0
  76. miniworld_maze/core/miniworld_gymnasium/textures/navyblue_1.png +0 -0
  77. miniworld_maze/core/miniworld_gymnasium/textures/oakbrown_1.png +0 -0
  78. miniworld_maze/core/miniworld_gymnasium/textures/orange_1.png +0 -0
  79. miniworld_maze/core/miniworld_gymnasium/textures/orchid_1.png +0 -0
  80. miniworld_maze/core/miniworld_gymnasium/textures/picket_fence_1.png +0 -0
  81. miniworld_maze/core/miniworld_gymnasium/textures/portraits/adelaide_hanscom1.license +2 -0
  82. miniworld_maze/core/miniworld_gymnasium/textures/portraits/adelaide_hanscom1.png +0 -0
  83. miniworld_maze/core/miniworld_gymnasium/textures/portraits/alessandro_allori1.license +2 -0
  84. miniworld_maze/core/miniworld_gymnasium/textures/portraits/alessandro_allori1.png +0 -0
  85. miniworld_maze/core/miniworld_gymnasium/textures/portraits/alessandro_allori2.license +2 -0
  86. miniworld_maze/core/miniworld_gymnasium/textures/portraits/alessandro_allori2.png +0 -0
  87. miniworld_maze/core/miniworld_gymnasium/textures/portraits/alexandre_cabanel1.license +2 -0
  88. miniworld_maze/core/miniworld_gymnasium/textures/portraits/alexandre_cabanel1.png +0 -0
  89. miniworld_maze/core/miniworld_gymnasium/textures/portraits/alexei_harlamov1.license +2 -0
  90. miniworld_maze/core/miniworld_gymnasium/textures/portraits/alexei_harlamov1.png +0 -0
  91. miniworld_maze/core/miniworld_gymnasium/textures/portraits/alexey_petrovich_antropov1.license +2 -0
  92. miniworld_maze/core/miniworld_gymnasium/textures/portraits/alexey_petrovich_antropov1.png +0 -0
  93. miniworld_maze/core/miniworld_gymnasium/textures/portraits/alice_pike_barney1.license +2 -0
  94. miniworld_maze/core/miniworld_gymnasium/textures/portraits/alice_pike_barney1.png +0 -0
  95. miniworld_maze/core/miniworld_gymnasium/textures/portraits/aman_theodor1.license +2 -0
  96. miniworld_maze/core/miniworld_gymnasium/textures/portraits/aman_theodor1.png +0 -0
  97. miniworld_maze/core/miniworld_gymnasium/textures/portraits/antonello_messina1.license +2 -0
  98. miniworld_maze/core/miniworld_gymnasium/textures/portraits/antonello_messina1.png +0 -0
  99. miniworld_maze/core/miniworld_gymnasium/textures/portraits/antonio_herrera_toro1.license +2 -0
  100. miniworld_maze/core/miniworld_gymnasium/textures/portraits/antonio_herrera_toro1.png +0 -0
  101. miniworld_maze/core/miniworld_gymnasium/textures/portraits/benjamin-constant1.license +2 -0
  102. miniworld_maze/core/miniworld_gymnasium/textures/portraits/benjamin-constant1.png +0 -0
  103. miniworld_maze/core/miniworld_gymnasium/textures/portraits/benoist_marie-guillemine1.license +2 -0
  104. miniworld_maze/core/miniworld_gymnasium/textures/portraits/benoist_marie-guillemine1.png +0 -0
  105. miniworld_maze/core/miniworld_gymnasium/textures/portraits/bouguereau_william-adolphe1.license +2 -0
  106. miniworld_maze/core/miniworld_gymnasium/textures/portraits/bouguereau_william-adolphe1.png +0 -0
  107. miniworld_maze/core/miniworld_gymnasium/textures/portraits/byron1.license +2 -0
  108. miniworld_maze/core/miniworld_gymnasium/textures/portraits/byron1.png +0 -0
  109. miniworld_maze/core/miniworld_gymnasium/textures/portraits/carl_fredric_breda1.license +2 -0
  110. miniworld_maze/core/miniworld_gymnasium/textures/portraits/carl_fredric_breda1.png +0 -0
  111. miniworld_maze/core/miniworld_gymnasium/textures/portraits/carl_fredric_breda2.license +2 -0
  112. miniworld_maze/core/miniworld_gymnasium/textures/portraits/carl_fredric_breda2.png +0 -0
  113. miniworld_maze/core/miniworld_gymnasium/textures/portraits/cramacj_lucas1.license +2 -0
  114. miniworld_maze/core/miniworld_gymnasium/textures/portraits/cramacj_lucas1.png +0 -0
  115. miniworld_maze/core/miniworld_gymnasium/textures/portraits/cranach_lucas2.license +2 -0
  116. miniworld_maze/core/miniworld_gymnasium/textures/portraits/cranach_lucas2.png +0 -0
  117. miniworld_maze/core/miniworld_gymnasium/textures/portraits/cristobal_rojas1.license +2 -0
  118. miniworld_maze/core/miniworld_gymnasium/textures/portraits/cristobal_rojas1.png +0 -0
  119. miniworld_maze/core/miniworld_gymnasium/textures/portraits/delacroix_eugene_ferdinand_victor1.license +2 -0
  120. miniworld_maze/core/miniworld_gymnasium/textures/portraits/delacroix_eugene_ferdinand_victor1.png +0 -0
  121. miniworld_maze/core/miniworld_gymnasium/textures/portraits/domenikos_theotokopoulos1.license +2 -0
  122. miniworld_maze/core/miniworld_gymnasium/textures/portraits/domenikos_theotokopoulos1.png +0 -0
  123. miniworld_maze/core/miniworld_gymnasium/textures/portraits/edmund_blair_leighton1.license +2 -0
  124. miniworld_maze/core/miniworld_gymnasium/textures/portraits/edmund_blair_leighton1.png +0 -0
  125. miniworld_maze/core/miniworld_gymnasium/textures/portraits/edmund_blair_leighton2.license +2 -0
  126. miniworld_maze/core/miniworld_gymnasium/textures/portraits/edmund_blair_leighton2.png +0 -0
  127. miniworld_maze/core/miniworld_gymnasium/textures/portraits/edwin_longsden_long1.license +2 -0
  128. miniworld_maze/core/miniworld_gymnasium/textures/portraits/edwin_longsden_long1.png +0 -0
  129. miniworld_maze/core/miniworld_gymnasium/textures/portraits/falero_luis_ricardo1.license +2 -0
  130. miniworld_maze/core/miniworld_gymnasium/textures/portraits/falero_luis_ricardo1.png +0 -0
  131. miniworld_maze/core/miniworld_gymnasium/textures/portraits/felix_bonfils1.license +2 -0
  132. miniworld_maze/core/miniworld_gymnasium/textures/portraits/felix_bonfils1.png +0 -0
  133. miniworld_maze/core/miniworld_gymnasium/textures/portraits/francesco_hayez1.license +2 -0
  134. miniworld_maze/core/miniworld_gymnasium/textures/portraits/francesco_hayez1.png +0 -0
  135. miniworld_maze/core/miniworld_gymnasium/textures/portraits/francisco_goya_lucientes1.license +2 -0
  136. miniworld_maze/core/miniworld_gymnasium/textures/portraits/francisco_goya_lucientes1.png +0 -0
  137. miniworld_maze/core/miniworld_gymnasium/textures/portraits/francisco_goya_lucientes2.license +2 -0
  138. miniworld_maze/core/miniworld_gymnasium/textures/portraits/francisco_goya_lucientes2.png +0 -0
  139. miniworld_maze/core/miniworld_gymnasium/textures/portraits/francisco_zurbaran1.license +2 -0
  140. miniworld_maze/core/miniworld_gymnasium/textures/portraits/francisco_zurbaran1.png +0 -0
  141. miniworld_maze/core/miniworld_gymnasium/textures/portraits/franz_von_defregger1.license +2 -0
  142. miniworld_maze/core/miniworld_gymnasium/textures/portraits/franz_von_defregger1.png +0 -0
  143. miniworld_maze/core/miniworld_gymnasium/textures/portraits/franz_von_defregger2.license +2 -0
  144. miniworld_maze/core/miniworld_gymnasium/textures/portraits/franz_von_defregger2.png +0 -0
  145. miniworld_maze/core/miniworld_gymnasium/textures/portraits/franz_von_defregger3.license +2 -0
  146. miniworld_maze/core/miniworld_gymnasium/textures/portraits/franz_von_defregger3.png +0 -0
  147. miniworld_maze/core/miniworld_gymnasium/textures/portraits/frederic_westin1.license +2 -0
  148. miniworld_maze/core/miniworld_gymnasium/textures/portraits/frederic_westin1.png +0 -0
  149. miniworld_maze/core/miniworld_gymnasium/textures/portraits/frederic_yates1.license +2 -0
  150. miniworld_maze/core/miniworld_gymnasium/textures/portraits/frederic_yates1.png +0 -0
  151. miniworld_maze/core/miniworld_gymnasium/textures/portraits/frederick_leighton1.license +2 -0
  152. miniworld_maze/core/miniworld_gymnasium/textures/portraits/frederick_leighton1.png +0 -0
  153. miniworld_maze/core/miniworld_gymnasium/textures/portraits/gaston_bussiere1.license +2 -0
  154. miniworld_maze/core/miniworld_gymnasium/textures/portraits/gaston_bussiere1.png +0 -0
  155. miniworld_maze/core/miniworld_gymnasium/textures/portraits/george_henry_hall1.license +2 -0
  156. miniworld_maze/core/miniworld_gymnasium/textures/portraits/george_henry_hall1.png +0 -0
  157. miniworld_maze/core/miniworld_gymnasium/textures/portraits/giovanni_battista_tiepolo1.license +2 -0
  158. miniworld_maze/core/miniworld_gymnasium/textures/portraits/giovanni_battista_tiepolo1.png +0 -0
  159. miniworld_maze/core/miniworld_gymnasium/textures/portraits/giovanni_bellini1.license +2 -0
  160. miniworld_maze/core/miniworld_gymnasium/textures/portraits/giovanni_bellini1.png +0 -0
  161. miniworld_maze/core/miniworld_gymnasium/textures/portraits/hans_holbein1.license +2 -0
  162. miniworld_maze/core/miniworld_gymnasium/textures/portraits/hans_holbein1.png +0 -0
  163. miniworld_maze/core/miniworld_gymnasium/textures/portraits/hayez_francesco1.license +2 -0
  164. miniworld_maze/core/miniworld_gymnasium/textures/portraits/hayez_francesco1.png +0 -0
  165. miniworld_maze/core/miniworld_gymnasium/textures/portraits/henryk_siemiradzki1.license +2 -0
  166. miniworld_maze/core/miniworld_gymnasium/textures/portraits/henryk_siemiradzki1.png +0 -0
  167. miniworld_maze/core/miniworld_gymnasium/textures/portraits/ilja_jefimowitsch_repin1.license +2 -0
  168. miniworld_maze/core/miniworld_gymnasium/textures/portraits/ilja_jefimowitsch_repin1.png +0 -0
  169. miniworld_maze/core/miniworld_gymnasium/textures/portraits/james_carrol_beckwith1.license +2 -0
  170. miniworld_maze/core/miniworld_gymnasium/textures/portraits/james_carrol_beckwith1.png +0 -0
  171. miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-baptiste-camille_corot1.license +2 -0
  172. miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-baptiste-camille_corot1.png +0 -0
  173. miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-baptiste-camille_corot2.license +2 -0
  174. miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-baptiste-camille_corot2.png +0 -0
  175. miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-leon_gerome1.license +2 -0
  176. miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-leon_gerome1.png +0 -0
  177. miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-leon_gerome2.license +2 -0
  178. miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-leon_gerome2.png +0 -0
  179. miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-leon_gerome3.license +2 -0
  180. miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-leon_gerome3.png +0 -0
  181. miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-leon_gerome4.license +2 -0
  182. miniworld_maze/core/miniworld_gymnasium/textures/portraits/jean-leon_gerome4.png +0 -0
  183. miniworld_maze/core/miniworld_gymnasium/textures/portraits/john_william_godward1.license +2 -0
  184. miniworld_maze/core/miniworld_gymnasium/textures/portraits/john_william_godward1.png +0 -0
  185. miniworld_maze/core/miniworld_gymnasium/textures/portraits/john_william_godward2.license +2 -0
  186. miniworld_maze/core/miniworld_gymnasium/textures/portraits/john_william_godward2.png +0 -0
  187. miniworld_maze/core/miniworld_gymnasium/textures/portraits/john_william_godward3.license +2 -0
  188. miniworld_maze/core/miniworld_gymnasium/textures/portraits/john_william_godward3.png +0 -0
  189. miniworld_maze/core/miniworld_gymnasium/textures/portraits/julije_klovic1.license +2 -0
  190. miniworld_maze/core/miniworld_gymnasium/textures/portraits/julije_klovic1.png +0 -0
  191. miniworld_maze/core/miniworld_gymnasium/textures/portraits/juriaen_streek1.license +2 -0
  192. miniworld_maze/core/miniworld_gymnasium/textures/portraits/juriaen_streek1.png +0 -0
  193. miniworld_maze/core/miniworld_gymnasium/textures/portraits/kiprenskij_orest_adamovic1.license +2 -0
  194. miniworld_maze/core/miniworld_gymnasium/textures/portraits/kiprenskij_orest_adamovic1.png +0 -0
  195. miniworld_maze/core/miniworld_gymnasium/textures/portraits/konstantin_makovsky1.license +2 -0
  196. miniworld_maze/core/miniworld_gymnasium/textures/portraits/konstantin_makovsky1.png +0 -0
  197. miniworld_maze/core/miniworld_gymnasium/textures/portraits/lefebvre_jules_joseph1.license +2 -0
  198. miniworld_maze/core/miniworld_gymnasium/textures/portraits/lefebvre_jules_joseph1.png +0 -0
  199. miniworld_maze/core/miniworld_gymnasium/textures/portraits/leon-francois_comerre1.license +2 -0
  200. miniworld_maze/core/miniworld_gymnasium/textures/portraits/leon-francois_comerre1.png +0 -0
  201. miniworld_maze/core/miniworld_gymnasium/textures/portraits/leopold_loffler1.license +2 -0
  202. miniworld_maze/core/miniworld_gymnasium/textures/portraits/leopold_loffler1.png +0 -0
  203. miniworld_maze/core/miniworld_gymnasium/textures/portraits/lewis_john_frederick1.license +2 -0
  204. miniworld_maze/core/miniworld_gymnasium/textures/portraits/lewis_john_frederick1.png +0 -0
  205. miniworld_maze/core/miniworld_gymnasium/textures/portraits/madrazo_garreta_raimundo1.license +2 -0
  206. miniworld_maze/core/miniworld_gymnasium/textures/portraits/madrazo_garreta_raimundo1.png +0 -0
  207. miniworld_maze/core/miniworld_gymnasium/textures/portraits/marie_bashkirtseff1.license +2 -0
  208. miniworld_maze/core/miniworld_gymnasium/textures/portraits/marie_bashkirtseff1.png +0 -0
  209. miniworld_maze/core/miniworld_gymnasium/textures/portraits/moritz_kellerhoven1.license +2 -0
  210. miniworld_maze/core/miniworld_gymnasium/textures/portraits/moritz_kellerhoven1.png +0 -0
  211. miniworld_maze/core/miniworld_gymnasium/textures/portraits/nathaniel_jocelyn1.license +2 -0
  212. miniworld_maze/core/miniworld_gymnasium/textures/portraits/nathaniel_jocelyn1.png +0 -0
  213. miniworld_maze/core/miniworld_gymnasium/textures/portraits/nikolai_alexandrowitsch_jaroschenko1.license +2 -0
  214. miniworld_maze/core/miniworld_gymnasium/textures/portraits/nikolai_alexandrowitsch_jaroschenko1.png +0 -0
  215. miniworld_maze/core/miniworld_gymnasium/textures/portraits/nils_johan_olsson_blommer1.license +2 -0
  216. miniworld_maze/core/miniworld_gymnasium/textures/portraits/nils_johan_olsson_blommer1.png +0 -0
  217. miniworld_maze/core/miniworld_gymnasium/textures/portraits/paolo_veronese1.license +2 -0
  218. miniworld_maze/core/miniworld_gymnasium/textures/portraits/paolo_veronese1.png +0 -0
  219. miniworld_maze/core/miniworld_gymnasium/textures/portraits/parmigianino1.license +2 -0
  220. miniworld_maze/core/miniworld_gymnasium/textures/portraits/parmigianino1.png +0 -0
  221. miniworld_maze/core/miniworld_gymnasium/textures/portraits/paul_cesar_helleu1.license +2 -0
  222. miniworld_maze/core/miniworld_gymnasium/textures/portraits/paul_cesar_helleu1.png +0 -0
  223. miniworld_maze/core/miniworld_gymnasium/textures/portraits/regnault_henri1.license +2 -0
  224. miniworld_maze/core/miniworld_gymnasium/textures/portraits/regnault_henri1.png +0 -0
  225. miniworld_maze/core/miniworld_gymnasium/textures/portraits/richard_bergh1.license +2 -0
  226. miniworld_maze/core/miniworld_gymnasium/textures/portraits/richard_bergh1.png +0 -0
  227. miniworld_maze/core/miniworld_gymnasium/textures/portraits/richard_bergh2.license +2 -0
  228. miniworld_maze/core/miniworld_gymnasium/textures/portraits/richard_bergh2.png +0 -0
  229. miniworld_maze/core/miniworld_gymnasium/textures/portraits/robert_dampier1.license +2 -0
  230. miniworld_maze/core/miniworld_gymnasium/textures/portraits/robert_dampier1.png +0 -0
  231. miniworld_maze/core/miniworld_gymnasium/textures/portraits/robert_lefevre1.license +2 -0
  232. miniworld_maze/core/miniworld_gymnasium/textures/portraits/robert_lefevre1.png +0 -0
  233. miniworld_maze/core/miniworld_gymnasium/textures/portraits/robert_leopold1.license +2 -0
  234. miniworld_maze/core/miniworld_gymnasium/textures/portraits/robert_leopold1.png +0 -0
  235. miniworld_maze/core/miniworld_gymnasium/textures/portraits/sichel_nathanael1.license +2 -0
  236. miniworld_maze/core/miniworld_gymnasium/textures/portraits/sichel_nathanael1.png +0 -0
  237. miniworld_maze/core/miniworld_gymnasium/textures/portraits/svetoslav_roerich1.license +2 -0
  238. miniworld_maze/core/miniworld_gymnasium/textures/portraits/svetoslav_roerich1.png +0 -0
  239. miniworld_maze/core/miniworld_gymnasium/textures/portraits/velazquez_diego1.license +2 -0
  240. miniworld_maze/core/miniworld_gymnasium/textures/portraits/velazquez_diego1.png +0 -0
  241. miniworld_maze/core/miniworld_gymnasium/textures/portraits/viktor_vasnetsov1.license +2 -0
  242. miniworld_maze/core/miniworld_gymnasium/textures/portraits/viktor_vasnetsov1.png +0 -0
  243. miniworld_maze/core/miniworld_gymnasium/textures/portraits/william-adolphe_bouguereau1.license +2 -0
  244. miniworld_maze/core/miniworld_gymnasium/textures/portraits/william-adolphe_bouguereau1.png +0 -0
  245. miniworld_maze/core/miniworld_gymnasium/textures/realblueberry_1.png +0 -0
  246. miniworld_maze/core/miniworld_gymnasium/textures/redbean_1.png +0 -0
  247. miniworld_maze/core/miniworld_gymnasium/textures/rock_1.png +0 -0
  248. miniworld_maze/core/miniworld_gymnasium/textures/seablue_1.png +0 -0
  249. miniworld_maze/core/miniworld_gymnasium/textures/silver_1.png +0 -0
  250. miniworld_maze/core/miniworld_gymnasium/textures/skyblue_1.png +0 -0
  251. miniworld_maze/core/miniworld_gymnasium/textures/slime_1.png +0 -0
  252. miniworld_maze/core/miniworld_gymnasium/textures/stucco_1.png +0 -0
  253. miniworld_maze/core/miniworld_gymnasium/textures/sunnyyellow_1.png +0 -0
  254. miniworld_maze/core/miniworld_gymnasium/textures/turquoise_1.png +0 -0
  255. miniworld_maze/core/miniworld_gymnasium/textures/violet_1.png +0 -0
  256. miniworld_maze/core/miniworld_gymnasium/textures/water_1.png +0 -0
  257. miniworld_maze/core/miniworld_gymnasium/textures/water_2.png +0 -0
  258. miniworld_maze/core/miniworld_gymnasium/textures/water_3.png +0 -0
  259. miniworld_maze/core/miniworld_gymnasium/textures/white_1.png +0 -0
  260. miniworld_maze/core/miniworld_gymnasium/textures/wood_1.png +0 -0
  261. miniworld_maze/core/miniworld_gymnasium/textures/wood_2.png +0 -0
  262. miniworld_maze/core/miniworld_gymnasium/textures/wood_planks_1.png +0 -0
  263. miniworld_maze/core/miniworld_gymnasium/unified_env.py +1364 -0
  264. miniworld_maze/core/miniworld_gymnasium/utils.py +37 -0
  265. miniworld_maze/core/miniworld_gymnasium/wrappers.py +2 -0
  266. miniworld_maze/core/observation_types.py +32 -0
  267. miniworld_maze/environments/__init__.py +16 -0
  268. miniworld_maze/environments/base_grid_rooms.py +204 -0
  269. miniworld_maze/environments/factory.py +155 -0
  270. miniworld_maze/environments/nine_rooms.py +74 -0
  271. miniworld_maze/environments/spiral_nine_rooms.py +70 -0
  272. miniworld_maze/environments/twenty_five_rooms.py +122 -0
  273. miniworld_maze/tools/__init__.py +5 -0
  274. miniworld_maze/tools/generate_observations.py +199 -0
  275. miniworld_maze/wrappers/__init__.py +5 -0
  276. miniworld_maze/wrappers/image_transforms.py +40 -0
  277. miniworld_maze-1.0.0.dist-info/METADATA +108 -0
  278. miniworld_maze-1.0.0.dist-info/RECORD +280 -0
  279. miniworld_maze-1.0.0.dist-info/WHEEL +4 -0
  280. 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