ClementRomac HF staff commited on
Commit
09a6f7f
1 Parent(s): a791ca7

Added interactive demo with some policies

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +0 -30
  2. base_envs_set.json +1 -0
  3. base_envs_set/00_Flat_parkour_+_Bipedal_Walker.json +1 -0
  4. base_envs_set/01_Easy_parkour_+_Chimpanzee.json +0 -0
  5. base_envs_set/02_Underwater_parkour_+_Fish.json +0 -0
  6. base_envs_set/03_Hard_parkour.json +0 -0
  7. demo.css +94 -0
  8. images/about/rl_demo_diagram_EN.png +0 -0
  9. images/about/rl_demo_diagram_FR.png +0 -0
  10. images/about/rl_diagram_fr.png +0 -0
  11. images/about/rl_diagram_transparent_bg.png +0 -0
  12. images/agents_thumbnails/bipedal_thumbnail.png +0 -0
  13. images/agents_thumbnails/chimpanzee_thumbnail.png +0 -0
  14. images/agents_thumbnails/fish_thumbnail.png +0 -0
  15. images/agents_thumbnails/spider_thumbnail.png +0 -0
  16. images/favicon.ico +0 -0
  17. index.html +609 -21
  18. index.js +661 -0
  19. js/Box2D_dynamics/climbing_dynamics.js +175 -0
  20. js/Box2D_dynamics/contact_detector.js +88 -0
  21. js/Box2D_dynamics/water_dynamics.js +244 -0
  22. js/CPPN/cppn.js +33 -0
  23. js/CPPN/weights/ground_cppn/.data-00000-of-00001 +0 -0
  24. js/CPPN/weights/ground_cppn/.index +0 -0
  25. js/CPPN/weights/ground_cppn/.meta +0 -0
  26. js/CPPN/weights/ground_cppn/checkpoint +3 -0
  27. js/CPPN/weights/same_ground_ceiling_cppn/.data-00000-of-00001 +0 -0
  28. js/CPPN/weights/same_ground_ceiling_cppn/.index +0 -0
  29. js/CPPN/weights/same_ground_ceiling_cppn/.meta +0 -0
  30. js/CPPN/weights/same_ground_ceiling_cppn/checkpoint +3 -0
  31. js/CPPN/weights/same_ground_ceiling_cppn/frozen_model/saved_model.pb +0 -0
  32. js/CPPN/weights/same_ground_ceiling_cppn/tfjs_model/group1-shard1of1.bin +3 -0
  33. js/CPPN/weights/same_ground_ceiling_cppn/tfjs_model/model.json +1 -0
  34. js/bodies/abstract_body.js +161 -0
  35. js/bodies/bodies_enum.js +7 -0
  36. js/bodies/climbers/climber_abstract_body.js +60 -0
  37. js/bodies/climbers/climbing_profile_chimpanzee.js +293 -0
  38. js/bodies/swimmers/fish_body.js +248 -0
  39. js/bodies/swimmers/swimmer_abstract_body.js +26 -0
  40. js/bodies/walkers/classic_bipedal_body.js +140 -0
  41. js/bodies/walkers/old_classic_bipedal_body.js +140 -0
  42. js/bodies/walkers/spider_body.js +189 -0
  43. js/bodies/walkers/walker_abstract_body.js +16 -0
  44. js/box2d.js +0 -0
  45. js/draw_p5js.js +592 -0
  46. js/envs/multi_agents_continuous_parkour.js +1264 -0
  47. js/game.js +165 -0
  48. js/i18n.js +599 -0
  49. js/ui_state/components/about_tab.js +54 -0
  50. js/ui_state/components/advanced_options.js +83 -0
.gitattributes CHANGED
@@ -1,31 +1 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
  *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ftz filter=lfs diff=lfs merge=lfs -text
6
- *.gz filter=lfs diff=lfs merge=lfs -text
7
- *.h5 filter=lfs diff=lfs merge=lfs -text
8
- *.joblib filter=lfs diff=lfs merge=lfs -text
9
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
10
- *.model filter=lfs diff=lfs merge=lfs -text
11
- *.msgpack filter=lfs diff=lfs merge=lfs -text
12
- *.npy filter=lfs diff=lfs merge=lfs -text
13
- *.npz filter=lfs diff=lfs merge=lfs -text
14
- *.onnx filter=lfs diff=lfs merge=lfs -text
15
- *.ot filter=lfs diff=lfs merge=lfs -text
16
- *.parquet filter=lfs diff=lfs merge=lfs -text
17
- *.pb filter=lfs diff=lfs merge=lfs -text
18
- *.pickle filter=lfs diff=lfs merge=lfs -text
19
- *.pkl filter=lfs diff=lfs merge=lfs -text
20
- *.pt filter=lfs diff=lfs merge=lfs -text
21
- *.pth filter=lfs diff=lfs merge=lfs -text
22
- *.rar filter=lfs diff=lfs merge=lfs -text
23
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
24
- *.tar.* filter=lfs diff=lfs merge=lfs -text
25
- *.tflite filter=lfs diff=lfs merge=lfs -text
26
- *.tgz filter=lfs diff=lfs merge=lfs -text
27
- *.wasm filter=lfs diff=lfs merge=lfs -text
28
- *.xz filter=lfs diff=lfs merge=lfs -text
29
- *.zip filter=lfs diff=lfs merge=lfs -text
30
- *.zstandard filter=lfs diff=lfs merge=lfs -text
31
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
1
  *.bin filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
base_envs_set.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"filenames": ["00_Flat_parkour_+_Bipedal_Walker.json", "01_Easy_parkour_+_Chimpanzee.json", "02_Underwater_parkour_+_Fish.json", "03_Hard_parkour.json"]}
base_envs_set/00_Flat_parkour_+_Bipedal_Walker.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"terrain":{"ground":[{"x":9.333333333333334,"y":3.3333333333333335},{"x":9.8,"y":3.3333333333333335},{"x":10.266666666666667,"y":3.3333333333333335},{"x":10.733333333333334,"y":3.3333333333333335},{"x":11.2,"y":3.3333333333333335},{"x":11.666666666666666,"y":3.3333333333333335},{"x":12.133333333333333,"y":3.3333333333333335},{"x":12.6,"y":3.3333333333333335},{"x":13.066666666666666,"y":3.3333333333333335},{"x":13.533333333333333,"y":3.3333333333333335},{"x":14,"y":3.3333333333333335},{"x":14.466666666666667,"y":3.3333333333333335},{"x":14.933333333333334,"y":3.3333333333333335},{"x":15.4,"y":3.3333333333333335},{"x":15.866666666666667,"y":3.3333333333333335},{"x":16.333333333333332,"y":3.3333333333333335},{"x":16.8,"y":3.3333333333333335},{"x":17.266666666666666,"y":3.3333333333333335},{"x":17.733333333333334,"y":3.3333333333333335},{"x":18.2,"y":3.3333333333333335},{"x":18.666666666666668,"y":3.3333333333333335},{"x":19.133333333333333,"y":3.3333333333333335},{"x":19.6,"y":3.3333333333333335},{"x":20.066666666666666,"y":3.3333333333333335},{"x":20.533333333333335,"y":3.3333333333333335},{"x":21,"y":3.3333333333333335},{"x":21.46666666666667,"y":3.3333333333333335},{"x":21.933333333333334,"y":3.3333333333333335},{"x":22.4,"y":3.3333333333333335},{"x":22.866666666666667,"y":3.3333333333333335},{"x":23.333333333333332,"y":3.3333333333333335},{"x":23.8,"y":3.3333333333333335},{"x":24.266666666666666,"y":3.3333333333333335},{"x":24.733333333333334,"y":3.3333333333333335},{"x":25.2,"y":3.3333333333333335},{"x":25.666666666666668,"y":3.3333333333333335},{"x":26.133333333333333,"y":3.3333333333333335},{"x":26.6,"y":3.3333333333333335},{"x":27.066666666666666,"y":3.3333333333333335},{"x":27.533333333333335,"y":3.3333333333333335},{"x":28,"y":3.3333333333333335},{"x":28.46666666666667,"y":3.3333333333333335},{"x":28.933333333333334,"y":3.3333333333333335},{"x":29.400000000000002,"y":3.3333333333333335},{"x":29.866666666666667,"y":3.3333333333333335},{"x":30.333333333333332,"y":3.3333333333333335},{"x":30.8,"y":3.3333333333333335},{"x":31.266666666666666,"y":3.3333333333333335},{"x":31.733333333333334,"y":3.3333333333333335},{"x":32.2,"y":3.3333333333333335},{"x":32.666666666666664,"y":3.3333333333333335},{"x":33.13333333333333,"y":3.3333333333333335},{"x":33.6,"y":3.3333333333333335},{"x":34.06666666666667,"y":3.3333333333333335},{"x":34.53333333333333,"y":3.3333333333333335},{"x":35,"y":3.3333333333333335},{"x":35.46666666666667,"y":3.3333333333333335},{"x":35.93333333333334,"y":3.3333333333333335},{"x":36.4,"y":3.3333333333333335},{"x":36.86666666666667,"y":3.3333333333333335},{"x":37.333333333333336,"y":3.3333333333333335},{"x":37.8,"y":3.3333333333333335},{"x":38.266666666666666,"y":3.3333333333333335},{"x":38.733333333333334,"y":3.3333333333333335},{"x":39.2,"y":3.3333333333333335},{"x":39.666666666666664,"y":3.3333333333333335},{"x":40.13333333333333,"y":3.3333333333333335},{"x":40.6,"y":3.3333333333333335},{"x":41.06666666666667,"y":3.3333333333333335},{"x":41.53333333333333,"y":3.3333333333333335},{"x":42,"y":3.3333333333333335},{"x":42.46666666666667,"y":3.3333333333333335},{"x":42.93333333333334,"y":3.3333333333333335},{"x":43.4,"y":3.3333333333333335},{"x":43.86666666666667,"y":3.3333333333333335},{"x":44.333333333333336,"y":3.3333333333333335},{"x":44.8,"y":3.3333333333333335},{"x":45.266666666666666,"y":3.3333333333333335},{"x":45.733333333333334,"y":3.3333333333333335},{"x":46.2,"y":3.3333333333333335},{"x":46.666666666666664,"y":3.3333333333333335},{"x":47.13333333333333,"y":3.3333333333333335},{"x":47.6,"y":3.3333333333333335},{"x":48.06666666666667,"y":3.3333333333333335},{"x":48.53333333333333,"y":3.3333333333333335},{"x":49,"y":3.3333333333333335},{"x":49.46666666666667,"y":3.3333333333333335},{"x":49.93333333333334,"y":3.3333333333333335},{"x":50.4,"y":3.3333333333333335},{"x":50.86666666666667,"y":3.3333333333333335},{"x":51.333333333333336,"y":3.3333333333333335},{"x":51.800000000000004,"y":3.3333333333333335},{"x":52.266666666666666,"y":3.3333333333333335},{"x":52.733333333333334,"y":3.3333333333333335},{"x":53.2,"y":3.3333333333333335},{"x":53.666666666666664,"y":3.3333333333333335},{"x":54.13333333333333,"y":3.3333333333333335},{"x":54.6,"y":3.3333333333333335},{"x":55.06666666666667,"y":3.3333333333333335},{"x":55.53333333333333,"y":3.3333333333333335},{"x":56,"y":3.3333333333333335},{"x":56.46666666666667,"y":3.3333333333333335},{"x":56.93333333333334,"y":3.3333333333333335},{"x":57.4,"y":3.3333333333333335},{"x":57.86666666666667,"y":3.3333333333333335},{"x":58.333333333333336,"y":3.3333333333333335},{"x":58.800000000000004,"y":3.3333333333333335},{"x":59.266666666666666,"y":3.3333333333333335},{"x":59.733333333333334,"y":3.3333333333333335},{"x":60.2,"y":3.3333333333333335},{"x":60.666666666666664,"y":3.3333333333333335},{"x":61.13333333333333,"y":3.3333333333333335},{"x":61.6,"y":3.3333333333333335},{"x":62.06666666666667,"y":3.3333333333333335},{"x":62.53333333333333,"y":3.3333333333333335},{"x":63,"y":3.3333333333333335},{"x":63.46666666666667,"y":3.3333333333333335},{"x":63.93333333333334,"y":3.3333333333333335},{"x":64.4,"y":3.3333333333333335},{"x":64.86666666666667,"y":3.3333333333333335},{"x":65.33333333333333,"y":3.3333333333333335},{"x":65.8,"y":3.3333333333333335},{"x":66.26666666666667,"y":3.3333333333333335},{"x":66.73333333333333,"y":3.3333333333333335},{"x":67.2,"y":3.3333333333333335},{"x":67.66666666666667,"y":3.3333333333333335},{"x":68.13333333333334,"y":3.3333333333333335},{"x":68.6,"y":3.3333333333333335},{"x":69.06666666666666,"y":3.3333333333333335},{"x":69.53333333333333,"y":3.3333333333333335},{"x":70,"y":3.3333333333333335},{"x":70.46666666666667,"y":3.3333333333333335},{"x":70.93333333333334,"y":3.3333333333333335},{"x":71.4,"y":3.3333333333333335},{"x":71.86666666666667,"y":3.3333333333333335},{"x":72.33333333333333,"y":3.3333333333333335},{"x":72.8,"y":3.3333333333333335},{"x":73.26666666666667,"y":3.3333333333333335},{"x":73.73333333333333,"y":3.3333333333333335},{"x":74.2,"y":3.3333333333333335},{"x":74.66666666666667,"y":3.3333333333333335},{"x":75.13333333333334,"y":3.3333333333333335},{"x":75.6,"y":3.3333333333333335},{"x":76.06666666666666,"y":3.3333333333333335},{"x":76.53333333333333,"y":3.3333333333333335},{"x":77,"y":3.3333333333333335},{"x":77.46666666666667,"y":3.3333333333333335},{"x":77.93333333333334,"y":3.3333333333333335},{"x":78.4,"y":3.3333333333333335},{"x":78.86666666666667,"y":3.3333333333333335},{"x":79.33333333333333,"y":3.3333333333333335},{"x":79.8,"y":3.3333333333333335},{"x":80.26666666666667,"y":3.3333333333333335},{"x":80.73333333333333,"y":3.3333333333333335},{"x":81.2,"y":3.3333333333333335},{"x":81.66666666666667,"y":3.3333333333333335},{"x":82.13333333333334,"y":3.3333333333333335},{"x":82.6,"y":3.3333333333333335},{"x":83.06666666666666,"y":3.3333333333333335},{"x":83.53333333333333,"y":3.3333333333333335},{"x":84,"y":3.3333333333333335},{"x":84.46666666666667,"y":3.3333333333333335},{"x":84.93333333333334,"y":3.3333333333333335},{"x":85.4,"y":3.3333333333333335},{"x":85.86666666666667,"y":3.3333333333333335},{"x":86.33333333333333,"y":3.3333333333333335},{"x":86.8,"y":3.3333333333333335},{"x":87.26666666666667,"y":3.3333333333333335},{"x":87.73333333333333,"y":3.3333333333333335},{"x":88.2,"y":3.3333333333333335},{"x":88.66666666666667,"y":3.3333333333333335},{"x":89.13333333333334,"y":3.3333333333333335},{"x":89.6,"y":3.3333333333333335},{"x":90.06666666666666,"y":3.3333333333333335},{"x":90.53333333333333,"y":3.3333333333333335},{"x":91,"y":3.3333333333333335},{"x":91.46666666666667,"y":3.3333333333333335},{"x":91.93333333333334,"y":3.3333333333333335},{"x":92.4,"y":3.3333333333333335},{"x":92.86666666666667,"y":3.3333333333333335},{"x":93.33333333333333,"y":3.3333333333333335},{"x":93.8,"y":3.3333333333333335},{"x":94.26666666666667,"y":3.3333333333333335},{"x":94.73333333333333,"y":3.3333333333333335},{"x":95.2,"y":3.3333333333333335},{"x":95.66666666666667,"y":3.3333333333333335},{"x":96.13333333333334,"y":3.3333333333333335},{"x":96.60000000000001,"y":3.3333333333333335},{"x":97.06666666666666,"y":3.3333333333333335},{"x":97.53333333333333,"y":3.3333333333333335},{"x":98,"y":3.3333333333333335},{"x":98.46666666666667,"y":3.3333333333333335},{"x":98.93333333333334,"y":3.3333333333333335},{"x":99.4,"y":3.3333333333333335},{"x":99.86666666666667,"y":3.3333333333333335},{"x":100.33333333333333,"y":3.3333333333333335},{"x":100.8,"y":3.3333333333333335},{"x":101.26666666666667,"y":3.3333333333333335},{"x":101.73333333333333,"y":3.3333333333333335},{"x":102.2,"y":3.3333333333333335},{"x":102.66666666666667,"y":3.3333333333333335}],"ceiling":[{"x":9.333333333333334,"y":16.666666666666668},{"x":9.8,"y":16.666666666666668},{"x":10.266666666666667,"y":16.666666666666668},{"x":10.733333333333334,"y":16.666666666666668},{"x":11.2,"y":16.666666666666668},{"x":11.666666666666666,"y":16.666666666666668},{"x":12.133333333333333,"y":16.666666666666668},{"x":12.6,"y":16.666666666666668},{"x":13.066666666666666,"y":16.666666666666668},{"x":13.533333333333333,"y":16.666666666666668},{"x":14,"y":16.666666666666668},{"x":14.466666666666667,"y":16.666666666666668},{"x":14.933333333333334,"y":16.666666666666668},{"x":15.4,"y":16.666666666666668},{"x":15.866666666666667,"y":16.666666666666668},{"x":16.333333333333332,"y":16.666666666666668},{"x":16.8,"y":16.666666666666668},{"x":17.266666666666666,"y":16.666666666666668},{"x":17.733333333333334,"y":16.666666666666668},{"x":18.2,"y":16.666666666666668},{"x":18.666666666666668,"y":16.666666666666668},{"x":19.133333333333333,"y":16.666666666666668},{"x":19.6,"y":16.666666666666668},{"x":20.066666666666666,"y":16.666666666666668},{"x":20.533333333333335,"y":16.666666666666668},{"x":21,"y":16.666666666666668},{"x":21.46666666666667,"y":16.666666666666668},{"x":21.933333333333334,"y":16.666666666666668},{"x":22.4,"y":16.666666666666668},{"x":22.866666666666667,"y":16.666666666666668},{"x":23.333333333333332,"y":16.666666666666668},{"x":23.8,"y":16.666666666666668},{"x":24.266666666666666,"y":16.666666666666668},{"x":24.733333333333334,"y":16.666666666666668},{"x":25.2,"y":16.666666666666668},{"x":25.666666666666668,"y":16.666666666666668},{"x":26.133333333333333,"y":16.666666666666668},{"x":26.6,"y":16.666666666666668},{"x":27.066666666666666,"y":16.666666666666668},{"x":27.533333333333335,"y":16.666666666666668},{"x":28,"y":16.666666666666668},{"x":28.46666666666667,"y":16.666666666666668},{"x":28.933333333333334,"y":16.666666666666668},{"x":29.400000000000002,"y":16.666666666666668},{"x":29.866666666666667,"y":16.666666666666668},{"x":30.333333333333332,"y":16.666666666666668},{"x":30.8,"y":16.666666666666668},{"x":31.266666666666666,"y":16.666666666666668},{"x":31.733333333333334,"y":16.666666666666668},{"x":32.2,"y":16.666666666666668},{"x":32.666666666666664,"y":16.666666666666668},{"x":33.13333333333333,"y":16.666666666666668},{"x":33.6,"y":16.666666666666668},{"x":34.06666666666667,"y":16.666666666666668},{"x":34.53333333333333,"y":16.666666666666668},{"x":35,"y":16.666666666666668},{"x":35.46666666666667,"y":16.666666666666668},{"x":35.93333333333334,"y":16.666666666666668},{"x":36.4,"y":16.666666666666668},{"x":36.86666666666667,"y":16.666666666666668},{"x":37.333333333333336,"y":16.666666666666668},{"x":37.8,"y":16.666666666666668},{"x":38.266666666666666,"y":16.666666666666668},{"x":38.733333333333334,"y":16.666666666666668},{"x":39.2,"y":16.666666666666668},{"x":39.666666666666664,"y":16.666666666666668},{"x":40.13333333333333,"y":16.666666666666668},{"x":40.6,"y":16.666666666666668},{"x":41.06666666666667,"y":16.666666666666668},{"x":41.53333333333333,"y":16.666666666666668},{"x":42,"y":16.666666666666668},{"x":42.46666666666667,"y":16.666666666666668},{"x":42.93333333333334,"y":16.666666666666668},{"x":43.4,"y":16.666666666666668},{"x":43.86666666666667,"y":16.666666666666668},{"x":44.333333333333336,"y":16.666666666666668},{"x":44.8,"y":16.666666666666668},{"x":45.266666666666666,"y":16.666666666666668},{"x":45.733333333333334,"y":16.666666666666668},{"x":46.2,"y":16.666666666666668},{"x":46.666666666666664,"y":16.666666666666668},{"x":47.13333333333333,"y":16.666666666666668},{"x":47.6,"y":16.666666666666668},{"x":48.06666666666667,"y":16.666666666666668},{"x":48.53333333333333,"y":16.666666666666668},{"x":49,"y":16.666666666666668},{"x":49.46666666666667,"y":16.666666666666668},{"x":49.93333333333334,"y":16.666666666666668},{"x":50.4,"y":16.666666666666668},{"x":50.86666666666667,"y":16.666666666666668},{"x":51.333333333333336,"y":16.666666666666668},{"x":51.800000000000004,"y":16.666666666666668},{"x":52.266666666666666,"y":16.666666666666668},{"x":52.733333333333334,"y":16.666666666666668},{"x":53.2,"y":16.666666666666668},{"x":53.666666666666664,"y":16.666666666666668},{"x":54.13333333333333,"y":16.666666666666668},{"x":54.6,"y":16.666666666666668},{"x":55.06666666666667,"y":16.666666666666668},{"x":55.53333333333333,"y":16.666666666666668},{"x":56,"y":16.666666666666668},{"x":56.46666666666667,"y":16.666666666666668},{"x":56.93333333333334,"y":16.666666666666668},{"x":57.4,"y":16.666666666666668},{"x":57.86666666666667,"y":16.666666666666668},{"x":58.333333333333336,"y":16.666666666666668},{"x":58.800000000000004,"y":16.666666666666668},{"x":59.266666666666666,"y":16.666666666666668},{"x":59.733333333333334,"y":16.666666666666668},{"x":60.2,"y":16.666666666666668},{"x":60.666666666666664,"y":16.666666666666668},{"x":61.13333333333333,"y":16.666666666666668},{"x":61.6,"y":16.666666666666668},{"x":62.06666666666667,"y":16.666666666666668},{"x":62.53333333333333,"y":16.666666666666668},{"x":63,"y":16.666666666666668},{"x":63.46666666666667,"y":16.666666666666668},{"x":63.93333333333334,"y":16.666666666666668},{"x":64.4,"y":16.666666666666668},{"x":64.86666666666667,"y":16.666666666666668},{"x":65.33333333333333,"y":16.666666666666668},{"x":65.8,"y":16.666666666666668},{"x":66.26666666666667,"y":16.666666666666668},{"x":66.73333333333333,"y":16.666666666666668},{"x":67.2,"y":16.666666666666668},{"x":67.66666666666667,"y":16.666666666666668},{"x":68.13333333333334,"y":16.666666666666668},{"x":68.6,"y":16.666666666666668},{"x":69.06666666666666,"y":16.666666666666668},{"x":69.53333333333333,"y":16.666666666666668},{"x":70,"y":16.666666666666668},{"x":70.46666666666667,"y":16.666666666666668},{"x":70.93333333333334,"y":16.666666666666668},{"x":71.4,"y":16.666666666666668},{"x":71.86666666666667,"y":16.666666666666668},{"x":72.33333333333333,"y":16.666666666666668},{"x":72.8,"y":16.666666666666668},{"x":73.26666666666667,"y":16.666666666666668},{"x":73.73333333333333,"y":16.666666666666668},{"x":74.2,"y":16.666666666666668},{"x":74.66666666666667,"y":16.666666666666668},{"x":75.13333333333334,"y":16.666666666666668},{"x":75.6,"y":16.666666666666668},{"x":76.06666666666666,"y":16.666666666666668},{"x":76.53333333333333,"y":16.666666666666668},{"x":77,"y":16.666666666666668},{"x":77.46666666666667,"y":16.666666666666668},{"x":77.93333333333334,"y":16.666666666666668},{"x":78.4,"y":16.666666666666668},{"x":78.86666666666667,"y":16.666666666666668},{"x":79.33333333333333,"y":16.666666666666668},{"x":79.8,"y":16.666666666666668},{"x":80.26666666666667,"y":16.666666666666668},{"x":80.73333333333333,"y":16.666666666666668},{"x":81.2,"y":16.666666666666668},{"x":81.66666666666667,"y":16.666666666666668},{"x":82.13333333333334,"y":16.666666666666668},{"x":82.6,"y":16.666666666666668},{"x":83.06666666666666,"y":16.666666666666668},{"x":83.53333333333333,"y":16.666666666666668},{"x":84,"y":16.666666666666668},{"x":84.46666666666667,"y":16.666666666666668},{"x":84.93333333333334,"y":16.666666666666668},{"x":85.4,"y":16.666666666666668},{"x":85.86666666666667,"y":16.666666666666668},{"x":86.33333333333333,"y":16.666666666666668},{"x":86.8,"y":16.666666666666668},{"x":87.26666666666667,"y":16.666666666666668},{"x":87.73333333333333,"y":16.666666666666668},{"x":88.2,"y":16.666666666666668},{"x":88.66666666666667,"y":16.666666666666668},{"x":89.13333333333334,"y":16.666666666666668},{"x":89.6,"y":16.666666666666668},{"x":90.06666666666666,"y":16.666666666666668},{"x":90.53333333333333,"y":16.666666666666668},{"x":91,"y":16.666666666666668},{"x":91.46666666666667,"y":16.666666666666668},{"x":91.93333333333334,"y":16.666666666666668},{"x":92.4,"y":16.666666666666668},{"x":92.86666666666667,"y":16.666666666666668},{"x":93.33333333333333,"y":16.666666666666668},{"x":93.8,"y":16.666666666666668},{"x":94.26666666666667,"y":16.666666666666668},{"x":94.73333333333333,"y":16.666666666666668},{"x":95.2,"y":16.666666666666668},{"x":95.66666666666667,"y":16.666666666666668},{"x":96.13333333333334,"y":16.666666666666668},{"x":96.60000000000001,"y":16.666666666666668},{"x":97.06666666666666,"y":16.666666666666668},{"x":97.53333333333333,"y":16.666666666666668},{"x":98,"y":16.666666666666668},{"x":98.46666666666667,"y":16.666666666666668},{"x":98.93333333333334,"y":16.666666666666668},{"x":99.4,"y":16.666666666666668},{"x":99.86666666666667,"y":16.666666666666668},{"x":100.33333333333333,"y":16.666666666666668},{"x":100.8,"y":16.666666666666668},{"x":101.26666666666667,"y":16.666666666666668},{"x":101.73333333333333,"y":16.666666666666668},{"x":102.2,"y":16.666666666666668}],"parkourConfig":{"dim1":0,"dim2":0,"dim3":0,"smoothing":15,"waterLevel":0},"creepersConfig":{"width":0.2,"height":0.2,"spacing":5,"type":"Rigid"}},"agents":[{"morphology":"bipedal","name":"Joe","age": "adult","path":"policy_models/walker/bipedal/16-02_old_walker_parkour_student_sac_v0.1.1_teacher_ALP-GMM_s1","init_pos":{"x":4.664080407626852,"y":5.7120234541018675}}],"description":{"EN": {"name":"Flat parkour + Bipedal Walker","text":"This parkour is completely flat, perfect for bipedal walkers."}, "FR": {"name":"Parkour plat + Bipède","text":"Ce parkour est totalement plat, parfait pour les agents bipèdes."}},"image":"data:image/octet-stream;base64,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QIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5ObcEYbAAAFzklEQVQVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAuLp2cOdS4AAAQIECBAgQIAAAQIECBAgQIBAExBPm5MVAQIECBAgQIAAAQIECBAgQIAAAQJnAgPdBsAyo4QHGwAAAABJRU5ErkJggg=="}
base_envs_set/01_Easy_parkour_+_Chimpanzee.json ADDED
The diff for this file is too large to render. See raw diff
 
base_envs_set/02_Underwater_parkour_+_Fish.json ADDED
The diff for this file is too large to render. See raw diff
 
base_envs_set/03_Hard_parkour.json ADDED
The diff for this file is too large to render. See raw diff
 
demo.css ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .ib{
2
+ display: inline-block;
3
+ margin-right: 30px;
4
+ }
5
+
6
+ #creepers{
7
+ position: relative;
8
+ bottom: 25px;
9
+ }
10
+
11
+ .btn-asset{
12
+ background-color: #885C00;
13
+ color: white;
14
+ }
15
+
16
+ btn-asset:hover{
17
+ background-color: #885C00;
18
+ color: white;
19
+ }
20
+
21
+ .btn-outline-asset{
22
+ background-color: white;
23
+ color: #885C00;
24
+ border-color: #885C00;
25
+ }
26
+
27
+ .btn-outline-asset:hover{
28
+ background-color: #885C00;
29
+ color: white;
30
+ }
31
+
32
+ #agents_list_container{
33
+ height: 400px;
34
+ overflow-y: scroll;
35
+ }
36
+
37
+ .list-group-item{
38
+ border-color: white;
39
+ }
40
+
41
+ .list-group-item.active{
42
+ background-color: #FFC700;
43
+ border-color: #FFC700;
44
+ }
45
+
46
+ .list-group-item.disabled{
47
+ background-color: lightgrey;
48
+ border-color: lightgrey;
49
+ }
50
+
51
+ hr.solid {
52
+ border-top: 2px solid #999;
53
+ }
54
+
55
+ .card > img{
56
+ height: 125px;
57
+ object-fit: cover;
58
+ object-position: 5% 0%;
59
+ }
60
+
61
+ .introjs-tooltip {
62
+ font-size: 20px;
63
+ min-width: 400px;
64
+ max-width: 600px;
65
+ }
66
+
67
+ .introjs-tooltip-title {
68
+ font-size: 22px;
69
+ color: #0a41c9;
70
+ }
71
+
72
+ .introjs-hintReference > .introjs-tooltip{
73
+ font-size: 16px;
74
+ }
75
+
76
+ .introjs-tooltip ul {
77
+ margin-left: 8px;
78
+ list-style-type: disc;
79
+ }
80
+
81
+ .nav-link {
82
+ color: black;
83
+ font-weight: bold;
84
+ }
85
+
86
+ .about-text {
87
+ font-size: 18px;
88
+ }
89
+
90
+ .about-subsection {
91
+ font-size: 26px;
92
+ font-weight: bold;
93
+ color: #0a41c9;
94
+ }
images/about/rl_demo_diagram_EN.png ADDED
images/about/rl_demo_diagram_FR.png ADDED
images/about/rl_diagram_fr.png ADDED
images/about/rl_diagram_transparent_bg.png ADDED
images/agents_thumbnails/bipedal_thumbnail.png ADDED
images/agents_thumbnails/chimpanzee_thumbnail.png ADDED
images/agents_thumbnails/fish_thumbnail.png ADDED
images/agents_thumbnails/spider_thumbnail.png ADDED
images/favicon.ico ADDED
index.html CHANGED
@@ -1,24 +1,612 @@
1
  <!DOCTYPE html>
2
  <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>
13
- You can modify this app directly by editing <i>index.html</i> in the
14
- Files and versions tab.
15
- </p>
16
- <p>
17
- Also don't forget to check the
18
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank"
19
- >Spaces documentation</a
20
- >.
21
- </p>
22
- </div>
23
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  </html>
 
1
  <!DOCTYPE html>
2
  <html>
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width, initial-zoom=1">
7
+ <title>Interactive Deep Reinforcement Learning Demo</title>
8
+ <link rel="icon" href="images/favicon.ico" />
9
+
10
+ <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
11
+
12
+ <!-- Bootstrap CSS -->
13
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
14
+ integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x"
15
+ crossorigin="anonymous">
16
+
17
+ <!-- Bootstrap JS -->
18
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
19
+ integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4"
20
+ crossorigin="anonymous"></script>
21
+
22
+ <script defer src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js"></script>
23
+ <script defer src="https://cdn.jsdelivr.net/npm/[email protected]/lib/p5.js"></script>
24
+ <script src=//cdnjs.cloudflare.com/ajax/libs/seedrandom/2.3.10/seedrandom.min.js></script>
25
+
26
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/intro.min.js"></script>
27
+
28
+ <!--SCRIPTS DEPENDENCIES-->
29
+ <script defer src="./js/box2d.js"></script>
30
+
31
+ <script defer src="js/Box2D_dynamics/water_dynamics.js"></script>
32
+ <script defer src="js/Box2D_dynamics/climbing_dynamics.js"></script>
33
+ <script defer src="js/Box2D_dynamics/contact_detector.js"></script>
34
+ <script defer src="./js/utils/custom_user_data.js"></script>
35
+
36
+ <!-- Morphologies -->
37
+ <script defer src="./js/bodies/bodies_enum.js"></script>
38
+ <script defer src="./js/bodies/abstract_body.js"></script>
39
+ <script defer src="js/bodies/walkers/walker_abstract_body.js"></script>
40
+ <script defer src="js/bodies/walkers/classic_bipedal_body.js"></script>
41
+ <script defer src="js/bodies/walkers/spider_body.js"></script>
42
+ <script defer src="js/bodies/climbers/climber_abstract_body.js"></script>
43
+ <script defer src="js/bodies/climbers/climbing_profile_chimpanzee.js"></script>
44
+ <script defer src="js/bodies/swimmers/swimmer_abstract_body.js"></script>
45
+ <script defer src="js/bodies/swimmers/fish_body.js"></script>
46
+
47
+ <script defer src="js/CPPN/cppn.js"></script>
48
+ <script defer src="js/envs/multi_agents_continuous_parkour.js"></script>
49
+ <script defer src="js/game.js"></script>
50
+ <script defer src="js/draw_p5js.js"></script>
51
+ <script defer src="js/i18n.js"></script>
52
+
53
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css">
54
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
55
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/introjs.css">
56
+ <link rel="stylesheet" href="./demo.css">
57
+ </head>
58
+
59
+ <body>
60
+
61
+ <div class="row justify-content-between g-2 mt-2 mb-3">
62
+ <div class="col-9 col-xxl-11">
63
+ <h1 id="demoTitle" class="title has-text-centered align-items-center">Interactive Deep Reinforcement Learning Demo</h1>
64
+ </div>
65
+ <div class="col-auto col-xxl-1">
66
+ <select id="langSelect" class="form-select">
67
+ <option value="EN">🇬🇧 English</option>
68
+ <option value="FR">🇫🇷 Français</option>
69
+ </select>
70
+ </div>
71
+ </div>
72
+
73
+ <div class="container-fluid">
74
+
75
+ <div class="row justify-content-md-left g-2">
76
+ <div class="col-12 col-md-9 " id="canvas-and-main-buttons">
77
+
78
+ <!-- Canvas -->
79
+ <div id="canvas_container"></div>
80
+
81
+ <!-- Main buttons row -->
82
+ <div id="mainButtons" class="row justify-content-center">
83
+ <div class="col-auto py-2 px-1">
84
+ <button id="runButton" class="btn btn-success" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Run the simulation"><i class="fas fa-play"></i></button>
85
+ </div>
86
+ <div class="col-auto py-2 px-1">
87
+ <button id="resetButton" class="btn btn-danger" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Reset the simulation"><i class="fas fa-undo-alt fa-lg"></i></button>
88
+ </div>
89
+
90
+ <div class="col-auto py-2 px-1">
91
+ <span data-toggle="modal" data-target="#saveEnvModal">
92
+ <button id="saveEnvButton" class="btn btn-primary mx-3" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Save the current environment">
93
+ <i class="far fa-save fa-lg"></i>
94
+ </button>
95
+ </span>
96
+ </div>
97
+ </div>
98
+ </div>
99
+
100
+ <!-- List of running agents -->
101
+ <div id="agents_list_container" class="col-12 col-md border border-secondary rounded">
102
+ <h1 class="has-text-centered my-2" id="agents_list_title"><strong> List of running agents </strong></h1>
103
+ <ol class="list-group" id="agents_list"></ol>
104
+ </div>
105
+ </div>
106
+
107
+ <!-- Dialog box for saving current environment -->
108
+ <div class="modal fade" id="saveEnvModal" tabindex="-1" role="dialog" aria-labelledby="saveEnvModalLabel" aria-hidden="true">
109
+ <div class="modal-dialog" role="document">
110
+ <div class="modal-content">
111
+ <div class="modal-header">
112
+ <h1 class="modal-title" id="save-modal-title"><strong>Please enter a name and a description for the current environment</strong>.</h1>
113
+ <button type="button" class="btn close" data-dismiss="modal" aria-label="Close">
114
+ <i class="fas fa-times"></i>
115
+ </button>
116
+ </div>
117
+ <div class="modal-body">
118
+ <p id="save-modal-text" class="modal-text">This environment will be saved in your collection of custom environments so that you could reload it later or download it to share it.</p>
119
+ <form>
120
+ <div class="form-group">
121
+ <label id="env-name-label" for="env-name" class="col-form-label">Name:</label>
122
+ <input type="text" class="form-control text-field" id="env-name">
123
+ </div>
124
+ <div class="form-group">
125
+ <label id="env-description-label" for="env-description" class="col-form-label">Description:</label>
126
+ <textarea class="form-control text-field" id="env-description"></textarea>
127
+ </div>
128
+ </form>
129
+ </div>
130
+ <div class="modal-footer">
131
+ <button id="save-cancel-btn" type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
132
+ <button id="save-confirm-btn" type="button" class="btn btn-primary">Save</button>
133
+ </div>
134
+ </div>
135
+ </div>
136
+ </div>
137
+
138
+ <div class="row mt-1">
139
+ <div class="col-10">
140
+ <!-- Nav tabs -->
141
+ <ul class="nav nav-tabs bg-light nav-fill" id="tabs-buttons" role="tablist">
142
+ <li class="nav-item" role="presentation">
143
+ <button class="nav-link active" id="getting-started-btn" data-bs-toggle="tab"
144
+ data-bs-target="#getting-started-tab" type="button" role="tab" aria-controls="getting-started-tab"
145
+ aria-selected="true"><strong>Getting Started</strong></button>
146
+ </li>
147
+ <li class="nav-item" role="presentation">
148
+ <button class="nav-link" id="parkour-custom-btn" data-bs-toggle="tab"
149
+ data-bs-target="#parkour-custom-tab" type="button" role="tab" aria-controls="parkour-custom-tab">
150
+ <strong>Parkour Customization</strong>
151
+ </button>
152
+ </li>
153
+ <li class="nav-item" role="presentation">
154
+ <button class="nav-link" id="advanced-options-btn" data-bs-toggle="tab" data-bs-target="#advanced-options-tab"
155
+ type="button" role="tab" aria-controls="advanced-options-tab" aria-selected="false">
156
+ <strong>Advanced Options</strong>
157
+ </button>
158
+ </li>
159
+ <li class="nav-item" role="presentation">
160
+ <button class="nav-link" id="about-btn" data-bs-toggle="tab" data-bs-target="#about-tab"
161
+ type="button" role="tab" aria-controls="about-tab" aria-selected="false">
162
+ <i class="fas fa-info-circle fa-lg"></i> <strong>About...</strong>
163
+ </button>
164
+ </li>
165
+ </ul>
166
+
167
+ <!-- Tab panes -->
168
+ <div class="tab-content">
169
+
170
+ <!-- Getting Started tab pane -->
171
+ <div class="tab-pane active" id="getting-started-tab" role="tabpanel" aria-labelledby="getting-started-btn">
172
+
173
+ <p id="baseSetText" class="mt-3 has-text-centered">To begin you can select one of the following environments to load it into the simulation.</p>
174
+
175
+ <div id="baseEnvsSet" class="row row-cols-2 row-cols-md-4 my-3">
176
+
177
+ </div>
178
+
179
+ <hr class="solid">
180
+ <div id="customSetSection">
181
+ <p id="customSetText" class="has-text-centered">In this section you can store your own custom environments by saving them thanks to the <span style="color: blue"><i
182
+ class="far fa-save fa-lg"></i></span> button above or by uploading them from a JSON file.</p>
183
+
184
+ <div id="customEnvsSet" class="row row-cols-2 row-cols-md-4 my-3">
185
+ </div>
186
+
187
+
188
+
189
+ </div>
190
+ </div>
191
+
192
+ <!-- Parkour Customization tab pane -->
193
+ <div class="tab-pane" id="parkour-custom-tab" role="tab-panel" aria-labelledby="parkour-custom-btn">
194
+
195
+ <div class="row justify-content-center">
196
+ <div class="col-2"></div>
197
+ <div class="col">
198
+ <h1 id="terrain-generation-title" class="has-text-centered mt-2"><strong> Terrain Generation </strong></h1>
199
+ </div>
200
+ </div>
201
+
202
+ <div class="row justify-content-center">
203
+ <div class="col-2">
204
+ <div class="nav flex-column nav-pills" id="parkour-pills-tab" role="tablist" aria-orientation="vertical">
205
+ <button class="nav-link active my-2 border border-primary" id="draw-tab-btn" data-bs-toggle="pill" data-bs-target="#draw-tab"
206
+ type="button" role="tab" aria-controls="draw-tab" aria-selected="true"> Draw Yourself! </button>
207
+
208
+ <hr class="solid">
209
+
210
+ <button class="nav-link my-2 border border-primary" id="proc-gen-tab-btn" data-bs-toggle="pill" data-bs-target="#proc-gen-tab"
211
+ type="button" role="tab" aria-controls="proc-gen-tab" aria-selected="false"> Procedural Generation </button>
212
+ </div>
213
+ </div>
214
+
215
+ <div class="col">
216
+ <div class="tab-content" id="parkour-vpills-tabcontent">
217
+ <div class="tab-pane fade show active" id="draw-tab" role="tabpanel" aria-labelledby="draw-tab-btn">
218
+ <!-- Draw Yourself! tab pane -->
219
+ <div class="mx-4 mt-2">
220
+ <p id="drawingIntro" class="has-text-centered">
221
+ Here you can draw your own parkour!
222
+ </p>
223
+ <div id="drawingMode" class="row justify-content-md-center g-2 my-2">
224
+ <div class="col-auto">
225
+ <button id="drawGroundButton" class="btn btn-outline-success disabled"><i class="fas fa-pencil-alt"></i> Ground </button>
226
+ </div>
227
+ <div class="col-auto">
228
+ <button id="drawCeilingButton" class="btn btn-outline-secondary disabled"><i class="fas fa-pencil-alt"></i> Ceiling </button>
229
+ </div>
230
+ <div class="col-auto">
231
+ <button id="eraseButton" class="btn btn-outline-warning disabled"><i class="fas fa-eraser"></i>
232
+ Erase </button>
233
+ </div>
234
+ <div class="col-auto">
235
+ <button id="clearButton" class="btn btn-danger"><i class="fas fa-times"></i> Clear </button>
236
+ </div>
237
+ <div class="col-auto">
238
+ <button id="generateTerrainButton" class="btn btn-success disabled"> Generate Terrain </button>
239
+ </div>
240
+ </div>
241
+
242
+ <p id="drawingText" class="has-text-centered my-2">
243
+ Select the <strong style="color: green"><i class="fas fa-pencil-alt"></i> Ground</strong> or <strong style="color: dimgrey"><i class="fas fa-pencil-alt"></i> Ceiling</strong> button to start drawing the corresponding terrain shape with the mouse.<br>
244
+ Be careful not to draw more than one line at different heights if you want the result to be optimal.
245
+ You can use the <strong style="color: #FFC700"><i class="fas fa-eraser"></i> Erase</strong> button if you need to correct your drawing or the <strong style="color: red"><i class="fas fa-times"></i> Clear</strong> one to clear all your drawing.<br>
246
+ When you are satisfied with the result, just click the <strong style="color: green">Generate Terrain</strong> button.
247
+ </p>
248
+ </div>
249
+ </div>
250
+
251
+ <div class="tab-pane" id="proc-gen-tab" role="tabpanel" aria-labelledby="proc-gen-tab-btn">
252
+ <!-- Procedural Generation tab pane-->
253
+ <div class="my-2 mx-1">
254
+ <p id="proc-gen-text" class="has-text-centered">
255
+ You can also use these three sliders to generate the <strong>terrain shapes</strong> automatically.
256
+ </p>
257
+ </div>
258
+
259
+ <div class="row justify-content-center mx-1 my-3">
260
+ <div class="row">
261
+ <div class="col-11">
262
+ <input type="range" class="form-range" min="-1" max="1" step="0.01" id="dim1Slider">
263
+ </div>
264
+ <div class="col">
265
+ <span id="dim1Value"></span>
266
+ </div>
267
+ </div>
268
+ <div class="row">
269
+ <div class="col-11">
270
+ <input type="range" class="form-range" min="-1" max="1" step="0.01" id="dim2Slider">
271
+ </div>
272
+ <div class="col">
273
+ <span id="dim2Value"></span>
274
+ </div>
275
+ </div>
276
+ <div class="row">
277
+ <div class="col-11">
278
+ <input type="range" class="form-range" min="-1" max="1" step="0.01" id="dim3Slider">
279
+ </div>
280
+ <div class="col">
281
+ <span id="dim3Value"></span>
282
+ </div>
283
+ </div>
284
+ </div>
285
+ </div>
286
+ </div>
287
+ </div>
288
+ </div>
289
+
290
+ <hr class="solid">
291
+
292
+ <div class="row justify-content-center mb-3">
293
+
294
+ <!-- Smoothing + Water level sliders -->
295
+ <div class="col-6 border border-top-0 border-start-0 border-bottom-0">
296
+ <div class="row justify-content-center mb-4">
297
+ <strong id="general-parameters-title" class="has-text-centered">General parameters</strong>
298
+ </div>
299
+
300
+ <div class="row justify-content-center">
301
+ <div class="col-auto">
302
+ <label id="smoothing-label" for="smoothingSlider" class="form-label">Smoothing</label>
303
+ </div>
304
+ <div class="col-9">
305
+ <input type="range" class="form-range" min="10" max="40" value="20" step="0.01"
306
+ id="smoothingSlider">
307
+ </div>
308
+ <div class="col-1 p-0">
309
+ <span id="smoothingValue"></span>
310
+ </div>
311
+ </div>
312
+
313
+ <div class="row justify-content-center">
314
+
315
+ <div class="col-auto">
316
+ <label id="water-level-label" for="waterSlider" class="form-label">Water level</label>
317
+ </div>
318
+ <div class="col-9">
319
+ <input type="range" class="form-range" min="0" max="1" step="0.01" value="0"
320
+ id="waterSlider">
321
+ </div>
322
+ <div class="col-1 p-0">
323
+ <span id="waterValue"></span>
324
+ </div>
325
+ </div>
326
+ </div>
327
+
328
+ <!-- Creepers parameters -->
329
+ <div class="col">
330
+ <div class="row justify-content-center align-items-center mb-2">
331
+ <div class="col-auto">
332
+ <strong id="creepers-title" class="has-text-centered">Creepers</strong>
333
+ </div>
334
+
335
+ <div class="col-auto">
336
+ <select id="creepersType" class="form-select">
337
+ <option id="rigid-otpion" value="Rigid">Rigid</option>
338
+ <option id="swingable-option" value="Swingable">Swingable</option>
339
+ </select>
340
+ </div>
341
+ </div>
342
+
343
+ <div class="row justify-content-center">
344
+ <div class="col-auto">
345
+ <label id="creepers-width-label" for="creepersWidthSlider" class="form-label">Width</label>
346
+ </div>
347
+ <div class="col-9">
348
+ <input type="range" class="form-range" min="0.2" max="0.7" value="0.3" step="0.01"
349
+ id="creepersWidthSlider">
350
+ </div>
351
+ <div class="col-auto">
352
+ <span id="creepersWidthValue"></span>
353
+ </div>
354
+ </div>
355
+
356
+ <div class="row justify-content-center">
357
+ <div class="col-auto">
358
+ <label id="creepers-height-label" for="creepersHeightSlider" class="form-label">Height</label>
359
+ </div>
360
+ <div class="col-9">
361
+ <input type="range" class="form-range" min="0.2" max="5" value="3" step="0.01"
362
+ id="creepersHeightSlider">
363
+ </div>
364
+ <div class="col-auto">
365
+ <span id="creepersHeightValue"></span>
366
+ </div>
367
+ </div>
368
+
369
+ <div class="row justify-content-center ">
370
+ <div class="col-auto">
371
+ <label id="creepers-spacing-label" for="creepersSpacingSlider" class="form-label">Spacing</label>
372
+ </div>
373
+ <div class="col-9">
374
+ <input type="range" class="form-range" min="0.6" max="5" value="1" step="0.01"
375
+ id="creepersSpacingSlider">
376
+ </div>
377
+ <div class="col-auto">
378
+ <span id="creepersSpacingValue"></span>
379
+ </div>
380
+ </div>
381
+ </div>
382
+ </div>
383
+ </div>
384
+
385
+ <!-- Advanced Options tab pane -->
386
+ <div class="tab-pane" id="advanced-options-tab" role="tabpanel" aria-labelledby="advanced-options-btn">
387
+ <!-- Draw joins/sensors/names selectors -->
388
+ <div id="advancedOptions" class="row mt-3">
389
+
390
+ <div class="col-3 border border-start-0 border-top-0 border-bottom-0">
391
+ <h1 id="renderingOptionsTitle" class="has-text-centered my-2"><strong> Rendering Options </strong></h1>
392
+ <div id="drawSelectors" class="mx-5 my-2">
393
+ <div class="form-check form-switch">
394
+ <input class="form-check-input" type="checkbox" id="drawJointsSwitch" data-bs-toggle="tooltip" title="Draw joints">
395
+ <label id="drawJointsLabel" class="form-check-label" for="drawJointsSwitch">Draw joints</label>
396
+ </div>
397
+ <div class="form-check form-switch">
398
+ <input class="form-check-input" type="checkbox" id="drawLidarsSwitch" data-bs-toggle="tooltip" title="Draw lidars">
399
+ <label id="drawLidarsLabel" class="form-check-label" for="drawLidarsSwitch">Draw lidars</label>
400
+ </div>
401
+ <div class="form-check form-switch">
402
+ <input class="form-check-input" type="checkbox" id="drawNamesSwitch" data-bs-toggle="tooltip" title="Draw names">
403
+ <label id="drawNamesLabel" class="form-check-label" for="drawNamesSwitch">Draw names</label>
404
+ </div>
405
+ <div class="form-check form-switch">
406
+ <input class="form-check-input" type="checkbox" id="drawObservationSwitch" data-bs-toggle="tooltip" title="Draw observation">
407
+ <label id="drawObservationLabel" class="form-check-label" for="drawObservationSwitch">Draw observations</label>
408
+ </div>
409
+ <div class="form-check form-switch">
410
+ <input class="form-check-input" type="checkbox" id="drawRewardSwitch" data-bs-toggle="tooltip" title="Draw rewards">
411
+ <label id="drawRewardLabel" class="form-check-label" for="drawRewardSwitch">Draw rewards</label>
412
+ </div>
413
+ </div>
414
+ </div>
415
+
416
+ <!--<div class="col-4 border border-start-0 border-top-0 border-bottom-0">
417
+ <h1 class="has-text-centered my-2"><strong> Performance Options </strong></h1>
418
+ </div>-->
419
+
420
+ <div class="col">
421
+ <div class="has-text-centered">
422
+ <h1 id="assetsTitle" class="my-2"><strong> Assets </strong></h1>
423
+ <p id="assetsText" class="mb-2">Here you can find several types of assets, which are objects that you can add to the simulation using the mouse.</p>
424
+ </div>
425
+
426
+ <button id="circleAssetButton" class="btn btn-outline-asset mx-3"><i class="fas fa-circle"></i> Circle </button>
427
+ <span id="comingSoon" class="mx-3">More assets coming soon...</span>
428
+ </div>
429
+ </div>
430
+ </div>
431
+
432
+ <!-- About... tab pane -->
433
+ <div class="tab-pane" id="about-tab" role="tabpanel" aria-labelledby="about-btn">
434
+ <div class="about-text px-5 my-5">
435
+
436
+ <h2 id="purpose-title" class="about-subsection mb-4">Purpose of the demo</h2>
437
+
438
+ <div id="purpose-text" class="mb-4">
439
+ <p class="mb-2">
440
+ The goal of this demo is to showcase the challenge of <strong>generalization</strong> to unknown tasks
441
+ for <strong>Deep Reinforcement Learning (DRL)</strong> agents.
442
+ </p>
443
+
444
+ <p class="mb-2">
445
+ <strong>DRL</strong> is a <strong>machine learning</strong> approach for teaching <strong>virtual agents</strong>
446
+ how to solve tasks by combining <strong>Reinforcement Learning</strong> and <strong>Deep Learning</strong> methods.
447
+ This approach has been used for a diverse set of applications including robotics (e.g. <a href="https://openai.com/blog/solving-rubiks-cube/">Solving Rubik's Cube</a>),
448
+ video games and boardgames (e.g. <a href="https://deepmind.com/research/case-studies/alphago-the-story-so-far">AlphaGo</a>).
449
+ </p>
450
+
451
+ <p class="mb-2">
452
+ In this demo, all the agents have been <strong>autonomously trained</strong> to learn an efficient behaviour to navigate through a 2D environment,
453
+ combining different methods so that they can be able to <strong>generalize their behaviour to never-seen-before situations</strong>.
454
+ </p>
455
+
456
+ <p>
457
+ The demo provides different tools to customize the environment in order to test and challenge the
458
+ <strong>robustness</strong> of the agents on different situations.
459
+ </p>
460
+ </div>
461
+
462
+
463
+ <h2 id="rl-title" class="about-subsection mb-4">Reinforcement Learning</h2>
464
+
465
+ <div id="rl-text" class="mb-4">
466
+ <p>
467
+ <strong>Reinforcement Learning (RL)</strong> is the study of agents and how they learn by <strong>trial and error</strong>.
468
+ The main idea is to <strong>reward or punish</strong> an agent according to the actions it takes in order to teach it an efficient behavior to reach an objective.
469
+ <br>
470
+ The RL approaches generally feature an <strong>agent</strong> which evolves and interacts with a <strong>world</strong>.
471
+ At each interaction step, the agent sees a partial <strong>observation</strong> of the current state of the environment and decides of an action to take.
472
+ Each action taken by the agent changes the state of the world.
473
+ The agent also receives a <strong>reward</strong> signal at each step, that indicates how good or bad the current state is
474
+ according to the objective the agent has to reach.
475
+ </p>
476
+
477
+ <div class="row align-items-center mb-4">
478
+ <div class="col-12 col-md-6">
479
+ <p>
480
+ The diagram on the right presents this interaction process between the <strong>agent</strong> and the <strong>environment</strong>,
481
+ with the different information they exchange at each step.
482
+ <br>
483
+ <strong>Maximizing the reward</strong> over steps is a way for the agent to learn a behaviour, also called <strong>policy</strong>,
484
+ to achieve its objective.
485
+ </p>
486
+ </div>
487
+ <div class="col-12 col-md-6">
488
+ <img id="rl-diagram" class="w-100" src="images/about/rl_diagram_transparent_bg.png" alt="RL diagram">
489
+ </div>
490
+ </div>
491
+ </div>
492
+
493
+ <h2 id="drl-title" class="about-subsection mb-4">Deep RL</h2>
494
+
495
+ <div id="drl-text" class="mb-4">
496
+ <p class="mb-2">
497
+ In order to remember and improve the actions taken by the agent, DRL algorithms utilizes <strong>artificial neural networks</strong>.
498
+ With <strong>training</strong>, these neural networks are able to <strong>learn to predict an optimal action to take at each step from the observation received</strong>,
499
+ and relying on all the observations and rewards previously received after each action during training.
500
+ Thanks to this, DRL algorithms are able to produce behaviours that are very effective in situations similar to those they were trained on.
501
+ </p>
502
+
503
+ <div class="row justify-content-center my-4">
504
+ <img id="rl-demo_diagram" class="w-50" src="images/about/rl_demo_diagram_EN.png" alt="RL demo diagram">
505
+ </div>
506
+
507
+ <p>
508
+ However, in real-world applications, the environment rarely remains still and frequently evolves. Therefore one would
509
+ want DRL agents to be able to <strong>generalize their behaviour</strong> to previously unseen changes of the environment so that
510
+ they can <strong>adapt to a large range of situations</strong>.
511
+ </p>
512
+ </div>
513
+
514
+ <h2 id="acl-title" class="about-subsection mb-4">Automatic Curriculum Learning</h2>
515
+
516
+ <div id="acl-text" class="mb-4">
517
+ <p class="mb-2">
518
+ One solution to handle this challenge is to train DRL agents on <strong>procedurally generated environments</strong>.
519
+ <br>
520
+ <strong>Procedural generation</strong> is a method of automatically creating environments according to some parameters.
521
+ Using this method, DRL agents can be trained on a <strong>very wide range of environments</strong>, hence allowing them
522
+ to <strong>generalize their behaviour</strong> to more different situations.
523
+ </p>
524
+
525
+ <p>
526
+ However, randomly generating environments during training implies the risk to generate environments that are too difficult or too easy to resolve
527
+ for the agents, preventing them to continuously learn in an efficient way.
528
+ <br>
529
+ Therefore, one would need <strong>smarter training strategies</strong> that propose relevant environments tailored to the current <strong>learning progress</strong> of the <strong>student</strong> (DRL agent).
530
+ This method is called <strong>Automatic Curriculum Learning (ACL)</strong> and is embodied by a <strong>teacher algorithm</strong> which is trained to learn to generate
531
+ the most relevant environments throughout the entire training process according to the student performances.
532
+ <br>
533
+ This way, the teacher proposes easy environments to the student at the beginning and <strong>gradually increases the difficulty
534
+ and the diversity</strong> of the tasks in order to guarantee that the <strong>student is progressing while not always facing the same situation or forgetting what it has already learned</strong>.
535
+ </p>
536
+ </div>
537
+
538
+ <h2 id="about-demo-title" class="about-subsection mb-4">About the demo</h2>
539
+
540
+ <div id="about-demo-text" class="mb-4">
541
+ <p class="mb-2">
542
+ In this demo, all the available agents were trained using <a href="https://spinningup.openai.com/en/latest/algorithms/sac.html">Soft Actor Critic</a>
543
+ as the <strong>DRL student algorithm</strong> alongside different <strong>ACL teacher algorithms</strong> such as <a href="https://arxiv.org/abs/1910.07224">ALP-GMM</a>.
544
+ <br>
545
+ They successfully learned efficient behaviours to move through the environment and to <strong>generalize</strong> to never-seen-before situations.
546
+ </p>
547
+
548
+ <p>
549
+ The physics of the simulation are supported by <a href="https://github.com/kripken/box2d.js">box2d.js</a>
550
+ which is a direct port of the <a href="https://github.com/erincatto/box2d">Box2D</a> physics engine to JavaScript.
551
+ <br>
552
+ The <strong>pre-trained policies</strong> (agents behaviours) are loaded in the browser thanks to <a href="https://www.tensorflow.org/js">TensorFlow.js</a>.
553
+ </p>
554
+ </div>
555
+
556
+ <h2 id="credits-title" class="about-subsection mb-4">Credits</h2>
557
+
558
+ <div id="credits-text" class="mb-4">
559
+ <p class="mb-4">
560
+ This demo was designed by <a href="https://github.com/pgermon">Paul Germon</a> as part of an internship within <a href="https://flowers.inria.fr/">Flowers</a>
561
+ research team at <a href="https://www.inria.fr/fr">Inria</a>. This internship was monitored by Rémy Portelas and Clément Romac,
562
+ and supervised by Pierre-Yves Oudeyer. Special thanks to Nikita Melkozerov for its very helpful contribution.
563
+ Recommended citation format:
564
+ <a name="germon2021demo"></a><pre>
565
+ @misc{germon2021demo,
566
+ title={Interactive Deep Reinforcement Learning Demo},
567
+ author={Germon, Paul and Romac, Clément and Portelas, Rémy and Pierre-Yves, Oudeyer},
568
+ url={https://developmentalsystems.org/Interactive_DeepRL_Demo/},
569
+ year={2021}
570
+ }
571
+ </pre>
572
+ </p>
573
+
574
+ <ul class="px-3" style="list-style-type: disc">
575
+ <li>The code of this demo is open-source and can be found on this <a href="https://github.com/flowersteam/Interactive_DeepRL_Demo">github repository.</a></li>
576
+ <li>The code of the environment and agents is adapted from the <a href="http://developmentalsystems.org/TeachMyAgent/">TeachMyAgent</a> benchmark's Python code to JavaScript.</li>
577
+ </ul>
578
+ </div>
579
+
580
+ <h2 id="references-title" class="about-subsection mb-4">References</h2>
581
+
582
+ <div id="references-text">
583
+ <ul class="mb-4">
584
+ <li id="ref1">[1] OpenAI, Ilge Akkaya, Marcin Andrychowicz, Maciek Chociej, Mateusz Litwin, Bob McGrew, Arthur Petron, Alex Paino, Matthias Plappert, Glenn Powell, Raphael Ribas, Jonas Schneider, Nikolas Tezak, Jerry Tworek, Peter Welinder, Lilian Weng, Qiming Yuan, Wojciech Zaremba, Lei Zhang:
585
+ Solving Rubik's Cube with a Robot Hand (2019). <a href="https://arxiv.org/abs/1910.07113">https://arxiv.org/abs/1910.07113</a></li>
586
+ <li id="ref2">[2] Silver, D., Huang, A., Maddison, C. et al. Mastering the game of Go with deep neural networks and tree search. Nature 529, 484–489 (2016). <a href="https://doi.org/10.1038/nature16961">https://doi.org/10.1038/nature16961</a></li>
587
+ <li id="ref3">[3] Portelas, R., Colas, C., Weng, L., Hofmann, K., & Oudeyer, P. Y. (2020). Automatic curriculum learning for deep rl: A short survey (2020). <a href="https://arxiv.org/abs/2003.04664">https://arxiv.org/abs/2003.04664</a></li>
588
+ <li id="ref4">[4] Haarnoja, T., Zhou, A., Abbeel, P., & Levine, S. (2018, July). Soft actor-critic: Off-policy maximum entropy deep reinforcement learning with a stochastic actor. <em>In International conference on machine learning</em> (pp. 1861-1870). PMLR <a href="https://arxiv.org/abs/1801.01290">https://arxiv.org/abs/1801.01290</a></li>
589
+ <li id="ref5">[5] Portelas, R., Colas, C., Hofmann, K., & Oudeyer, P. Y. (2020, May). Teacher algorithms for curriculum learning of deep rl in continuously parameterized environments. <em>In Conference on Robot Learning</em> (pp. 835-853). PMLR. <a href="https://arxiv.org/abs/1910.07224">https://arxiv.org/abs/1910.07224</a></li>
590
+ <li id="ref6">[6] Romac, C., Portelas, R., Hofmann, K., & Oudeyer, P. Y. (2021). TeachMyAgent: a Benchmark for Automatic Curriculum Learning in Deep RL. <a href="https://arxiv.org/abs/2103.09815">https://arxiv.org/abs/2103.09815</a></li>
591
+ </ul>
592
+ </div>
593
+ </div>
594
+ </div>
595
+ </div>
596
+ </div>
597
+
598
+ <div id="agents-selection" class="col mx-1 mb-2 px-2 border border-top-0 border-end-0 border-bottom-0">
599
+ <!-- select the morphology -->
600
+ <h1 id="agents-selection-title" class="has-text-centered my-2"> <strong> Add an agent</strong> </h1>
601
+ <p id="agents-selection-text" class="has-text-centered my-2"> Here you can add an agent to the simulation with the morphology of your choice.</p>
602
+ <ul class="list-group" id="morphologies-list"></ul>
603
+ </div>
604
+ </div>
605
+ </div>
606
+
607
+ <script src="./index.js"></script>
608
+ <script type="module" src="./ui.js"></script>
609
+
610
+ </body>
611
+
612
  </html>
index.js ADDED
@@ -0,0 +1,661 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* GLOBAL VARIABLES */
2
+
3
+ window.erasing_radius = 15;
4
+ window.asset_size = 8;
5
+
6
+ // Lists of points {x, y} composing the terrain shapes
7
+ window.ground = [];
8
+ window.ceiling = [];
9
+
10
+ // Lists of raw points {x, y} drawn by the user for the terrain shapes
11
+ window.terrain = {
12
+ ground: [],
13
+ ceiling: []
14
+ };
15
+
16
+ // Parameters to handle the alignment of the terrain to the startpad according to the situation
17
+ window.align_terrain = {
18
+ align: true,
19
+ ceiling_offset: null,
20
+ ground_offset: null,
21
+ smoothing: null
22
+ };
23
+
24
+ /* INIT FUNCTIONS */
25
+
26
+ /**
27
+ * Initializes the game.
28
+ * @param cppn_input_vector {Array} - 3-dimensional array that encodes the CPPN
29
+ * @param water_level {number}
30
+ * @param creepers_width {number}
31
+ * @param creepers_height {number}
32
+ * @param creepers_spacing {number}
33
+ * @param smoothing {number}
34
+ * @param creepers_type {boolean}
35
+ * @param ground {Array} - List of points {x, y} composing the ground
36
+ * @param ceiling {Array} - List of points {x, y} composing the ceiling
37
+ * @param align {Object}
38
+ * @param zoom {number} - Zoom to apply to the environment
39
+ * @param scroll {{x: number, y:number}} - Scroll to apply to the environment
40
+ */
41
+ function init_game(cppn_input_vector, water_level, creepers_width, creepers_height, creepers_spacing,
42
+ smoothing, creepers_type, ground, ceiling, align, zoom=null, scroll=null) {
43
+
44
+ let agents = {
45
+ morphologies: [],
46
+ policies: [],
47
+ positions: []
48
+ }
49
+
50
+ // Pauses the game if it already exists and gets the information about the running agents
51
+ if(window.game != null){
52
+ window.game.pause();
53
+ agents.morphologies = [...window.game.env.agents.map(a => a.morphology)];
54
+ agents.policies = [...window.game.env.agents.map(a => a.policy)];
55
+ agents.positions = [...window.game.env.agents.map(agent => agent.agent_body.reference_head_object.GetPosition())];
56
+ }
57
+ window.game = new Game(agents, cppn_input_vector, water_level, creepers_width, creepers_height,
58
+ creepers_spacing, smoothing, creepers_type, ground, ceiling, align);
59
+ window.set_agent_selected(-1);
60
+ window.asset_selected = null;
61
+
62
+ if(zoom == null){
63
+ window.game.env.set_zoom(INIT_ZOOM);
64
+ }
65
+ else {
66
+ window.game.env.set_zoom(zoom);
67
+ }
68
+
69
+ if(scroll == null){
70
+ window.game.env.set_scroll(window.agent_selected, INIT_SCROLL_X, 0);
71
+ }
72
+ else{
73
+ window.game.env.set_scroll(window.agent_selected, scroll[0], scroll[1]);
74
+ }
75
+ window.game.env.render();
76
+ }
77
+
78
+ /**
79
+ * Indicates if the creepers type is 'Swingable' or not.
80
+ * @returns {boolean}
81
+ */
82
+ function getCreepersType() {
83
+ return document.getElementById("creepersType").value == 'Swingable';
84
+ }
85
+
86
+ /**
87
+ * First function called after the code is entirely loaded.
88
+ * Loads the model of the CPPN, initializes the game by default, loads the default environmnent and starts the language selection.
89
+ * @returns {Promise<void>}
90
+ */
91
+ async function onLoadInit() {
92
+ window.cppn_model = await tf.loadGraphModel('./js/CPPN/weights/same_ground_ceiling_cppn/tfjs_model/model.json');
93
+ window.init_default();
94
+ window.loadDefaultEnv();
95
+ window.langIntroSetUp();
96
+ }
97
+
98
+ // Calls onLoadInit() when all the files are loaded
99
+ window.addEventListener("load", onLoadInit, false);
100
+
101
+ /* IN-CANVAS MOUSE INTERACTIONS */
102
+
103
+ /**
104
+ * Converts the given position relative to the canvas to the environment scale.
105
+ * @param x_pos {number} - X-coordinate inside the canvas.
106
+ * @param y_pos {number} - Y-coordinate inside the canvas.
107
+ * @returns {{x: number, y: number}} - Position inside the environment.
108
+ */
109
+ function convertPosCanvasToEnv(x_pos, y_pos){
110
+ let x = Math.max(-window.canvas.width * 0.01, Math.min(x_pos, window.canvas.width * 1.01));
111
+ let y = Math.max(0, Math.min(y_pos, window.canvas.height));
112
+
113
+ x += window.game.env.scroll[0];
114
+ y = -(y - window.game.env.scroll[1]);
115
+
116
+ x = x / (window.game.env.scale * window.game.env.zoom);
117
+ y = y / (window.game.env.scale * window.game.env.zoom);
118
+
119
+ y += (1 - window.game.env.scale * window.game.env.zoom) * RENDERING_VIEWER_H/(window.game.env.scale * window.game.env.zoom)
120
+ + (window.game.env.zoom - 1) * (window.game.env.ceiling_offset)/window.game.env.zoom * 1/3 + RENDERING_VIEWER_H;
121
+
122
+ return {x: x, y: y};
123
+ }
124
+
125
+ /**
126
+ * Converts the given position relative to the environment to the canvas scale.
127
+ * @param x_pos {number} - X-coordinate inside the environment.
128
+ * @param y_pos {number} - Y-coordinate inside the environment.
129
+ * @returns {{x: number, y: number}} - Position inside the canvas.
130
+ */
131
+ function convertPosEnvToCanvas(x_pos, y_pos){
132
+ let x = x_pos * window.game.env.scale * window.game.env.zoom - window.game.env.scroll[0];
133
+ let y = window.game.env.scroll[1] - (y_pos - RENDERING_VIEWER_H) * window.game.env.scale * window.game.env.zoom
134
+ + (1 - window.game.env.scale * window.game.env.zoom) * RENDERING_VIEWER_H
135
+ + (window.game.env.zoom - 1) * window.game.env.ceiling_offset * window.game.env.scale * 1/3;
136
+
137
+ return {x: x, y: y};
138
+ }
139
+
140
+ /**
141
+ * Checks if the given position is inside the given body.
142
+ * Used for clicking on assets.
143
+ * @param pos {{x: number, y: number}}
144
+ * @param body {b2Body} - A Box2D body
145
+ * @returns {boolean}
146
+ */
147
+ function isPosInsideBody(pos, body){
148
+ let shape = body.GetFixtureList().GetShape();
149
+
150
+ if(shape.m_type == b2.Shape.e_circle){
151
+ let center = body.GetWorldCenter();
152
+ return Math.pow(center.x - pos.x, 2) + Math.pow(center.y - pos.y, 2) <= Math.pow(shape.m_radius, 2);
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Handles actions when mouse is pressed.
158
+ */
159
+ function mousePressed(){
160
+
161
+ // Hides all the tooltips when mouse pressed
162
+ document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach((el, index) => {
163
+ let tooltip = bootstrap.Tooltip.getInstance(el);
164
+ tooltip.hide();
165
+ });
166
+
167
+ // Case mouse is pressed inside the canvas
168
+ if(mouseX >= 0 && mouseX <= window.canvas.width
169
+ && mouseY >= 0 && mouseY <= window.canvas.height){
170
+
171
+ // Stores the current position of the mouse, used when dragging
172
+ window.prevMouseX = mouseX;
173
+ window.prevMouseY = mouseY;
174
+
175
+ // Creates a circle asset at the mouse position and render the environment
176
+ if(window.is_drawing_circle()){
177
+ let mousePos = convertPosCanvasToEnv(mouseX, mouseY);
178
+ window.game.env.create_circle_asset(mousePos, window.asset_size * 2 / window.game.env.scale);
179
+
180
+ if(window.agent_selected != null){
181
+ window.agent_selected.is_selected = false;
182
+ window.set_agent_selected(-1);
183
+ }
184
+ window.game.env.render();
185
+ }
186
+
187
+ // Handles agents and assets selection
188
+ else if(!window.is_drawing()){
189
+ let mousePos = convertPosCanvasToEnv(mouseX, mouseY);
190
+
191
+ // Selects an agent in the canvas if the mouse is clicked over its body
192
+ let one_agent_touched = false;
193
+ for(let i = 0; i < window.game.env.agents.length; i++){
194
+ let agent = window.game.env.agents[i];
195
+
196
+ // Checks if the agent is touched by the mouse
197
+ let is_agent_touched = agent.agent_body.isPosInside(mousePos);
198
+
199
+ // If the agent is touched and not selected yet, it is now selected and all other agents are deselected
200
+ if(is_agent_touched){
201
+ one_agent_touched = true;
202
+
203
+ if(!agent.is_selected) {
204
+ agent.is_selected = true;
205
+ window.set_agent_selected(i);
206
+ for (let other_agent of window.game.env.agents) {
207
+ if (other_agent != agent) {
208
+ other_agent.is_selected = false;
209
+ }
210
+ }
211
+ }
212
+ break;
213
+ }
214
+ // If the agent is not touched it is deselected
215
+ else {
216
+ agent.is_selected = false;
217
+ }
218
+ }
219
+
220
+ // If no agent is touched, the selected agent is set to null
221
+ if(!one_agent_touched && window.agent_selected != null){
222
+ window.set_agent_selected(-1);
223
+ }
224
+
225
+ // Selects an asset in the canvas if the mouse is clicked over its body and no agent has been touched
226
+ if(!one_agent_touched){
227
+ let one_asset_touched = false;
228
+ for(let asset of window.game.env.assets_bodies){
229
+
230
+ // Checks if the asset is touched by the mouse
231
+ let is_asset_touched = isPosInsideBody(mousePos, asset.body);
232
+
233
+ // If the asset is touched and not selected yet, it is now selected and all other assets are deselected
234
+ if(is_asset_touched){
235
+ one_asset_touched = true;
236
+
237
+ if(!asset.is_selected){
238
+ asset.is_selected = true;
239
+ window.asset_selected = asset;
240
+ for(let other_asset of window.game.env.assets_bodies){
241
+ if(other_asset != asset){
242
+ other_asset.is_selected = false;
243
+ }
244
+ }
245
+ break;
246
+ }
247
+ }
248
+ // If the asset is not touched it is deselected
249
+ else if(!is_asset_touched){
250
+ asset.is_selected = false;
251
+ }
252
+ }
253
+
254
+ // If no asset is touched, the selected asset is set to null
255
+ if(!one_asset_touched && window.asset_selected != null){
256
+ window.asset_selected = null;
257
+ }
258
+ }
259
+
260
+ window.game.env.render();
261
+ }
262
+ }
263
+ }
264
+
265
+ // Handles clicks outside canvas when drawing (deselect drawing buttons)
266
+ document.addEventListener('mousedown', (event) => {
267
+ if(window.is_drawing() || window.is_drawing_circle()){
268
+ let canvas_id = "#" + window.canvas.canvas.id;
269
+
270
+ // Elements that can be clicked without deselecting drawing buttons: canvas + ground, ceiling, erase buttons
271
+ let authorized_elements = [
272
+ document.querySelector(canvas_id),
273
+ document.querySelector('#drawGroundButton'),
274
+ document.querySelector('#drawCeilingButton'),
275
+ document.querySelector('#eraseButton')
276
+ ];
277
+
278
+ // If
279
+ if(authorized_elements.indexOf(event.target) == -1) {
280
+ window.deselectDrawingButtons();
281
+ }
282
+ }
283
+ });
284
+
285
+ /**
286
+ * Handles actions when mouse is dragged.
287
+ * @returns {boolean}
288
+ */
289
+ function mouseDragged(){
290
+
291
+ // Case mouse is dragged inside the canvas
292
+ if(mouseX >= 0 && mouseX <= window.canvas.width
293
+ && mouseY >= 0 && mouseY <= window.canvas.height) {
294
+
295
+ // DRAWING
296
+ if(window.is_drawing()) {
297
+
298
+ // Gets the position of the mouse in the environment scale
299
+ let mousePos = convertPosCanvasToEnv(mouseX, mouseY);
300
+
301
+ // Vertical offset to shift the drawing, trace and forbidden canvas in order to align them to the environment
302
+ let y_offset = SCROLL_Y_MAX - window.game.env.scroll[1];
303
+
304
+ // Drawing ground to the right of the terrain startpad
305
+ if(window.is_drawing_ground() && mousePos.x > (INITIAL_TERRAIN_STARTPAD - 1) * TERRAIN_STEP){
306
+ drawing_canvas.push();
307
+ drawing_canvas.stroke("#66994D");
308
+ drawing_canvas.strokeWeight(4);
309
+ // Draws a ground line between the current and previous positions of the mouse
310
+ drawing_canvas.line(mouseX, mouseY + y_offset, window.prevMouseX, window.prevMouseY + y_offset);
311
+ drawing_canvas.pop();
312
+ window.terrain.ground.push(mousePos);
313
+ }
314
+
315
+ // Drawing ceiling to the right of the terrain startpad
316
+ else if(window.is_drawing_ceiling() && mousePos.x > (INITIAL_TERRAIN_STARTPAD - 1) * TERRAIN_STEP){
317
+ drawing_canvas.push();
318
+ drawing_canvas.stroke("#808080");
319
+ drawing_canvas.strokeWeight(4);
320
+ // Draws a ceiling line between the current and previous positions of the mouse
321
+ drawing_canvas.line(mouseX, mouseY + y_offset, window.prevMouseX, window.prevMouseY + y_offset);
322
+ drawing_canvas.pop();
323
+ window.terrain.ceiling.push(mousePos);
324
+ }
325
+
326
+ // Erasing to the right of the terrain startpad
327
+ else if(window.is_erasing() && mousePos.x > INITIAL_TERRAIN_STARTPAD * TERRAIN_STEP){
328
+
329
+ // Draws a circle trace at the mouse position to show the erasing radius
330
+ trace_canvas.clear();
331
+ trace_canvas.noStroke();
332
+ trace_canvas.fill(255);
333
+ trace_canvas.circle(mouseX, mouseY + y_offset, window.erasing_radius * 2);
334
+
335
+ // Removes the points that are within the circle's radius from the ground and ceiling lists
336
+ window.terrain.ground = window.terrain.ground.filter(function(point, index, array){
337
+ return Math.pow(point.x - mousePos.x, 2) + Math.pow(point.y - mousePos.y, 2) > Math.pow(window.erasing_radius / (window.game.env.scale * window.game.env.zoom), 2);
338
+ });
339
+ window.terrain.ceiling = window.terrain.ceiling.filter(function(point, index, array){
340
+ return Math.pow(point.x - mousePos.x, 2) + Math.pow(point.y - mousePos.y, 2) > Math.pow(window.erasing_radius / (window.game.env.scale * window.game.env.zoom), 2);
341
+ });
342
+
343
+ // Erases the drawing canvas inside the circle's radius
344
+ drawing_canvas.erase();
345
+ drawing_canvas.circle(mouseX, mouseY + y_offset, window.erasing_radius * 2);
346
+ drawing_canvas.noErase();
347
+ }
348
+
349
+ // Dragging to scroll
350
+ else{
351
+ cursor(MOVE);
352
+ window.game.env.set_scroll(null, window.game.env.scroll[0] + window.prevMouseX - mouseX, window.game.env.scroll[1] + mouseY - prevMouseY);
353
+
354
+ // Re-draws the terrain shapes according to the new scroll
355
+ window.refresh_drawing();
356
+ y_offset = SCROLL_Y_MAX - window.game.env.scroll[1];
357
+ }
358
+
359
+ // Renders the environment and displays the off-screen canvas on top of it
360
+ window.game.env.render();
361
+ image(drawing_canvas, 0, -y_offset);
362
+ image(trace_canvas, 0, -y_offset);
363
+ image(forbidden_canvas, 0, -y_offset);
364
+ }
365
+
366
+ // DRAGGING
367
+ else{
368
+ cursor(MOVE);
369
+
370
+ // Dragging an agent
371
+ for (let agent of window.game.env.agents) {
372
+
373
+ // Drags the selected agent
374
+ if (agent.is_selected) {
375
+
376
+ // Computes the terrain's length according to the agent's morphology
377
+ let terrain_length;
378
+ if (agent.agent_body.body_type == BodyTypesEnum.CLIMBER) {
379
+ terrain_length = window.game.env.terrain_ceiling[window.game.env.terrain_ceiling.length - 1].x;
380
+ }
381
+ else if (agent.agent_body.body_type == BodyTypesEnum.WALKER) {
382
+ terrain_length = window.game.env.terrain_ground[window.game.env.terrain_ground.length - 1].x;
383
+ }
384
+ else if(agent.agent_body.body_type == BodyTypesEnum.SWIMMER){
385
+ terrain_length = Math.max(window.game.env.terrain_ground[window.game.env.terrain_ground.length - 1].x,
386
+ window.game.env.terrain_ceiling[window.game.env.terrain_ceiling.length - 1].x);
387
+ }
388
+
389
+ // Gets the mouse position inside the environment and clamps it horizontally to the edges of the terrain
390
+ let mousePos = convertPosCanvasToEnv(mouseX, mouseY);
391
+ let x = Math.max(0.02, Math.min(0.98, mousePos.x / terrain_length)) * terrain_length;
392
+
393
+ // Sets the position of the agent to the mouse position
394
+ window.game.env.set_agent_position(agent, x, mousePos.y);
395
+ window.game.env.render();
396
+ window.is_dragging_agent = true;
397
+ break;
398
+ }
399
+ }
400
+
401
+ // Dragging an asset
402
+ for(let asset of window.game.env.assets_bodies){
403
+
404
+ // Drags the selected asset
405
+ if (asset.is_selected && !window.is_dragging_agent) {
406
+ let terrain_length = Math.max(window.game.env.terrain_ground[window.game.env.terrain_ground.length - 1].x,
407
+ window.game.env.terrain_ceiling[window.game.env.terrain_ceiling.length - 1].x);
408
+
409
+ // Gets the mouse position inside the environment and clamps it horizontally to the edges of the terrain
410
+ let mousePos = convertPosCanvasToEnv(mouseX, mouseY);
411
+ mousePos.x = Math.max(0.02, Math.min(0.98, mousePos.x / terrain_length)) * terrain_length;
412
+
413
+ // Sets the position of the asset to the mouse position
414
+ window.game.env.set_asset_position(asset, mousePos);
415
+ window.game.env.render();
416
+ window.is_dragging_asset = true;
417
+ }
418
+ }
419
+
420
+ // Dragging to scroll
421
+ if(!window.is_dragging_agent && !window.is_dragging_asset){
422
+
423
+ // Scrolling manually cancels agent following
424
+ if(window.agent_followed != null){
425
+ window.set_agent_followed(-1);
426
+ }
427
+ window.game.env.set_scroll(null, window.game.env.scroll[0] + window.prevMouseX - mouseX, window.game.env.scroll[1] + mouseY - prevMouseY);
428
+ window.game.env.render();
429
+ }
430
+ }
431
+ }
432
+
433
+ // Dragging an agent horizontally out of canvas
434
+ else if(window.is_dragging_agent
435
+ && mouseY >= 0 && mouseY < window.canvas.height){
436
+
437
+ if(mouseX < 0){
438
+ window.dragging_side = "left";
439
+ }
440
+ else if(mouseX > window.canvas.width){
441
+ window.dragging_side = "right";
442
+ }
443
+
444
+ cursor(MOVE);
445
+
446
+ // Dragging an agent
447
+ for (let agent of window.game.env.agents) {
448
+
449
+ // Drags the selected agent
450
+ if (agent.is_selected) {
451
+
452
+ // Scrolls horizontally according to the dragging side to follow the agent
453
+ window.game.env.set_scroll(null);
454
+
455
+ // Computes the terrain's length according to the agent's morphology
456
+ let terrain_length;
457
+ if (agent.agent_body.body_type == BodyTypesEnum.CLIMBER) {
458
+ terrain_length = window.game.env.terrain_ceiling[window.game.env.terrain_ceiling.length - 1].x;
459
+ }
460
+ else if (agent.agent_body.body_type == BodyTypesEnum.WALKER) {
461
+ terrain_length = window.game.env.terrain_ground[window.game.env.terrain_ground.length - 1].x;
462
+ }
463
+ else if(agent.agent_body.body_type == BodyTypesEnum.SWIMMER){
464
+ terrain_length = Math.max(window.game.env.terrain_ground[window.game.env.terrain_ground.length - 1].x,
465
+ window.game.env.terrain_ceiling[window.game.env.terrain_ceiling.length - 1].x);
466
+ }
467
+
468
+ // Gets the mouse position inside the environment and clamps it horizontally to the edges of the terrain
469
+ let mousePos = convertPosCanvasToEnv(mouseX, mouseY);
470
+ let x = Math.max(0.02, Math.min(0.98, mousePos.x / terrain_length)) * terrain_length;
471
+
472
+ // Sets the position of the agent to the mouse position
473
+ window.game.env.set_agent_position(agent, x, mousePos.y);
474
+ window.game.env.render();
475
+ break;
476
+ }
477
+ }
478
+
479
+ // Prevents default behaviour when dragging the mouse
480
+ return false;
481
+ }
482
+
483
+ window.prevMouseX = mouseX;
484
+ window.prevMouseY = mouseY;
485
+ }
486
+
487
+ /**
488
+ * Handles actions when mouse is released.
489
+ */
490
+ function mouseReleased(){
491
+ cursor();
492
+ window.is_dragging_agent = false;
493
+ window.is_dragging_asset = false;
494
+ window.dragging_side = null;
495
+ }
496
+
497
+ /**
498
+ * Handles actions when mouse is moved.
499
+ */
500
+ function mouseMoved(){
501
+
502
+ // Draws the trace of the circle asset at the mouse position
503
+ if(window.is_drawing_circle()){
504
+ trace_canvas.clear();
505
+ if(mouseX >= 0 && mouseX <= window.canvas.width
506
+ && mouseY >= 0 && mouseY <= window.canvas.height) {
507
+ trace_canvas.noStroke();
508
+ trace_canvas.fill(136, 92, 0, 180);
509
+ trace_canvas.circle(mouseX, mouseY + SCROLL_Y_MAX - window.game.env.scroll[1], window.asset_size * 4 * window.game.env.zoom);
510
+ }
511
+ window.game.env.render();
512
+ image(trace_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
513
+ }
514
+
515
+ // Draws the trace of the eraser at the mouse position
516
+ else if (window.is_erasing()) {
517
+ trace_canvas.clear();
518
+ if (mouseX >= 0 && mouseX <= window.canvas.width
519
+ && mouseY >= 0 && mouseY <= window.canvas.height) {
520
+ trace_canvas.noStroke();
521
+ trace_canvas.fill(255, 180);
522
+ trace_canvas.circle(mouseX, mouseY + SCROLL_Y_MAX - window.game.env.scroll[1], window.erasing_radius * 2);
523
+ }
524
+ window.game.env.render();
525
+ image(drawing_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
526
+ image(trace_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
527
+ image(forbidden_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
528
+ }
529
+ }
530
+
531
+ /**
532
+ * Handles actions when a mouse wheel event is detected (actual mouse wheel or touchpad).
533
+ * @param event {WheelEvent}
534
+ * @returns {boolean}
535
+ */
536
+ function mouseWheel(event){
537
+ if(mouseX >= 0 && mouseX <= window.canvas.width
538
+ && mouseY >= 0 && mouseY <= window.canvas.height) {
539
+
540
+ trace_canvas.clear();
541
+
542
+ // Resizes circle asset radius
543
+ if(window.is_drawing_circle()){
544
+ window.asset_size = Math.max(3, Math.min(window.asset_size - event.delta / 100, 30));
545
+ trace_canvas.noStroke();
546
+ trace_canvas.fill(136, 92, 0, 180);
547
+ trace_canvas.circle(mouseX, mouseY + SCROLL_Y_MAX - window.game.env.scroll[1], window.asset_size * 4 * window.game.env.zoom);
548
+ window.game.env.render();
549
+ image(trace_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
550
+ }
551
+
552
+ // Resizes erasing radius
553
+ else if(window.is_erasing()){
554
+ window.erasing_radius = Math.max(5, Math.min(window.erasing_radius - event.delta / 100, 30));
555
+ trace_canvas.noStroke();
556
+ trace_canvas.fill(255, 180);
557
+ trace_canvas.circle(mouseX, mouseY + SCROLL_Y_MAX - window.game.env.scroll[1], window.erasing_radius * 2);
558
+ window.game.env.render();
559
+ image(drawing_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
560
+ image(trace_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
561
+ image(forbidden_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
562
+ }
563
+
564
+ // Zooms in or out
565
+ else {
566
+ window.game.env.set_zoom(window.game.env.zoom - event.delta / 2000);
567
+ // TODO: scroll on the mouse position
568
+ window.game.env.set_scroll(null, window.game.env.scroll[0], window.game.env.scroll[1]);
569
+
570
+ // If drawing mode, re-draws the terrain shapes according to the new zoom
571
+ if(window.is_drawing()){
572
+ window.refresh_drawing();
573
+ window.game.env.render();
574
+ image(drawing_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
575
+ image(forbidden_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
576
+ }
577
+ else{
578
+ window.game.env.render();
579
+ }
580
+
581
+ }
582
+
583
+ // Prevents default behaviour for mouse wheel events inside the canvas
584
+ return false;
585
+ }
586
+ }
587
+
588
+ /**
589
+ * Handles actions when a key is pressed.
590
+ * @returns {boolean}
591
+ */
592
+ function keyPressed(){
593
+ // Deletes the agent or asset selected when pressing the delete key
594
+ if(keyCode == DELETE){
595
+ if(window.agent_selected != null){
596
+ window.delete_agent(agent_selected);
597
+ window.agent_selected(null);
598
+ return false;
599
+ }
600
+ else if(window.asset_selected != null){
601
+ window.game.env.delete_asset(window.asset_selected);
602
+ window.asset_selected = null;
603
+ window.game.env.render();
604
+ return false;
605
+ }
606
+ }
607
+ }
608
+
609
+ /**
610
+ * Handles actions when the window is resized.
611
+ */
612
+ function windowResized(){
613
+
614
+ let canvas_container = document.querySelector('#canvas_container');
615
+
616
+ // Recomputes RENDERING_VIEWER_W, INIT_ZOOM and THUMBNAIL_ZOOM
617
+ RENDERING_VIEWER_W = canvas_container.offsetWidth;
618
+ INIT_ZOOM = RENDERING_VIEWER_W / ((TERRAIN_LENGTH + INITIAL_TERRAIN_STARTPAD) * 1.05 * TERRAIN_STEP * SCALE);
619
+ THUMBNAIL_ZOOM = RENDERING_VIEWER_W / ((TERRAIN_LENGTH + INITIAL_TERRAIN_STARTPAD) * 0.99 * TERRAIN_STEP * SCALE);
620
+
621
+ // Resizes the main canvas
622
+ resizeCanvas(RENDERING_VIEWER_W, RENDERING_VIEWER_H);
623
+ drawing_canvas.resizeCanvas(RENDERING_VIEWER_W + SCROLL_X_MAX, RENDERING_VIEWER_H + 2 * SCROLL_Y_MAX);
624
+ trace_canvas.resizeCanvas(RENDERING_VIEWER_W + SCROLL_X_MAX, RENDERING_VIEWER_H + 2 * SCROLL_Y_MAX);
625
+ forbidden_canvas.resizeCanvas(RENDERING_VIEWER_W + SCROLL_X_MAX, RENDERING_VIEWER_H + 2 * SCROLL_Y_MAX);
626
+
627
+ // Generates the terrain from the drawing
628
+ if(is_drawing()){
629
+ window.refresh_drawing();
630
+ window.game.env.render();
631
+ image(drawing_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
632
+ image(forbidden_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
633
+ }
634
+ // Re-initializes the environment
635
+ else{
636
+ window.init_default();
637
+ }
638
+ }
639
+
640
+ window.downloadObjectAsJson = (exportObj, exportName) => {
641
+ let dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportObj));
642
+ let downloadAnchorNode = document.createElement('a');
643
+ downloadAnchorNode.setAttribute("href", dataStr);
644
+ downloadAnchorNode.setAttribute("download", exportName + ".json");
645
+ document.body.appendChild(downloadAnchorNode); // required for firefox
646
+ downloadAnchorNode.click();
647
+ downloadAnchorNode.remove();
648
+ }
649
+
650
+ window.strUcFirst = (a) => {
651
+ return (a+'').charAt(0).toUpperCase()+a.substr(1);
652
+ }
653
+
654
+ window.draw_forbidden_area = () => {
655
+ forbidden_canvas.clear();
656
+ forbidden_canvas.stroke("#FF0000");
657
+ forbidden_canvas.strokeWeight(3);
658
+ forbidden_canvas.fill(255, 50, 0, 75);
659
+ let w = convertPosEnvToCanvas((INITIAL_TERRAIN_STARTPAD - 1) * TERRAIN_STEP, 0).x;
660
+ forbidden_canvas.rect(0, 0, w, RENDERING_VIEWER_H + 2 * SCROLL_Y_MAX);
661
+ }
js/Box2D_dynamics/climbing_dynamics.js ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @classdesc Class that handles the climbing dynamics.
3
+ */
4
+ class ClimbingDynamics {
5
+ constructor(){};
6
+
7
+ /**
8
+ * Prepares the agent's sensors to grasp or release according to the actions.
9
+ * @param actions {Array} - Actions of the agent
10
+ * @param agent_body {Object} - Climber morphology
11
+ * @param world - {Object} - Box2D world
12
+ */
13
+ before_step_climbing_dynamics(actions, agent_body, world){
14
+ for(let i = 0; i < agent_body.sensors.length; i++){
15
+ let action_to_check = actions[actions.length - i - 1];
16
+ let sensor_to_check = agent_body.sensors[agent_body.sensors.length - i - 1];
17
+ if(action_to_check > 0){ // Check whether the sensor should grasp or release
18
+ sensor_to_check.GetUserData().ready_to_attach = true;
19
+ }
20
+ else {
21
+ sensor_to_check.GetUserData().ready_to_attach = false;
22
+ if(sensor_to_check.GetUserData().has_joint){ // if released and it had a joint => destroys it
23
+ sensor_to_check.GetUserData().has_joint = false;
24
+
25
+ // Gets a list of all the joints of the sensor body
26
+ let sensor_joints = [];
27
+ let _joint = sensor_to_check.GetJointList();
28
+ while(_joint != null){
29
+ sensor_joints.push(_joint.joint);
30
+ _joint = _joint.next;
31
+ }
32
+ // Finds the index of the first revolute joint
33
+ const isRevolute = (s) => s.m_type == b2.Joint.e_revoluteJoint;
34
+ let idx_to_destroy = sensor_joints.findIndex(isRevolute);
35
+ if(idx_to_destroy != -1){
36
+ world.DestroyJoint(sensor_joints[idx_to_destroy]);
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Creates joints between sensors ready to grasp if collision with graspable area was detected
45
+ * @param contact_detector {Object}
46
+ * @param world {Object} - Box2D world
47
+ */
48
+ after_step_climbing_dynamics(contact_detector, world){
49
+ // Adds climbing joints if needed
50
+ for(let i = 0; i < contact_detector.contact_dictionaries.sensors.length; i++){
51
+ let sensor = contact_detector.contact_dictionaries.sensors[i];
52
+ if(contact_detector.contact_dictionaries.bodies[i].length > 0
53
+ && sensor.GetUserData().ready_to_attach
54
+ && !sensor.GetUserData().has_joint){
55
+ let other_bodies = [...contact_detector.contact_dictionaries.bodies[i]];
56
+ for(let other_body of other_bodies){
57
+
58
+ // Checks if still overlapping after solver
59
+ // Super coarse yet fast way, mainly useful for creepers
60
+ let other_body_shape = other_body.GetFixtureList().GetShape();
61
+ let x_values = [];
62
+ let y_values = [];
63
+ if(other_body_shape.m_type == b2.Shape.e_polygon){
64
+ for(let i = 0; i < other_body_shape.m_count; i++) {
65
+ x_values.push(other_body.GetWorldPoint(other_body_shape.m_vertices[i]).x);
66
+ y_values.push(other_body.GetWorldPoint(other_body_shape.m_vertices[i]).y);
67
+ }
68
+ }
69
+ else if(other_body_shape.m_type == b2.Shape.e_edge){
70
+ x_values = [other_body_shape.m_vertex1.x, other_body_shape.m_vertex2.x];
71
+ y_values = [other_body_shape.m_vertex1.y, other_body_shape.m_vertex2.y];
72
+ }
73
+
74
+ let radius = sensor.GetFixtureList().GetShape().m_radius + 0.01;
75
+ let sensor_world_center = sensor.GetWorldCenter();
76
+
77
+ if(sensor_world_center.x + radius > Math.min(...x_values)
78
+ && sensor_world_center.x - radius < Math.max(...x_values)
79
+ && sensor_world_center.y + radius > Math.min(...y_values)
80
+ && sensor_world_center.y - radius < Math.max(...y_values)){
81
+
82
+ let rjd = new b2.RevoluteJointDef();
83
+ rjd.Initialize(sensor, other_body, sensor_world_center);
84
+ let joint = world.CreateJoint(rjd);
85
+ joint.SetUserData(new CustomBodyUserData(false, false, "grip"));
86
+ joint.GetBodyA().GetUserData().joint = joint;
87
+ sensor.GetUserData().has_joint = true;
88
+ break;
89
+ }
90
+ else {
91
+ // Removes other_body from the list of bodies in contact with the sensor
92
+ let sensor_idx = contact_detector.contact_dictionaries.sensors.indexOf(sensor);
93
+ if(sensor_idx != -1){
94
+ let other_idx = contact_detector.contact_dictionaries.bodies[sensor_idx].indexOf(other_body);
95
+ contact_detector.contact_dictionaries.bodies[sensor_idx].splice(other_idx, 1);
96
+
97
+ if(contact_detector.contact_dictionaries.bodies[sensor_idx].length == 0){
98
+ sensor.GetUserData().has_contact = false;
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ /**
109
+ * @classdesc Stores contacts between sensors and graspable surfaces in a dictionaries associated to the sensor.
110
+ * @constructor
111
+ */
112
+ function ClimbingContactDetector() {
113
+ b2.ContactListener.call(this);
114
+ this.contact_dictionaries = {
115
+ sensors: [],
116
+ bodies: []
117
+ };
118
+ }
119
+
120
+ ClimbingContactDetector.prototype = Object.create(b2.ContactListener.prototype);
121
+ ClimbingContactDetector.prototype.constructor = ClimbingContactDetector;
122
+ ClimbingContactDetector.prototype.BeginContact = function (contact) {
123
+ let bodies = [contact.GetFixtureA().GetBody(), contact.GetFixtureB().GetBody()];
124
+ for(let i = 0; i < bodies.length; i++){
125
+ let body = bodies[i];
126
+ if(body.GetUserData().object_type == CustomUserDataObjectTypes.BODY_SENSOR
127
+ && body.GetUserData().check_contact){
128
+ let other_body = bodies[(i + 1) % 2];
129
+ if(other_body.GetUserData().object_type == CustomUserDataObjectTypes.GRIP_TERRAIN
130
+ || other_body.GetUserData().object_type == CustomUserDataObjectTypes.SENSOR_GRIP_TERRAIN){
131
+ body.GetUserData().has_contact = true;
132
+ let idx = this.contact_dictionaries.sensors.indexOf(body);
133
+ if(idx != -1){
134
+ this.contact_dictionaries.bodies[idx].push(other_body);
135
+ }
136
+ else{
137
+ this.contact_dictionaries.sensors.push(body);
138
+ this.contact_dictionaries.bodies.push([other_body]);
139
+ }
140
+ }
141
+ else{
142
+ return;
143
+ }
144
+ }
145
+ }
146
+ };
147
+
148
+ ClimbingContactDetector.prototype.EndContact = function (contact){
149
+ let bodies = [contact.GetFixtureA().GetBody(), contact.GetFixtureB().GetBody()];
150
+ for(let i = 0; i < bodies.length; i++) {
151
+ let body = bodies[i];
152
+ let other_body = bodies[(i + 1) % 2];
153
+ if(body.GetUserData().object_type == CustomUserDataObjectTypes.BODY_SENSOR &&
154
+ body.GetUserData().check_contact && body.GetUserData().has_contact){
155
+ let body_idx = this.contact_dictionaries.sensors.indexOf(body);
156
+ if (body_idx != -1) {
157
+ let other_idx = this.contact_dictionaries.bodies[body_idx].indexOf(other_body);
158
+ if(other_idx != -1){
159
+ this.contact_dictionaries.bodies[body_idx].splice(other_idx, 1);
160
+ }
161
+
162
+ if(this.contact_dictionaries.bodies[body_idx].length == 0){
163
+ body.GetUserData().has_contact = false;
164
+ }
165
+ }
166
+
167
+ }
168
+ }
169
+ };
170
+
171
+ ClimbingContactDetector.prototype.Reset = function(){
172
+ this.contact_dictionaries = {
173
+ body: []
174
+ };
175
+ };
js/Box2D_dynamics/contact_detector.js ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @classdesc Class that derives from b2.ContactListener and that handles both water and climbing collisions.
3
+ * @param env {Object}
4
+ * @constructor
5
+ */
6
+ function ContactDetector (env){
7
+ b2.ContactListener.call(this);
8
+ this.water_contact_detector = new WaterContactDetector();
9
+ this.climbing_contact_detector = new ClimbingContactDetector();
10
+ this.env = env;
11
+ }
12
+
13
+ ContactDetector.prototype = Object.create(b2.ContactListener.prototype);
14
+ ContactDetector.prototype.constructor = ContactDetector;
15
+ ContactDetector.prototype.BeginContact = function (contact){
16
+ let bodies = [contact.GetFixtureA().GetBody(), contact.GetFixtureB().GetBody()];
17
+ const anyWater = (body) => body.GetUserData().object_type == CustomUserDataObjectTypes.WATER;
18
+ const anySensor = (body) => body.GetUserData().object_type == CustomUserDataObjectTypes.BODY_SENSOR;
19
+ if(bodies.some(anyWater)){
20
+ this.water_contact_detector.BeginContact(contact);
21
+ }
22
+ else if(bodies.some(anySensor)){
23
+ this.climbing_contact_detector.BeginContact(contact);
24
+ }
25
+ else{
26
+ if(contact.GetFixtureA().IsSensor() || contact.GetFixtureB().IsSensor()){
27
+ return;
28
+ }
29
+
30
+ for(let i = 0; i < bodies.length; i++){
31
+ let body = bodies[i];
32
+ if(body.GetUserData().object_type == CustomUserDataObjectTypes.BODY_OBJECT && body.GetUserData().check_contact){
33
+ body.GetUserData().has_contact = true;
34
+ }
35
+ }
36
+ }
37
+ };
38
+
39
+ ContactDetector.prototype.EndContact = function (contact){
40
+ let bodies = [contact.GetFixtureA().GetBody(), contact.GetFixtureB().GetBody()];
41
+ const anyWater = (body) => body.GetUserData().object_type == CustomUserDataObjectTypes.WATER;
42
+ const anySensor = (body) => body.GetUserData().object_type == CustomUserDataObjectTypes.BODY_SENSOR;
43
+ if(bodies.some(anyWater)){
44
+ this.water_contact_detector.EndContact(contact);
45
+ }
46
+ else if(bodies.some(anySensor)){
47
+ this.climbing_contact_detector.EndContact(contact);
48
+ }
49
+ else {
50
+ for(let body of bodies){
51
+ if(body.GetUserData().object_type == CustomUserDataObjectTypes.BODY_OBJECT && body.GetUserData().check_contact){
52
+ body.GetUserData().has_contact = false;
53
+ }
54
+ }
55
+ }
56
+ };
57
+
58
+ ContactDetector.prototype.Reset = function (){
59
+ this.water_contact_detector.Reset();
60
+ this.climbing_contact_detector.Reset();
61
+ }
62
+
63
+ /**
64
+ * @classdesc Class that derives from b2.RayCastCallback and that handles lidars raycasts.
65
+ * @param agent_mask_filter
66
+ * @constructor
67
+ */
68
+ function LidarCallback(agent_mask_filter){
69
+ b2.RayCastCallback.call(this);
70
+ this.agent_mask_filter = agent_mask_filter;
71
+ this.fixture = null;
72
+ this.is_water_detected = false;
73
+ this.is_creeper_detected = false;
74
+ };
75
+
76
+ LidarCallback.prototype = Object.create(b2.RayCastCallback.prototype);
77
+ LidarCallback.prototype.constructor = LidarCallback;
78
+ LidarCallback.prototype.ReportFixture = function (fixture, point, normal, fraction){
79
+ if((fixture.GetFilterData().categoryBits & this.agent_mask_filter) == 0){
80
+ return -1;
81
+ }
82
+
83
+ this.p2 = point;
84
+ this.fraction = fraction;
85
+ this.is_water_detected = fixture.GetBody().GetUserData().object_type == CustomUserDataObjectTypes.WATER;
86
+ this.is_creeper_detected = fixture.GetBody().GetUserData().object_type == CustomUserDataObjectTypes.SENSOR_GRIP_TERRAIN;
87
+ return fraction;
88
+ }
js/Box2D_dynamics/water_dynamics.js ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @classdesc Class that handles the water dynamics.
3
+ */
4
+ class WaterDynamics {
5
+ constructor(gravity, drag_mod=0.25, lift_mod=0.25, push_mod=0.05,
6
+ max_drag=2000, max_lift=500, max_push=13){
7
+ this.gravity = gravity;
8
+ this.drag_mod = drag_mod;
9
+ this.lift_mod = lift_mod;
10
+ this.max_drag = max_drag;
11
+ this.max_lift = max_lift;
12
+ this.push_mod = push_mod;
13
+ this.max_push = max_push;
14
+ }
15
+
16
+ compute_centroids(vectors){
17
+ let count = vectors.length;
18
+ console.assert(count >= 3);
19
+
20
+ let c = new b2.Vec2(0, 0);
21
+ let area = 0;
22
+ let ref_point = new b2.Vec2(0, 0);
23
+ let inv3 = 1/3;
24
+
25
+ for(let i = 0; i < count; i++){
26
+ // Triangle vertices
27
+ let p1 = ref_point;
28
+ let p2 = vectors[i];
29
+ let p3 = i + 1 < count ? vectors[i + 1] : vectors[0];
30
+
31
+ let e1 = b2.Vec2.Subtract(p2, p1);
32
+ let e2 = b2.Vec2.Subtract(p3, p1);
33
+ let d = b2.Cross_v2_v2(e1, e2);
34
+ let triangle_area = 0.5 * d;
35
+ area += triangle_area;
36
+
37
+ // Area weighted centroid
38
+ c.Add(b2.Vec2.Multiply(triangle_area * inv3, b2.Vec2.Add(p1, b2.Vec2.Add(p2, p3))));
39
+ }
40
+
41
+ if(area > b2.epsilon){
42
+ c.Multiply(1/area);
43
+ }
44
+ else{
45
+ area = 0;
46
+ }
47
+
48
+ return [c, area];
49
+ }
50
+
51
+ inside(cp1, cp2, p){
52
+ return (cp2.x - cp1.x) * (p.y - cp1.y) > (cp2.y - cp1.y) * (p.x - cp1.x);
53
+ }
54
+
55
+ intersection(cp1, cp2, s, e){
56
+ let dc = new b2.Vec2(cp1.x - cp2.x, cp1.y - cp2.y);
57
+ let dp = new b2.Vec2(s.x - e.x, s.y - e.y);
58
+ let n1 = cp1.x * cp2.y - cp1.y * cp2.x;
59
+ let n2 = s.x * e.y - s.y * e.x;
60
+ let n3 = 1.0 / (dc.x * dp.y - dc.y * dp.x);
61
+ return new b2.Vec2((n1 * dp.x - n2 * dc.x) * n3, (n1 * dp.y - n2 * dc.y) * n3);
62
+ }
63
+
64
+ find_intersection(fixture_A, fixture_B){
65
+ // TODO : assert polygons
66
+ let output_vertices = [];
67
+ let polygon_A = fixture_A.GetShape();
68
+ let polygon_B = fixture_B.GetShape();
69
+
70
+ // fill 'subject polygon' from fixture_A polygon
71
+ for(let i = 0; i < polygon_A.m_count; i++){
72
+ output_vertices.push(fixture_A.GetBody().GetWorldPoint(polygon_A.m_vertices[i]));
73
+ }
74
+
75
+ // fill 'clip polygon' from fixture_B polygon
76
+ let clip_polygon = [];
77
+ for(let i = 0; i < polygon_B.m_count; i++){
78
+ clip_polygon.push(fixture_B.GetBody().GetWorldPoint(polygon_B.m_vertices[i]));
79
+ }
80
+
81
+ let cp1 = clip_polygon[clip_polygon.length - 1];
82
+ for(let j = 0; j < clip_polygon.length; j++){
83
+ let cp2 = clip_polygon[j];
84
+ if(output_vertices.length == 0){
85
+ break;
86
+ }
87
+ let input_list = output_vertices.slice();
88
+ output_vertices = [];
89
+
90
+ let s = input_list[input_list.length - 1];
91
+ for(let i = 0; i < input_list.length; i++){
92
+ let e = input_list[i];
93
+ if(this.inside(cp1, cp2, e)){
94
+ if(!this.inside(cp1, cp2, s)){
95
+ output_vertices.push(this.intersection(cp1, cp2, s, e));
96
+ }
97
+ output_vertices.push(e);
98
+ }
99
+ else if(this.inside(cp1, cp2, s)){
100
+ output_vertices.push(this.intersection(cp1, cp2, s, e));
101
+ }
102
+ s = e;
103
+ }
104
+ cp1 = cp2
105
+ }
106
+ return [(output_vertices.length != 0), output_vertices];
107
+ }
108
+
109
+ calculate_forces(fixture_pairs){
110
+ for(let pair of fixture_pairs){
111
+ let density = pair[0].GetDensity();
112
+ let [has_intersection, intersection_points] = this.find_intersection(pair[0], pair[1]);
113
+ if(has_intersection){
114
+ let [centroid, area] = this.compute_centroids(intersection_points);
115
+
116
+ // apply buoyancy force
117
+ let displaced_mass = pair[0].GetDensity() * area;
118
+ let buoyancy_force = b2.Vec2.Multiply(displaced_mass, b2.Vec2.Negate(this.gravity));
119
+ pair[1].GetBody().ApplyForce(buoyancy_force, centroid, true);
120
+
121
+ // apply complex drag
122
+ for(let i = 0; i < intersection_points.length; i++) {
123
+ let v0 = intersection_points[i];
124
+ let v1 = intersection_points[(i + 1) % intersection_points.length];
125
+ let mid_point = b2.Vec2.Multiply(0.5, b2.Vec2.Add(v0, v1));
126
+
127
+ // DRAG
128
+ // find relative velocity between object and fluid at edge midpoint
129
+ let vel_dir = b2.Vec2.Subtract(pair[1].GetBody().GetLinearVelocityFromWorldPoint(mid_point),
130
+ pair[0].GetBody().GetLinearVelocityFromWorldPoint(mid_point));
131
+ let vel = vel_dir.Normalize();
132
+
133
+ let edge = b2.Vec2.Subtract(v1, v0);
134
+ let edge_length = edge.Normalize();
135
+ let normal = b2.Cross_f_v2(-1, edge);
136
+ let drag_dot = b2.Dot_v2_v2(normal, vel_dir);
137
+ if(drag_dot >= 0){ // normal points backwards - this is not a leading edge
138
+ // apply drag
139
+ let drag_mag = drag_dot * this.drag_mod * edge_length * density * vel * vel;
140
+ drag_mag = Math.min(drag_mag, this.max_drag);
141
+ let drag_force = b2.Vec2.Multiply(drag_mag, b2.Vec2.Negate(vel_dir));
142
+ pair[1].GetBody().ApplyForce(drag_force, mid_point, true);
143
+
144
+ // apply lift
145
+ let lift_dot = b2.Dot_v2_v2(edge, vel_dir);
146
+ let lift_mag = drag_dot * lift_dot * this.lift_mod * edge_length * density * vel * vel;
147
+ lift_mag = Math.min(lift_mag, this.max_lift);
148
+ let lift_dir = b2.Cross_f_v2(1, vel_dir);
149
+ let lift_force = b2.Vec2.Multiply(lift_mag, lift_dir);
150
+ pair[1].GetBody().ApplyForce(lift_force, mid_point, true);
151
+ }
152
+
153
+ // PUSH
154
+ let body_to_check = pair[1].GetBody();
155
+ // Simplification /!\
156
+ let joints_to_check = [];
157
+ let joint_edge = body_to_check.GetJointList();
158
+ while(joint_edge != null){
159
+ if(joint_edge.joint.GetBodyB() == body_to_check){
160
+ joints_to_check.push(joint_edge.joint);
161
+ }
162
+ joint_edge = joint_edge.next;
163
+ }
164
+
165
+ for(let joint of joints_to_check){
166
+ if(joint.GetLowerLimit() < joint.GetJointAngle() && joint.GetJointAngle() < joint.GetUpperLimit()){
167
+ let torque = joint.GetMotorTorque(60);
168
+
169
+ // Calculate angular inertia of the object
170
+ let moment_of_inertia = body_to_check.GetInertia();
171
+ let angular_velocity = body_to_check.GetAngularVelocity();
172
+ let angular_inertia = moment_of_inertia * angular_velocity;
173
+
174
+ // Calculate the force applied to the object
175
+ let world_center = body_to_check.GetWorldCenter();
176
+ let anchor = joint.GetAnchorB();
177
+ let lever_vector = b2.Vec2.Subtract(world_center, anchor); // vector from pivot to point of application of the force
178
+ let force_applied_at_center = b2.Cross_v2_f(lever_vector, -torque);
179
+
180
+ let push_dot = b2.Dot_v2_v2(normal, force_applied_at_center);
181
+ if(push_dot > 0){
182
+ vel = torque + angular_inertia;
183
+ // Wrong approximation /!\
184
+ let push_mag = push_dot * this.push_mod * edge_length * density * vel * vel;
185
+ let force = b2.Vec2.Multiply(push_mag, b2.Vec2.Negate(force_applied_at_center));
186
+ let clip_force_x = Math.max(-this.max_push, Math.min(force.x, this.max_push));
187
+ let clip_force_y = Math.max(-this.max_push, Math.min(force.y, this.max_push))
188
+ let push_force = new b2.Vec2(clip_force_x, clip_force_y);
189
+ body_to_check.ApplyForce(push_force, joint.GetAnchorB(), true);
190
+ }
191
+ }
192
+ }
193
+
194
+ }
195
+
196
+ }
197
+
198
+ }
199
+ }
200
+ }
201
+
202
+ /**
203
+ * @classdesc Stores fixtures of objects in contact with water.
204
+ * @constructor
205
+ */
206
+ function WaterContactDetector() {
207
+ b2.ContactListener.call(this);
208
+ this.fixture_pairs = [];
209
+ }
210
+
211
+ WaterContactDetector.prototype = Object.create(b2.ContactListener.prototype);
212
+ WaterContactDetector.prototype.constructor = WaterContactDetector;
213
+ WaterContactDetector.prototype.BeginContact = function (contact){
214
+ if(contact.GetFixtureA().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.WATER
215
+ && contact.GetFixtureB().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.BODY_OBJECT){
216
+ this.fixture_pairs.push([contact.GetFixtureA(), contact.GetFixtureB()]);
217
+ }
218
+ else if(contact.GetFixtureB().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.WATER
219
+ && contact.GetFixtureA().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.BODY_OBJECT){
220
+ this.fixture_pairs.push([contact.GetFixtureB(), contact.GetFixtureA()]);
221
+ }
222
+ };
223
+
224
+ WaterContactDetector.prototype.EndContact = function (contact) {
225
+ if(contact.GetFixtureA().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.WATER
226
+ && contact.GetFixtureB().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.BODY_OBJECT){
227
+ let index = this.fixture_pairs.indexOf([contact.GetFixtureA(), contact.GetFixtureB()]);
228
+ if (index !== -1) {
229
+ this.fixture_pairs.splice(index, 1);
230
+ }
231
+ }
232
+ else if(contact.GetFixtureB().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.WATER
233
+ && contact.GetFixtureA().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.BODY_OBJECT){
234
+ let index = this.fixture_pairs.indexOf([contact.GetFixtureB(), contact.GetFixtureA()]);
235
+ if (index !== -1) {
236
+ this.fixture_pairs.splice(index, 1);
237
+ }
238
+ }
239
+ };
240
+
241
+ WaterContactDetector.prototype.Reset = function (){
242
+ this.fixture_pairs = [];
243
+ };
244
+
js/CPPN/cppn.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @classdesc CPPN class.
3
+ */
4
+ class CPPN {
5
+
6
+ /**
7
+ * @constructor
8
+ * @param x_dim {number} - Number of terrain steps to generate
9
+ * @param input_dim {number} - Dimension of the input encoding vector
10
+ */
11
+ constructor(x_dim, input_dim){
12
+ this.x_dim = x_dim;
13
+ this.input_dim = input_dim;
14
+ this.cppn_model = window.cppn_model;
15
+ }
16
+
17
+ /**
18
+ * Generates the terrain shapes with the CPPN model.
19
+ * @param input_vector {Array} - 3-dimensional array that encodes the CPPN
20
+ * @returns {Array} - Array of y-coordinates of the terrain shapes : [[y_ground, y_ceiling]
21
+ */
22
+ generate(input_vector){
23
+ let x = [...Array(this.x_dim).keys()];
24
+ let scaled_x = x.map(e => e / (this.x_dim - 1));
25
+ let x_vec = scaled_x.map(e => [e]);
26
+ let final_input = [];
27
+ for(let i = 0; i < this.x_dim; i++){
28
+ final_input.push([x_vec[i].concat(input_vector)]);
29
+ }
30
+ final_input = tf.tensor(final_input);
31
+ return this.cppn_model.predict(final_input.reshape([this.x_dim, this.input_dim + 1]));
32
+ }
33
+ }
js/CPPN/weights/ground_cppn/.data-00000-of-00001 ADDED
Binary file (50.4 kB). View file
 
js/CPPN/weights/ground_cppn/.index ADDED
Binary file (249 Bytes). View file
 
js/CPPN/weights/ground_cppn/.meta ADDED
Binary file (11.1 kB). View file
 
js/CPPN/weights/ground_cppn/checkpoint ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ model_checkpoint_path: "."
2
+ all_model_checkpoint_paths: "."
3
+ all_model_checkpoint_paths: "."
js/CPPN/weights/same_ground_ceiling_cppn/.data-00000-of-00001 ADDED
Binary file (50.7 kB). View file
 
js/CPPN/weights/same_ground_ceiling_cppn/.index ADDED
Binary file (249 Bytes). View file
 
js/CPPN/weights/same_ground_ceiling_cppn/.meta ADDED
Binary file (11.1 kB). View file
 
js/CPPN/weights/same_ground_ceiling_cppn/checkpoint ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ model_checkpoint_path: "."
2
+ all_model_checkpoint_paths: "."
3
+ all_model_checkpoint_paths: "."
js/CPPN/weights/same_ground_ceiling_cppn/frozen_model/saved_model.pb ADDED
Binary file (52.8 kB). View file
 
js/CPPN/weights/same_ground_ceiling_cppn/tfjs_model/group1-shard1of1.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:26132af7a3f2d2209504d78a3079b88fbf4d971d1de5485d41bc1cd7e272890c
3
+ size 50688
js/CPPN/weights/same_ground_ceiling_cppn/tfjs_model/model.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"format": "graph-model", "generatedBy": "1.15.0", "convertedBy": "TensorFlow.js Converter v3.3.0", "signature": {"outputs": {"Reshape": {"name": "Reshape"}}}, "modelTopology": {"node": [{"name": "Placeholder", "op": "Placeholder", "attr": {"dtype": {"type": "DT_FLOAT"}, "shape": {"shape": {"dim": [{"size": "200"}, {"size": "4"}]}}}}, {"name": "Variable", "op": "Const", "attr": {"value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "4"}, {"size": "64"}]}}}, "dtype": {"type": "DT_FLOAT"}}}, {"name": "Variable_4", "op": "Const", "attr": {"value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "64"}, {"size": "2"}]}}}, "dtype": {"type": "DT_FLOAT"}}}, {"name": "Variable_1", "op": "Const", "attr": {"value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "64"}, {"size": "64"}]}}}, "dtype": {"type": "DT_FLOAT"}}}, {"name": "Variable_2", "op": "Const", "attr": {"dtype": {"type": "DT_FLOAT"}, "value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "64"}, {"size": "64"}]}}}}}, {"name": "Variable_3", "op": "Const", "attr": {"value": {"tensor": {"dtype": "DT_FLOAT", "tensorShape": {"dim": [{"size": "64"}, {"size": "64"}]}}}, "dtype": {"type": "DT_FLOAT"}}}, {"name": "MatMul", "op": "MatMul", "input": ["Placeholder", "Variable"], "device": "/device:CPU:0", "attr": {"transpose_a": {"b": false}, "transpose_b": {"b": false}, "T": {"type": "DT_FLOAT"}}}, {"name": "Tanh", "op": "Tanh", "input": ["MatMul"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "MatMul_1", "op": "MatMul", "input": ["Tanh", "Variable_1"], "device": "/device:CPU:0", "attr": {"transpose_b": {"b": false}, "T": {"type": "DT_FLOAT"}, "transpose_a": {"b": false}}}, {"name": "Softplus", "op": "Softplus", "input": ["MatMul_1"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "MatMul_2", "op": "MatMul", "input": ["Softplus", "Variable_2"], "device": "/device:CPU:0", "attr": {"transpose_b": {"b": false}, "transpose_a": {"b": false}, "T": {"type": "DT_FLOAT"}}}, {"name": "Tanh_1", "op": "Tanh", "input": ["MatMul_2"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "MatMul_3", "op": "MatMul", "input": ["Tanh_1", "Variable_3"], "device": "/device:CPU:0", "attr": {"transpose_b": {"b": false}, "T": {"type": "DT_FLOAT"}, "transpose_a": {"b": false}}}, {"name": "Softplus_1", "op": "Softplus", "input": ["MatMul_3"], "attr": {"T": {"type": "DT_FLOAT"}}}, {"name": "MatMul_4", "op": "MatMul", "input": ["Softplus_1", "Variable_4"], "device": "/device:CPU:0", "attr": {"transpose_b": {"b": false}, "T": {"type": "DT_FLOAT"}, "transpose_a": {"b": false}}}, {"name": "Reshape", "op": "Identity", "input": ["MatMul_4"], "attr": {"T": {"type": "DT_FLOAT"}}}], "library": {}, "versions": {}}, "weightsManifest": [{"paths": ["group1-shard1of1.bin"], "weights": [{"name": "Variable", "shape": [4, 64], "dtype": "float32"}, {"name": "Variable_4", "shape": [64, 2], "dtype": "float32"}, {"name": "Variable_1", "shape": [64, 64], "dtype": "float32"}, {"name": "Variable_2", "shape": [64, 64], "dtype": "float32"}, {"name": "Variable_3", "shape": [64, 64], "dtype": "float32"}]}]}
js/bodies/abstract_body.js ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @classdesc Abstract class for agent's morphologies
3
+ */
4
+ class AbstractBody {
5
+
6
+ /**
7
+ * @constructor
8
+ * @param scale {number} - Scale of the environment
9
+ * @param motors_torque {number}
10
+ */
11
+ constructor(scale, motors_torque){
12
+ this.SCALE = scale;
13
+ this.MOTORS_TORQUE = motors_torque;
14
+ this.body_parts = [];
15
+ this.motors = [];
16
+ this.is_selected = false;
17
+ }
18
+
19
+ /**
20
+ * Gets the size of the motors state.
21
+ * @return {number}
22
+ */
23
+ get_state_size(){
24
+ return this.get_motors_state().length;
25
+ }
26
+
27
+ /**
28
+ * Gets the motors state.
29
+ * @return {Array}
30
+ */
31
+ get_motors_state(){
32
+ let state = [];
33
+ for(let motor of this.motors){
34
+ let motor_info = motor.GetUserData();
35
+ if(motor_info.check_contact){
36
+ let s = [
37
+ motor.GetJointAngle() + motor_info.angle_correction,
38
+ motor.GetJointSpeed() / motor_info.speed_control,
39
+ 0.0
40
+ ]
41
+ if(motor_info.contact_body.GetUserData().has_contact){
42
+ s[2] = 1.0;
43
+ }
44
+ state = state.concat(s);
45
+ }
46
+ else{
47
+ state = state.concat([
48
+ motor.GetJointAngle() + motor_info.angle_correction,
49
+ motor.GetJointSpeed() / motor_info.speed_control
50
+ ])
51
+ }
52
+ }
53
+ return state;
54
+ }
55
+
56
+ /**
57
+ * Gets the size of the action space.
58
+ * @return {number}
59
+ */
60
+ get_action_size(){
61
+ return this.motors.length;
62
+ }
63
+
64
+ /**
65
+ * Activates the motors according to the given actions by setting the motors speed and torque.
66
+ * @param actions {Array}
67
+ */
68
+ activate_motors(actions){
69
+ for(let i = 0; i < this.motors.length; i++){
70
+ this.motors[i].SetMotorSpeed(this.motors[i].GetUserData().speed_control * Math.sign(actions[i]));
71
+ let clamp01 = Math.max(0, Math.min(Math.abs(actions[i]), 1));
72
+ this.motors[i].SetMaxMotorTorque(this.MOTORS_TORQUE * clamp01);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Creates the Box2D body parts, joints and sensors of the agent.
78
+ * @param world {Object} - Box2D world
79
+ * @param init_x {number}
80
+ * @param init_y {number}
81
+ * @param force_to_center {number}
82
+ */
83
+ draw(world, init_x, init_y, force_to_center){}
84
+
85
+ /**
86
+ * Gets all the body parts
87
+ * @return {Array}
88
+ */
89
+ get_elements_to_render(){
90
+ return this.body_parts;
91
+ }
92
+
93
+ /**
94
+ * Checks if the given position is inside the agent's morphology
95
+ * @param pos {{x: number, y: number}}
96
+ * @return {boolean}
97
+ */
98
+ isPosInside(pos){
99
+ for(let body of this.body_parts){
100
+ let shape = body.GetFixtureList().GetShape();
101
+ let vertices = [];
102
+ for(let i = 0; i < shape.m_count; i++){
103
+ let world_pos = body.GetWorldPoint(shape.m_vertices[i]);
104
+ vertices.push({x: world_pos.x, y: world_pos.y});
105
+ }
106
+
107
+ // Counts the number of intersections between the edges of the polygon and the line of equation y = pos.y which are to the right of pos.x
108
+ let nb_intersections = 0;
109
+ for(let i = 0; i < vertices.length; i++){
110
+ let v1 = vertices[i];
111
+ let v2;
112
+ if(i == vertices.length - 1){
113
+ v2 = vertices[0];
114
+ }
115
+ else {
116
+ v2 = vertices[i+1];
117
+ }
118
+
119
+ // Checks if the edge between v1 and v2 cross the mouse y-coordinate
120
+ if(pos.y >= Math.min(v1.y, v2.y) && pos.y <= Math.max(v1.y, v2.y)){
121
+ let intersection_x;
122
+
123
+ // Computes the equation of the line between v1 and v2
124
+ let a = (v2.y - v1.y) / (v2.x - v1.x);
125
+ let b = v1.y - a * v1.x;
126
+
127
+ // Computes the x-coordinate of the intersection point
128
+ if(Math.abs(a) == Infinity){
129
+ intersection_x = v1.x;
130
+ }
131
+ else{
132
+ intersection_x = (pos.y - b) / a;
133
+ }
134
+
135
+ // Increases the number of intersection only if the intersection point is to the right of the mouse x-coordinate
136
+ if(intersection_x >= pos.x) {
137
+ nb_intersections += 1;
138
+ }
139
+ }
140
+ }
141
+
142
+ // The pos is inside the agent's body if there is an odd number of intersections, else it is outside
143
+ if(nb_intersections % 2 != 0){
144
+ return true;
145
+ }
146
+ }
147
+ return false;
148
+ }
149
+
150
+ /**
151
+ * Destroys all the body parts of the agents.
152
+ * @param world {Object} - Box2D world
153
+ */
154
+ destroy(world){
155
+ for(let body of this.body_parts){
156
+ world.DestroyBody(body);
157
+ }
158
+ this.body_parts = [];
159
+ this.motors = [];
160
+ }
161
+ }
js/bodies/bodies_enum.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ let BodyTypesEnum = {
2
+ WALKER: 0,
3
+ SWIMMER: 1,
4
+ CLIMBER: 2,
5
+ AMPHIBIAN: 3,
6
+ };
7
+
js/bodies/climbers/climber_abstract_body.js ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @classdesc Abstract class for climber morphologies.
3
+ */
4
+ class ClimberAbstractBody extends WalkerAbstractBody {
5
+ /**
6
+ * @constructor
7
+ * @param scale {number} - Scale of the environment
8
+ * @param motors_torque {number}
9
+ * @param nb_steps_under_water {number}
10
+ */
11
+ constructor(scale, motors_torque, nb_steps_under_water){
12
+ super(scale, motors_torque, nb_steps_under_water);
13
+
14
+ this.body_type = BodyTypesEnum.CLIMBER;
15
+ this.sensors = [];
16
+ this.SENSOR_FD = new b2.FixtureDef();
17
+ this.SENSOR_FD.shape = new b2.CircleShape();
18
+ this.SENSOR_FD.shape.m_radius = 0.05;
19
+ this.SENSOR_FD.density = 1.0;
20
+ this.SENSOR_FD.restitution = 0.0;
21
+ this.SENSOR_FD.filter.categoryBits = 0x20;
22
+ this.SENSOR_FD.filter.maskBits = 0x1;
23
+ this.SENSOR_FD.isSensor = true;
24
+ }
25
+
26
+ // States
27
+ get_state_size() {
28
+ return super.get_state_size() + this.get_sensors_state().length;
29
+ }
30
+
31
+ get_sensors_state(){
32
+ let state = [];
33
+ for(let sensor of this.sensors){
34
+ state = state.concat([
35
+ sensor.GetUserData().has_contact ? 1 : 0,
36
+ sensor.GetUserData().has_joint ? 1 : 0
37
+ ]);
38
+ }
39
+ return state;
40
+ }
41
+
42
+ // Actions
43
+ get_action_size() {
44
+ return super.get_action_size() + this.sensors.length;
45
+ }
46
+
47
+ // Draw
48
+ get_elements_to_render() {
49
+ return super.get_elements_to_render().concat(this.sensors);
50
+ }
51
+
52
+ // Destroy
53
+ destroy(world) {
54
+ super.destroy(world);
55
+ for(let sensor of this.sensors){
56
+ world.DestroyBody(sensor);
57
+ }
58
+ this.sensors = [];
59
+ }
60
+ }
js/bodies/climbers/climbing_profile_chimpanzee.js ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const CHIMP_SPEED_HIP = 4;
2
+ const CHIMP_SPEED_KNEE = 6;
3
+ const CHIMP_SPEED_HAND = 8;
4
+
5
+ /**
6
+ * @classdesc Chimpanzee morphology.
7
+ */
8
+ class ClimbingProfileChimpanzee extends ClimberAbstractBody {
9
+ /**
10
+ * @constructor
11
+ * @param scale {number} - Scale of the environment
12
+ * @param motors_torque {number}
13
+ * @param nb_steps_under_water {number}
14
+ */
15
+ constructor(scale, motors_torque=100, nb_steps_under_water=600){
16
+ super(scale, motors_torque, nb_steps_under_water);
17
+
18
+ this.LEG_DOWN = 12 / this.SCALE;
19
+ this.ARM_UP = 22 / this.SCALE;
20
+ this.LIMB_W = 8 / this.SCALE;
21
+ this.LIMB_H = 28 / this.SCALE;
22
+ this.HAND_PART_W = 4 / this.SCALE;
23
+ this.HAND_PART_H = 8 / this.SCALE;
24
+ this.LEG_H = this.LIMB_H;
25
+ this.TORQUE_PENALTY = 0.00035 / 5; // Legs + arms + hands
26
+ this.BODY_HEIGHT = 45;
27
+ this.HEAD_HEIGHT = 20;
28
+ this.HEAD_UP = 2.5 / this.SCALE; // formerly 0.2
29
+
30
+ this.AGENT_WIDTH = 24 / this.SCALE;
31
+ this.AGENT_HEIGHT = this.BODY_HEIGHT / this.SCALE
32
+ + this.HEAD_HEIGHT / this.SCALE
33
+ + this.HEAD_UP + this.LEG_H * 2
34
+ - this.LEG_DOWN;
35
+ this.AGENT_CENTER_HEIGHT = this.LEG_H * 2 + this.LEG_DOWN;
36
+
37
+ this.remove_reward_on_head_angle = true;
38
+ }
39
+
40
+ draw(world, init_x, init_y, force_to_center){
41
+
42
+ // Head
43
+ let head_fd = new b2.FixtureDef();
44
+ head_fd.shape = new b2.PolygonShape();
45
+ head_fd.shape.SetAsBox(5 / this.SCALE, 10 / this.SCALE);
46
+ /*head_fd.shape.Set([
47
+ new b2.Vec2(-5 / this.SCALE, 10 / this.SCALE),
48
+ new b2.Vec2(5 / this.SCALE, 10 / this.SCALE),
49
+ new b2.Vec2(5 / this.SCALE, -10 / this.SCALE),
50
+ new b2.Vec2(-5 / this.SCALE, -10 / this.SCALE),
51
+ ], 4);*/
52
+ head_fd.density = 5.0;
53
+ head_fd.friction = 0.1;
54
+ head_fd.filter.categoryBits = 0x20;
55
+ head_fd.filter.maskBits = 0x1;
56
+
57
+ let head_bd = new b2.BodyDef();
58
+ head_bd.type = b2.Body.b2_dynamicBody;
59
+ head_bd.position.Set(init_x,
60
+ init_y + this.BODY_HEIGHT / this.SCALE / 2 + this.HEAD_HEIGHT / this.SCALE / 2 + this.HEAD_UP);
61
+ let head = world.CreateBody(head_bd);
62
+ head.CreateFixture(head_fd);
63
+ head.color1 = "#806682"; // [0.5, 0.4, 0.9]
64
+ head.color2 = "#4D4D80";
65
+ head.ApplyForceToCenter(new b2.Vec2(force_to_center, 0), true);
66
+ head.SetUserData(new CustomBodyUserData(true, true, "head"));
67
+ this.body_parts.push(head);
68
+ this.reference_head_object = head;
69
+
70
+ // Body
71
+ let body_fd = new b2.FixtureDef();
72
+ body_fd.shape = new b2.PolygonShape();
73
+ body_fd.shape.Set([
74
+ new b2.Vec2(-12 / this.SCALE, 25 / this.SCALE),
75
+ new b2.Vec2(12 / this.SCALE, 25 / this.SCALE),
76
+ new b2.Vec2(8 / this.SCALE, -20 / this.SCALE),
77
+ new b2.Vec2(-8 / this.SCALE, -20 / this.SCALE),
78
+ ], 4);
79
+ body_fd.density = 5.0;
80
+ body_fd.friction = 0.1;
81
+ body_fd.filter.categoryBits = 0x20;
82
+ body_fd.filter.maskBits = 0x1; // collide only with ground
83
+
84
+ let body_bd = new b2.BodyDef();
85
+ body_bd.type = b2.Body.b2_dynamicBody;
86
+ body_bd.position.Set(init_x, init_y);
87
+ let body = world.CreateBody(body_bd);
88
+ body.CreateFixture(body_fd);
89
+ body.color1 = "#806682"; // [0.5, 0.4, 0.9]
90
+ body.color2 = "#4D4D80"; // [0.3, 0.3, 0.5]
91
+ body.SetUserData(new CustomBodyUserData(true, true, "body"));
92
+ this.body_parts.push(body);
93
+
94
+ // Revolute joint between head and body
95
+ let rjd = new b2.RevoluteJointDef();
96
+ rjd.Initialize(head, body, new b2.Vec2(init_x, init_y + this.BODY_HEIGHT / this.SCALE / 2));
97
+ rjd.enableMotor = false;
98
+ rjd.enableLimit = true;
99
+ rjd.lowerAngle = -0.1 * Math.PI;
100
+ rjd.upperAngle = 0.1 * Math.PI;
101
+ let joint_motor = world.CreateJoint(rjd);
102
+ joint_motor.SetUserData(new CustomMotorUserData("neck", 0, false));
103
+ this.neck_joint = joint_motor;
104
+
105
+ // Limbs FixtureDef
106
+ let UPPER_LIMB_FD = new b2.FixtureDef();
107
+ UPPER_LIMB_FD.shape = new b2.PolygonShape();
108
+ UPPER_LIMB_FD.shape.SetAsBox(this.LIMB_W / 2, this.LIMB_H / 2);
109
+ UPPER_LIMB_FD.density = 1.0;
110
+ UPPER_LIMB_FD.restitution = 0;
111
+ UPPER_LIMB_FD.filter.categoryBits = 0x20;
112
+ UPPER_LIMB_FD.filter.maskBits = 0x1;
113
+
114
+ let LOWER_LIMB_FD = new b2.FixtureDef();
115
+ LOWER_LIMB_FD.shape = new b2.PolygonShape();
116
+ LOWER_LIMB_FD.shape.SetAsBox(0.8 * this.LIMB_W / 2, this.LIMB_H / 2);
117
+ LOWER_LIMB_FD.density = 1.0;
118
+ LOWER_LIMB_FD.restitution = 0;
119
+ LOWER_LIMB_FD.filter.categoryBits = 0x20;
120
+ LOWER_LIMB_FD.filter.maskBits = 0x1;
121
+
122
+ let HAND_PART_FD = new b2.FixtureDef();
123
+ HAND_PART_FD.shape = new b2.PolygonShape();
124
+ HAND_PART_FD.shape.SetAsBox(this.HAND_PART_W / 2, this.HAND_PART_H / 2);
125
+ HAND_PART_FD.density = 1.0;
126
+ HAND_PART_FD.restitution = 0;
127
+ HAND_PART_FD.filter.categoryBits = 0x20;
128
+ HAND_PART_FD.filter.maskBits = 0x1;
129
+
130
+ // Legs
131
+ for(let i = 0; i < 2; i++){
132
+
133
+ // Upper leg
134
+ let upper_bd = new b2.BodyDef();
135
+ upper_bd.type = b2.Body.b2_dynamicBody;
136
+ upper_bd.position.Set(init_x, init_y - this.LIMB_H / 2 - this.LEG_DOWN);
137
+ let upper = world.CreateBody(upper_bd);
138
+ upper.CreateFixture(UPPER_LIMB_FD);
139
+ upper.color1 = "#964A7D"; // [0.59, 0.29, 0.49]
140
+ upper.color2 = "#63304A"; // [0.39, 0.19, 0.29]
141
+ upper.SetUserData(new CustomBodyUserData(false, false, "upper_leg"));
142
+ this.body_parts.push(upper);
143
+
144
+ // Revolute joint between body and upper leg
145
+ rjd = new b2.RevoluteJointDef();
146
+ rjd.Initialize(body, upper, new b2.Vec2(init_x, init_y - this.LEG_DOWN));
147
+ rjd.enableMotor = true;
148
+ rjd.enableLimit = true;
149
+ rjd.maxMotorTorque = this.MOTORS_TORQUE;
150
+ rjd.motorSpeed = 1;
151
+ rjd.lowerAngle = -0.3 * Math.PI;
152
+ rjd.upperAngle = 0.6 * Math.PI;
153
+ joint_motor = world.CreateJoint(rjd);
154
+ joint_motor.SetUserData(new CustomMotorUserData("hip", CHIMP_SPEED_HIP, false));
155
+ this.motors.push(joint_motor);
156
+
157
+ // Lower leg
158
+ let lower_bd = new b2.BodyDef();
159
+ lower_bd.type = b2.Body.b2_dynamicBody;
160
+ lower_bd.position.Set(init_x, init_y - this.LIMB_H * 3/2 - this.LEG_DOWN);
161
+ let lower = world.CreateBody(lower_bd);
162
+ lower.CreateFixture(LOWER_LIMB_FD);
163
+ lower.color1 = "#964A7D"; // [0.59, 0.29, 0.49]
164
+ lower.color2 = "#63304A"; // [0.39, 0.19, 0.29]
165
+ lower.SetUserData(new CustomBodyUserData(true, true, "lower_leg"));
166
+ this.body_parts.push(lower);
167
+
168
+ // Revolute joint between upper and lower leg
169
+ rjd = new b2.RevoluteJointDef();
170
+ rjd.Initialize(upper, lower, new b2.Vec2(init_x, init_y - this.LEG_H - this.LEG_DOWN));
171
+ rjd.enableMotor = true;
172
+ rjd.enableLimit = true;
173
+ rjd.maxMotorTorque = this.MOTORS_TORQUE;
174
+ rjd.motorSpeed = 1;
175
+ rjd.lowerAngle = -0.75 * Math.PI;
176
+ rjd.upperAngle = -0.1;
177
+ joint_motor = world.CreateJoint(rjd);
178
+ joint_motor.SetUserData(new CustomMotorUserData("knee",
179
+ CHIMP_SPEED_KNEE,
180
+ true,
181
+ 1,
182
+ lower));
183
+ this.motors.push(joint_motor);
184
+ }
185
+
186
+ // Arms
187
+ for(let i = 0; i < 2; i++){
188
+ // Upper arm
189
+ let upper_bd = new b2.BodyDef();
190
+ upper_bd.type = b2.Body.b2_dynamicBody;
191
+ upper_bd.position.Set(init_x, init_y + this.LIMB_H / 2 + this.ARM_UP);
192
+ let upper = world.CreateBody(upper_bd);
193
+ upper.CreateFixture(UPPER_LIMB_FD);
194
+ upper.color1 = "#9C4F82"; // [0.61, 0.31, 0.51]
195
+ upper.color2 = "#69364F"; // [0.41, 0.21, 0.31]
196
+ upper.SetUserData(new CustomBodyUserData(false, false, "upper_arm"));
197
+ this.body_parts.push(upper);
198
+
199
+ // Revolute joint between body and upper arm
200
+ rjd = new b2.RevoluteJointDef();
201
+ rjd.Initialize(body, upper, new b2.Vec2(init_x, init_y + this.ARM_UP));
202
+ rjd.enableMotor = true;
203
+ rjd.enableLimit = true;
204
+ rjd.maxMotorTorque = this.MOTORS_TORQUE;
205
+ rjd.motorSpeed = 1;
206
+ rjd.lowerAngle = -0.75 * 2 * Math.PI;
207
+ rjd.upperAngle = 0;
208
+ joint_motor = world.CreateJoint(rjd);
209
+ joint_motor.SetUserData(new CustomMotorUserData("shoulder", CHIMP_SPEED_HIP, false));
210
+ this.motors.push(joint_motor);
211
+
212
+ // Lower arm
213
+ let lower_bd = new b2.BodyDef();
214
+ lower_bd.type = b2.Body.b2_dynamicBody;
215
+ lower_bd.position.Set(init_x, init_y + this.LIMB_H * 3/2 + this.ARM_UP);
216
+ let lower = world.CreateBody(lower_bd);
217
+ lower.CreateFixture(LOWER_LIMB_FD);
218
+ lower.color1 = "#9C4F82"; // [0.61, 0.31, 0.51]
219
+ lower.color2 = "#69364F"; // [0.41, 0.21, 0.31]
220
+ lower.SetUserData(new CustomBodyUserData(false, false, "lower_arm"));
221
+ this.body_parts.push(lower);
222
+
223
+ // Revolute joint between upper arm and lower arm
224
+ rjd = new b2.RevoluteJointDef();
225
+ rjd.Initialize(upper, lower, new b2.Vec2(init_x, init_y + this.LIMB_H + this.ARM_UP));
226
+ rjd.enableMotor = true;
227
+ rjd.enableLimit = true;
228
+ rjd.maxMotorTorque = this.MOTORS_TORQUE;
229
+ rjd.motorSpeed = 1;
230
+ rjd.lowerAngle = 0;
231
+ rjd.upperAngle = 0.75 * Math.PI;
232
+ joint_motor = world.CreateJoint(rjd);
233
+ joint_motor.SetUserData(new CustomMotorUserData("elbow",
234
+ CHIMP_SPEED_HIP,
235
+ false));
236
+ this.motors.push(joint_motor);
237
+
238
+ // Hand
239
+ let prev_part = lower;
240
+ let initial_y = init_y + this.LIMB_H * 2 + this.ARM_UP;
241
+ let angle_boundaries = [[-0.5, 0.5]];
242
+ let nb_hand_parts = 1;
243
+ for(let u = 0; u < nb_hand_parts; u++){
244
+ let hand_part_bd = new b2.BodyDef();
245
+ hand_part_bd.type = b2.Body.b2_dynamicBody;
246
+ hand_part_bd.position.Set(init_x, initial_y + this.HAND_PART_H / 2 + this.HAND_PART_H * u);
247
+ let hand_part = world.CreateBody(hand_part_bd);
248
+ hand_part.CreateFixture(HAND_PART_FD);
249
+ hand_part.color1 = "#9C4F82"; // [0.61, 0.31, 0.51]
250
+ hand_part.color2 = "#69364F"; // [0.41, 0.21, 0.31]
251
+ hand_part.SetUserData(new CustomBodyUserData(true, false, "hand"));
252
+ this.body_parts.push(hand_part);
253
+
254
+ // Revolute joint between prev_part and hand_part
255
+ rjd = new b2.RevoluteJointDef();
256
+ rjd.Initialize(prev_part, hand_part, new b2.Vec2(init_x, initial_y + this.HAND_PART_H * u));
257
+ rjd.enableMotor = true;
258
+ rjd.enableLimit = true;
259
+ rjd.maxMotorTorque = this.MOTORS_TORQUE;
260
+ rjd.motorSpeed = 1;
261
+ rjd.lowerAngle = angle_boundaries[u][0] * Math.PI;
262
+ rjd.upperAngle = angle_boundaries[u][1] * Math.PI;
263
+ joint_motor = world.CreateJoint(rjd);
264
+ joint_motor.SetUserData(new CustomMotorUserData("hand",
265
+ CHIMP_SPEED_HAND,
266
+ true,
267
+ 0,
268
+ hand_part));
269
+ this.motors.push(joint_motor);
270
+
271
+ prev_part = hand_part;
272
+ }
273
+
274
+ // Sensor
275
+ let hand_sensor_position = new b2.Vec2(init_x, initial_y + this.HAND_PART_H * nb_hand_parts);
276
+ let hand_sensor_part_bd = new b2.BodyDef();
277
+ hand_sensor_part_bd.type = b2.Body.b2_dynamicBody;
278
+ hand_sensor_part_bd.position.Assign(hand_sensor_position);
279
+ let hand_sensor_part = world.CreateBody(hand_sensor_part_bd);
280
+ hand_sensor_part.CreateFixture(this.SENSOR_FD);
281
+ hand_sensor_part.color1 = "#FF0000"; // [1, 0, 0]
282
+ hand_sensor_part.color2 = "#FF0000"; // [1, 0, 0]
283
+ hand_sensor_part.SetUserData(new CustomBodySensorUserData(true, false, "hand_sensor"));
284
+ this.sensors.push(hand_sensor_part);
285
+
286
+ let wjd = new b2.WeldJointDef();
287
+ wjd.Initialize(prev_part, hand_sensor_part, hand_sensor_position);
288
+ world.CreateJoint(wjd);
289
+ }
290
+
291
+
292
+ }
293
+ }
js/bodies/swimmers/fish_body.js ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Head
2
+ HULL_POLYGON = [
3
+ [-20, +12], [+6, +12],
4
+ [+15, +4], [+15, -4],
5
+ [+6, -12], [-20, -12]
6
+ ];
7
+
8
+ BODY_P1 = [
9
+ [-8, +9], [+8, +12],
10
+ [+8, -12], [-8, -9]
11
+ ];
12
+
13
+ BODY_P2 = [
14
+ [-8, +4], [+8, +9],
15
+ [+8, -9], [-8, -4]
16
+ ];
17
+
18
+ // Tail
19
+ BODY_P3 = [
20
+ [-4, +2], [+4, +4],
21
+ [+4, -4], [-4, -2]
22
+ ];
23
+
24
+ FIN = [
25
+ [-1, -10], [-1, +10],
26
+ [+1, +10], [+1, -10]
27
+ ];
28
+
29
+ HULL_BOTTOM_WIDTH = 35;
30
+ const SPEED = 6;
31
+
32
+ /**
33
+ * @classdesc Fish morphology.
34
+ */
35
+ class FishBody extends SwimmerAbstractBody {
36
+ /**
37
+ * @constructor
38
+ * @param scale {number} - Scale of the environment
39
+ * @param motors_torque {number}
40
+ * @param density {number} - Density of the agent's body.
41
+ * @param nb_steps_outside_water {number}
42
+ */
43
+ constructor(scale, motors_torque=80, density, nb_steps_outside_water=600) {
44
+ super(scale, motors_torque, density, nb_steps_outside_water);
45
+ this.TORQUE_PENALTY = 0.00035;
46
+
47
+ this.AGENT_WIDTH = HULL_BOTTOM_WIDTH / this.SCALE;
48
+ this.AGENT_HEIGHT = 18 / this.SCALE;
49
+ this.AGENT_CENTER_HEIGHT = 9 / this.SCALE;
50
+
51
+ this.remove_reward_on_head_angle = true;
52
+
53
+ this.fins = [];
54
+ this.tail = null;
55
+ }
56
+
57
+ draw(world, init_x, init_y){
58
+
59
+ let vertices;
60
+ let rjd;
61
+ let joint_motor;
62
+
63
+ // HULL
64
+ let hull_fd = new b2.FixtureDef();
65
+ hull_fd.shape = new b2.PolygonShape();
66
+ vertices = [];
67
+ for(let vertex of HULL_POLYGON){
68
+ vertices.push(new b2.Vec2(vertex[0] / this.SCALE, vertex[1] / this.SCALE));
69
+ }
70
+ hull_fd.shape.Set(vertices, HULL_POLYGON.length);
71
+ hull_fd.density = this.DENSITY;
72
+ hull_fd.friction = 0.1;
73
+ hull_fd.filter.categoryBits = 0x20;
74
+ hull_fd.filter.maskBits = 0x000F; // 0.99 bouncy
75
+
76
+ let hull_bd = new b2.BodyDef();
77
+ hull_bd.type = b2.Body.b2_dynamicBody;
78
+ hull_bd.position.Set(init_x, init_y);
79
+ let hull = world.CreateBody(hull_bd);
80
+ hull.CreateFixture(hull_fd);
81
+ hull.color1 = "#806682"; // [0.5, 0.4, 0.9]
82
+ hull.color2 = "#4D4D80";
83
+ hull.SetUserData(new CustomBodyUserData(true, false, "head"));
84
+ this.body_parts.push(hull);
85
+ this.reference_head_object = hull;
86
+
87
+ // BODY_P1
88
+ let body_p1_x = init_x - 35 / 2 / this.SCALE - 16 / 2 / this.SCALE;
89
+ let body_p1_fd = new b2.FixtureDef();
90
+ body_p1_fd.shape = new b2.PolygonShape();
91
+ vertices = [];
92
+ for(let vertex of BODY_P1){
93
+ vertices.push(new b2.Vec2(vertex[0] / this.SCALE, vertex[1] / this.SCALE));
94
+ }
95
+ body_p1_fd.shape.Set(vertices, BODY_P1.length);
96
+ body_p1_fd.density = this.DENSITY;
97
+ body_p1_fd.restitution = 0.0;
98
+ body_p1_fd.filter.categoryBits = 0x20;
99
+ body_p1_fd.filter.maskBits = 0x000F; // 0.99 bouncy
100
+
101
+ let body_p1_bd = new b2.BodyDef();
102
+ body_p1_bd.type = b2.Body.b2_dynamicBody;
103
+ body_p1_bd.position.Set(body_p1_x, init_y);
104
+ let body_p1 = world.CreateBody(body_p1_bd);
105
+ body_p1.CreateFixture(body_p1_fd);
106
+ body_p1.color1 = "#806682"; // [0.5, 0.4, 0.9]
107
+ body_p1.color2 = "#4D4D80";
108
+ body_p1.SetUserData(new CustomBodyUserData(true, false, "body"));
109
+ this.body_parts.push(body_p1);
110
+
111
+ // Revolute joint between HULL and BODY_P1
112
+ rjd = new b2.RevoluteJointDef();
113
+ rjd.Initialize(hull, body_p1, new b2.Vec2(init_x - 35 / 2 / this.SCALE, init_y));
114
+ rjd.enableMotor = true;
115
+ rjd.enableLimit = true;
116
+ rjd.maxMotorTorque = this.MOTORS_TORQUE;
117
+ rjd.motorSpeed = 1;
118
+ rjd.lowerAngle = -0.1 * Math.PI;
119
+ rjd.upperAngle = 0.2 * Math.PI;
120
+ joint_motor = world.CreateJoint(rjd);
121
+ joint_motor.SetUserData(new CustomMotorUserData("neck", SPEED, true, 0.0, body_p1));
122
+ this.motors.push(joint_motor);
123
+
124
+ // BODY_P2
125
+ let body_p2_x = body_p1_x - 16 / 2 / this.SCALE - 16 / 2 / this.SCALE;
126
+ let body_p2_fd = new b2.FixtureDef();
127
+ body_p2_fd.shape = new b2.PolygonShape();
128
+ vertices = [];
129
+ for(let vertex of BODY_P2){
130
+ vertices.push(new b2.Vec2(vertex[0] / this.SCALE, vertex[1] / this.SCALE));
131
+ }
132
+ body_p2_fd.shape.Set(vertices, BODY_P2.length);
133
+ body_p2_fd.density = this.DENSITY;
134
+ body_p2_fd.restitution = 0.0;
135
+ body_p2_fd.filter.categoryBits = 0x20;
136
+ body_p2_fd.filter.maskBits = 0x000F;
137
+
138
+ let body_p2_bd = new b2.BodyDef();
139
+ body_p2_bd.type = b2.Body.b2_dynamicBody;
140
+ body_p2_bd.position.Set(body_p2_x, init_y);
141
+ let body_p2 = world.CreateBody(body_p2_bd);
142
+ body_p2.CreateFixture(body_p2_fd);
143
+ body_p2.color1 = "#806682"; // [0.5, 0.4, 0.9]
144
+ body_p2.color2 = "#4D4D80";
145
+ body_p2.SetUserData(new CustomBodyUserData(true, false, "body"));
146
+ this.body_parts.push(body_p2);
147
+
148
+ // Revolute joint between BODY_P1 and BODY_P2
149
+ rjd = new b2.RevoluteJointDef();
150
+ rjd.Initialize(body_p1, body_p2, new b2.Vec2(body_p1_x - 16 / 2 / this.SCALE, init_y));
151
+ rjd.enableMotor = true;
152
+ rjd.enableLimit = true;
153
+ rjd.maxMotorTorque = this.MOTORS_TORQUE;
154
+ rjd.motorSpeed = 1;
155
+ rjd.lowerAngle = -0.15 * Math.PI;
156
+ rjd.upperAngle = 0.15 * Math.PI;
157
+ joint_motor = world.CreateJoint(rjd);
158
+ joint_motor.SetUserData(new CustomMotorUserData("hip", SPEED, true, 0.0, body_p2));
159
+ this.motors.push(joint_motor);
160
+
161
+ // BODY_P3 - TAIL
162
+ let body_p3_x = body_p2_x - 16 / 2 / this.SCALE - 8 / 2 / this.SCALE;
163
+ let body_p3_fd = new b2.FixtureDef();
164
+ body_p3_fd.shape = new b2.PolygonShape();
165
+ vertices = [];
166
+ for(let vertex of BODY_P3){
167
+ vertices.push(new b2.Vec2(vertex[0] / this.SCALE, vertex[1] / this.SCALE));
168
+ }
169
+ body_p3_fd.shape.Set(vertices, BODY_P3.length);
170
+ body_p3_fd.density = this.DENSITY;
171
+ body_p3_fd.restitution = 0.0;
172
+ body_p3_fd.filter.categoryBits = 0x20;
173
+ body_p3_fd.filter.maskBits = 0x000F;
174
+
175
+ let body_p3_bd = new b2.BodyDef();
176
+ body_p3_bd.type = b2.Body.b2_dynamicBody;
177
+ body_p3_bd.position.Set(body_p3_x, init_y);
178
+ let body_p3 = world.CreateBody(body_p3_bd);
179
+ body_p3.CreateFixture(body_p3_fd);
180
+ body_p3.color1 = "#806682"; // [0.5, 0.4, 0.9]
181
+ body_p3.color2 = "#4D4D80";
182
+ body_p3.SetUserData(new CustomBodyUserData(true, false, "body"));
183
+ this.body_parts.push(body_p3);
184
+ this.tail = body_p3;
185
+
186
+ // Revolute joint between BODY_P2 and BODY_P3
187
+ rjd = new b2.RevoluteJointDef();
188
+ rjd.Initialize(body_p2, body_p3, new b2.Vec2(body_p2_x - 16 / 2 / this.SCALE, init_y));
189
+ rjd.enableMotor = true;
190
+ rjd.enableLimit = true;
191
+ rjd.maxMotorTorque = this.MOTORS_TORQUE;
192
+ rjd.motorSpeed = 1;
193
+ rjd.lowerAngle = -0.3 * Math.PI;
194
+ rjd.upperAngle = 0.3 * Math.PI;
195
+ joint_motor = world.CreateJoint(rjd);
196
+ joint_motor.SetUserData(new CustomMotorUserData("knee", SPEED, true, 0.0, body_p3));
197
+ this.motors.push(joint_motor);
198
+
199
+ // FINS
200
+ let fin_fd = new b2.FixtureDef();
201
+ fin_fd.shape = new b2.PolygonShape();
202
+ vertices = [];
203
+ for(let vertex of FIN){
204
+ vertices.push(new b2.Vec2(vertex[0] / this.SCALE, vertex[1] / this.SCALE));
205
+ }
206
+ fin_fd.shape.Set(vertices, FIN.length);
207
+ fin_fd.density = this.DENSITY;
208
+ fin_fd.restitution = 0.0;
209
+ fin_fd.filter.categoryBits = 0x20;
210
+ fin_fd.filter.maskBits = 0x000F;
211
+
212
+ let fin_positions = [
213
+ [init_x, init_y - 22 / 2 / this.SCALE + 0.2],
214
+ ];
215
+ let fin_angle = -0.2 * Math.PI;
216
+ let middle_fin_x_distance = Math.sin(fin_angle) * 20 / 2 / this.SCALE;
217
+ let middle_fin_y_distance = Math.cos(fin_angle) * 20 / 2 / this.SCALE;
218
+
219
+ for(let fin_pos of fin_positions){
220
+ let current_fin_x = fin_pos[0] + middle_fin_x_distance;
221
+ let current_fin_y = fin_pos[1] - middle_fin_y_distance;
222
+
223
+ let fin_bd = new b2.BodyDef();
224
+ fin_bd.type = b2.Body.b2_dynamicBody;
225
+ fin_bd.position.Set(current_fin_x, current_fin_y);
226
+ let fin = world.CreateBody(fin_bd);
227
+ fin.CreateFixture(fin_fd);
228
+ fin.color1 = "#806682"; // [0.5, 0.4, 0.9]
229
+ fin.color2 = "#4D4D80";
230
+ fin.SetUserData(new CustomBodyUserData(true, false, "fin"));
231
+ this.body_parts.push(fin);
232
+ this.fins.push(fin);
233
+
234
+ // Revolute joint between HULL and FIN
235
+ rjd = new b2.RevoluteJointDef();
236
+ rjd.Initialize(hull, fin, new b2.Vec2(fin_pos[0], fin_pos[1]));
237
+ rjd.enableMotor = true;
238
+ rjd.enableLimit = true;
239
+ rjd.maxMotorTorque = this.MOTORS_TORQUE;
240
+ rjd.motorSpeed = 1;
241
+ rjd.lowerAngle = -0.3 * Math.PI;
242
+ rjd.upperAngle = 0.2 * Math.PI;
243
+ joint_motor = world.CreateJoint(rjd);
244
+ joint_motor.SetUserData(new CustomMotorUserData("shoulder", SPEED, true, 0.0, fin));
245
+ this.motors.push(joint_motor);
246
+ }
247
+ }
248
+ }
js/bodies/swimmers/swimmer_abstract_body.js ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @classdesc Abstract class for swimmer morphologies.
3
+ */
4
+ class SwimmerAbstractBody extends AbstractBody{
5
+ /**
6
+ * @constructor
7
+ * @param scale {number} - Scale of the environment
8
+ * @param motors_torque {number}
9
+ * @param density {number} - Density of the agent's body.
10
+ * @param nb_steps_outside_water {number}
11
+ */
12
+ constructor(scale, motors_torque, density, nb_steps_outside_water) {
13
+ super(scale, motors_torque);
14
+ this.body_type = BodyTypesEnum.SWIMMER;
15
+ this.nb_steps_can_survive_outside_water = nb_steps_outside_water;
16
+
17
+ // set the embodiment's density to the same value as water so that it will be in a zero-gravity setup
18
+ this.DENSITY = density - 0.01; // Make it a little lighter such that it slowly goes up when no action is done
19
+ }
20
+
21
+ destroy(world) {
22
+ super.destroy(world);
23
+ this.fins = [];
24
+ this.tail = null;
25
+ }
26
+ }
js/bodies/walkers/classic_bipedal_body.js ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ HULL_POLYGONS = [
2
+ [[-30, +9], [+6, +9], [+34, +1], [+34, -8], [-30, -8]]
3
+ ];
4
+ HULL_BOTTOM_WIDTH = 64;
5
+ const SPEED_HIP = 4;
6
+ const SPEED_KNEE = 6;
7
+
8
+ /**
9
+ * @classdesc Bipedal Walker morphology.
10
+ */
11
+ class ClassicBipedalBody extends WalkerAbstractBody {
12
+ /**
13
+ * @constructor
14
+ * @param scale {number} - Scale of the environment
15
+ * @param motors_torque {number}
16
+ * @param nb_steps_under_water {number}
17
+ * @param reset_on_hull_critical_contact {boolean}
18
+ */
19
+ constructor(scale, motors_torque=80, nb_steps_under_water=600, reset_on_hull_critical_contact=false) {
20
+ super(scale, motors_torque, nb_steps_under_water);
21
+
22
+ this.LEG_DOWN = 3 / this.SCALE; // 0 = center of hull
23
+ this.LEG_W = 8 / this.SCALE;
24
+ this.LEG_H = 34 / this.SCALE;
25
+ this.TORQUE_PENALTY = 0.00035;
26
+ this.reset_on_hull_critical_contact = reset_on_hull_critical_contact;
27
+
28
+ this.AGENT_WIDTH = HULL_BOTTOM_WIDTH / this.SCALE;
29
+ this.AGENT_HEIGHT = 17 / this.SCALE + this.LEG_H * 2 - this.LEG_DOWN;
30
+ this.AGENT_CENTER_HEIGHT = this.LEG_H * 2 + this.LEG_DOWN;
31
+
32
+ this.old_morphology = false;
33
+ }
34
+
35
+ draw(world, init_x, init_y, force_to_center){
36
+ let HULL_FIXTURES = [];
37
+ let fd_polygon;
38
+ let vertices;
39
+ let y_offset = 0;//10/this.SCALE;
40
+
41
+ for(let polygon of HULL_POLYGONS){
42
+ fd_polygon = new b2.FixtureDef();
43
+ fd_polygon.shape = new b2.PolygonShape();
44
+ vertices = [];
45
+ for(let vertex of polygon){
46
+ vertices.push(new b2.Vec2(vertex[0] / this.SCALE, vertex[1] / this.SCALE));
47
+ }
48
+ fd_polygon.shape.Set(vertices, polygon.length);
49
+ fd_polygon.density = 5.0;
50
+ fd_polygon.friction = 0.1;
51
+ fd_polygon.filter.categoryBits = 0x20;
52
+ fd_polygon.filter.maskBits = 0x000F; // 0.99 bouncy
53
+ HULL_FIXTURES.push(fd_polygon);
54
+ }
55
+
56
+ let LEG_FD = new b2.FixtureDef();
57
+ LEG_FD.shape = new b2.PolygonShape();
58
+ LEG_FD.shape.SetAsBox(this.LEG_W / 2, this.LEG_H / 2);
59
+ LEG_FD.density = 1.0;
60
+ LEG_FD.restitution = 0.0;
61
+ LEG_FD.filter.categoryBits = 0x20;
62
+ LEG_FD.filter.maskBits = 0x000F;
63
+
64
+ let LOWER_FD = new b2.FixtureDef();
65
+ LOWER_FD.shape = new b2.PolygonShape();
66
+ LOWER_FD.shape.SetAsBox(0.8 * this.LEG_W / 2, this.LEG_H / 2);
67
+ LOWER_FD.density = 1.0;
68
+ LOWER_FD.restitution = 0.0;
69
+ LOWER_FD.filter.categoryBits = 0x20;
70
+ LOWER_FD.filter.maskBits = 0x000F;
71
+
72
+ let hull_bd = new b2.BodyDef();
73
+ hull_bd.type = b2.Body.b2_dynamicBody;
74
+ hull_bd.position.Set(init_x, init_y + y_offset);
75
+ let hull = world.CreateBody(hull_bd);
76
+ for(let fd of HULL_FIXTURES){
77
+ hull.CreateFixture(fd);
78
+ }
79
+ hull.color1 = "#806682"; // [0.5, 0.4, 0.9]
80
+ hull.color2 = "#4D4D80"; // [0.3, 0.3, 0.5]
81
+ hull.ApplyForceToCenter(new b2.Vec2(force_to_center, 0), true);
82
+ hull.SetUserData(new CustomBodyUserData(true, this.reset_on_hull_critical_contact, "hull"));
83
+ this.body_parts.push(hull);
84
+ this.reference_head_object = hull;
85
+
86
+ // Leg and lower bodies and joints
87
+ for(let i of [-1, +1]){
88
+
89
+ // Leg body
90
+ let leg_bd = new b2.BodyDef();
91
+ leg_bd.type = b2.Body.b2_dynamicBody;
92
+ leg_bd.position.Set(init_x, init_y - this.LEG_H / 2 - this.LEG_DOWN + y_offset);
93
+ //leg_bd.angle = i * 0.05; // 2°
94
+ let leg = world.CreateBody(leg_bd);
95
+ leg.CreateFixture(LEG_FD);
96
+ leg.color1 = i == -1 ? "#9C4F82" : "#964A7D"; // [0.61, 0.31, 0.51] : [0.59, 0.29, 0.49]
97
+ leg.color2 = i == -1 ? "#69364F" : "#63304A"; // [0.41, 0.21, 0.31] : [0.39, 0.19, 0.29]
98
+ leg.SetUserData(new CustomBodyUserData(false, false,"leg"));
99
+ this.body_parts.push(leg);
100
+
101
+ // Leg joint motor
102
+ let leg_rjd = new b2.RevoluteJointDef();
103
+ leg_rjd.Initialize(hull, leg, new b2.Vec2(init_x, init_y - this.LEG_DOWN + y_offset));
104
+ leg_rjd.enableMotor = true;
105
+ leg_rjd.enableLimit = true;
106
+ leg_rjd.maxMotorTorque = this.MOTORS_TORQUE;
107
+ leg_rjd.motorSpeed = i;
108
+ leg_rjd.lowerAngle = - 0.8;
109
+ leg_rjd.upperAngle = 1.1;
110
+ let joint_motor = world.CreateJoint(leg_rjd);
111
+ joint_motor.SetUserData(new CustomMotorUserData("hip", SPEED_HIP, false));
112
+ this.motors.push(joint_motor);
113
+
114
+ // lower body
115
+ let lower_bd = new b2.BodyDef();
116
+ lower_bd.type = b2.Body.b2_dynamicBody;
117
+ lower_bd.position.Set(init_x, init_y - this.LEG_H * 3 / 2 - this.LEG_DOWN + y_offset);
118
+ //lower_bd.angle = i * 0.05; // 2°
119
+ let lower = world.CreateBody(lower_bd);
120
+ lower.CreateFixture(LOWER_FD);
121
+ lower.color1 = i == -1 ? "#9C4F82" : "#964A7D"; // [0.61, 0.31, 0.51] : [0.59, 0.29, 0.49]
122
+ lower.color2 = i == -1 ? "#69364F" : "#63304A"; // [0.41, 0.21, 0.31] : [0.39, 0.19, 0.29]
123
+ lower.SetUserData(new CustomBodyUserData(true, false,"lower"));
124
+ this.body_parts.push(lower);
125
+
126
+ // lower joint motor
127
+ let lower_rjd = new b2.RevoluteJointDef();
128
+ lower_rjd.Initialize(leg, lower, new b2.Vec2(init_x, init_y - this.LEG_DOWN - this.LEG_H + y_offset));
129
+ lower_rjd.enableMotor = true;
130
+ lower_rjd.enableLimit = true;
131
+ lower_rjd.maxMotorTorque = this.MOTORS_TORQUE;
132
+ lower_rjd.motorSpeed = 1;
133
+ lower_rjd.lowerAngle = - 1.6;
134
+ lower_rjd.upperAngle = -0.1;
135
+ joint_motor = world.CreateJoint(lower_rjd);
136
+ joint_motor.SetUserData(new CustomMotorUserData("knee", SPEED_KNEE, true, 1.0, lower));
137
+ this.motors.push(joint_motor);
138
+ }
139
+ }
140
+ }
js/bodies/walkers/old_classic_bipedal_body.js ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ HULL_POLYGONS = [
2
+ [[-30, +9], [+6, +9], [+34, +1], [+34, -8], [-30, -8]]
3
+ ];
4
+ HULL_BOTTOM_WIDTH = 64;
5
+ SPEED_HIP = 4;
6
+ SPEED_KNEE = 6;
7
+
8
+ class OldClassicBipedalBody extends WalkerAbstractBody{
9
+ constructor(scale, nb_steps_under_water=600, reset_on_hull_critical_contact=true){
10
+ super(scale, 80, nb_steps_under_water);
11
+
12
+ this.LEG_DOWN = -8 / this.SCALE; // 0 = center of hull
13
+ this.LEG_W = 8 / this.SCALE;
14
+ this.LEG_H = 34 / this.SCALE;
15
+ this.TORQUE_PENALTY = 0.00035;
16
+ this.reset_on_hull_critical_contact = reset_on_hull_critical_contact;
17
+
18
+ // Approximative...
19
+ this.AGENT_WIDTH = HULL_BOTTOM_WIDTH / this.SCALE;
20
+ this.AGENT_HEIGHT = 17 / this.SCALE + this.LEG_H * 2 - this.LEG_DOWN + 0.5;
21
+ this.AGENT_CENTER_HEIGHT = this.LEG_H * 2 + this.LEG_DOWN + 0.5;
22
+
23
+ this.old_morphology = true;
24
+
25
+ this.body_parts = [];
26
+ this.nb_motors = 4;
27
+ this.motors = [];
28
+ this.state_size = this.nb_motors * 2 + 2;
29
+ }
30
+
31
+ draw(world, init_x, init_y, force_to_center){
32
+ let HULL_FIXTURES = [];
33
+ let fd_polygon;
34
+ let vertices;
35
+ let y_offset = 0//10/this.SCALE;
36
+
37
+ for(let polygon of HULL_POLYGONS){
38
+ fd_polygon = new b2.FixtureDef();
39
+ fd_polygon.shape = new b2.PolygonShape();
40
+ vertices = [];
41
+ for(let vertex of polygon){
42
+ vertices.push(new b2.Vec2(vertex[0] / this.SCALE, vertex[1] / this.SCALE));
43
+ }
44
+ fd_polygon.shape.Set(vertices, polygon.length);
45
+ fd_polygon.density = 5.0;
46
+ fd_polygon.friction = 0.1;
47
+ fd_polygon.filter.categoryBits = 0x20;
48
+ fd_polygon.filter.maskBits = 0x000F; // 0.99 bouncy
49
+ HULL_FIXTURES.push(fd_polygon);
50
+ }
51
+
52
+ let LEG_FD = new b2.FixtureDef();
53
+ LEG_FD.shape = new b2.PolygonShape();
54
+ LEG_FD.shape.SetAsBox(this.LEG_W / 2, this.LEG_H / 2);
55
+ LEG_FD.density = 1.0;
56
+ LEG_FD.restitution = 0.0;
57
+ LEG_FD.filter.categoryBits = 0x20;
58
+ LEG_FD.filter.maskBits = 0x000F;
59
+
60
+ let LOWER_FD = new b2.FixtureDef();
61
+ LOWER_FD.shape = new b2.PolygonShape();
62
+ LOWER_FD.shape.SetAsBox(0.8 * this.LEG_W / 2, this.LEG_H / 2);
63
+ LOWER_FD.density = 1.0;
64
+ LOWER_FD.restitution = 0.0;
65
+ LOWER_FD.filter.categoryBits = 0x20;
66
+ LOWER_FD.filter.maskBits = 0x000F;
67
+
68
+ let hull_bd = new b2.BodyDef();
69
+ hull_bd.type = b2.Body.b2_dynamicBody;
70
+ hull_bd.position.Set(init_x, init_y + y_offset);
71
+ let hull = world.CreateBody(hull_bd);
72
+ for(let fd of HULL_FIXTURES){
73
+ hull.CreateFixture(fd);
74
+ }
75
+ hull.color1 = "#806682"; // [0.5, 0.4, 0.9]
76
+ hull.color2 = "#4D4D80"; // [0.3, 0.3, 0.5]
77
+ //hull.ApplyForceToCenter(new b2.Vec2(force_to_center, 0), true);
78
+ hull.SetUserData(new CustomBodyUserData(true, this.reset_on_hull_critical_contact, "hull"));
79
+ this.body_parts.push(hull);
80
+ this.reference_head_object = hull;
81
+
82
+ // Leg and lower bodies and joints
83
+ for(let i of [-1, +1]){
84
+
85
+ // Leg body
86
+ let leg_bd = new b2.BodyDef();
87
+ leg_bd.type = b2.Body.b2_dynamicBody;
88
+ leg_bd.position.Set(init_x, init_y - this.LEG_H / 2 - this.LEG_DOWN + y_offset);
89
+ leg_bd.angle = i * 0.05; // 2°
90
+ let leg = world.CreateBody(leg_bd);
91
+ leg.CreateFixture(LEG_FD);
92
+ leg.color1 = i == -1 ? "#9C4F82" : "#964A7D"; // [0.61, 0.31, 0.51] : [0.59, 0.29, 0.49]
93
+ leg.color2 = i == -1 ? "#69364F" : "#63304A"; // [0.41, 0.21, 0.31] : [0.39, 0.19, 0.29]
94
+ leg.SetUserData(new CustomBodyUserData(false, false,"leg"));
95
+ this.body_parts.push(leg);
96
+
97
+ // Leg joint motor
98
+ let leg_rjd = new b2.RevoluteJointDef();
99
+ leg_rjd.Initialize(hull, leg, new b2.Vec2(init_x, init_y - this.LEG_DOWN + y_offset));
100
+ leg_rjd.localAnchorA = new b2.Vec2(0, this.LEG_DOWN);
101
+ leg_rjd.localAnchorB = new b2.Vec2(0, this.LEG_H / 2);
102
+ leg_rjd.enableMotor = true;
103
+ leg_rjd.enableLimit = true;
104
+ leg_rjd.maxMotorTorque = this.MOTORS_TORQUE;
105
+ leg_rjd.motorSpeed = i;
106
+ leg_rjd.lowerAngle = - 0.8;
107
+ leg_rjd.upperAngle = 1.1;
108
+ let joint_motor = world.CreateJoint(leg_rjd);
109
+ joint_motor.SetUserData(new CustomMotorUserData("hip", SPEED_HIP, false));
110
+ this.motors.push(joint_motor);
111
+
112
+ // lower body
113
+ let lower_bd = new b2.BodyDef();
114
+ lower_bd.type = b2.Body.b2_dynamicBody;
115
+ lower_bd.position.Set(init_x, init_y - this.LEG_H * 3 / 2 - this.LEG_DOWN + y_offset);
116
+ lower_bd.angle = i * 0.05; // 2°
117
+ let lower = world.CreateBody(lower_bd);
118
+ lower.CreateFixture(LOWER_FD);
119
+ lower.color1 = i == -1 ? "#9C4F82" : "#964A7D"; // [0.61, 0.31, 0.51] : [0.59, 0.29, 0.49]
120
+ lower.color2 = i == -1 ? "#69364F" : "#63304A"; // [0.41, 0.21, 0.31] : [0.39, 0.19, 0.29]
121
+ lower.SetUserData(new CustomBodyUserData(true, false,"lower"));
122
+ this.body_parts.push(lower);
123
+
124
+ // lower joint motor
125
+ let lower_rjd = new b2.RevoluteJointDef();
126
+ lower_rjd.Initialize(leg, lower, new b2.Vec2(init_x, init_y - this.LEG_DOWN - this.LEG_H + y_offset));
127
+ lower_rjd.localAnchorA = new b2.Vec2(0, - this.LEG_H / 2);
128
+ lower_rjd.localAnchorB = new b2.Vec2(0, this.LEG_H / 2);
129
+ lower_rjd.enableMotor = true;
130
+ lower_rjd.enableLimit = true;
131
+ lower_rjd.maxMotorTorque = this.MOTORS_TORQUE;
132
+ lower_rjd.motorSpeed = 1;
133
+ lower_rjd.lowerAngle = - 1.6;
134
+ lower_rjd.upperAngle = -0.1;
135
+ joint_motor = world.CreateJoint(lower_rjd);
136
+ joint_motor.SetUserData(new CustomMotorUserData("knee", SPEED_KNEE, true, 1.0, lower));
137
+ this.motors.push(joint_motor);
138
+ }
139
+ }
140
+ }
js/bodies/walkers/spider_body.js ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const MAIN_BODY_POLYGONS = [
2
+ [[-10, +10], [+10, +10], [+10, -10], [-10, -10]]
3
+ ]
4
+ const MAIN_BODY_BOTTOM_WIDTH = 20
5
+ const SPIDER_SPEED_HIP = 4
6
+ const SPIDER_SPEED_KNEE = 6
7
+
8
+ /**
9
+ * @classdesc Spider morphology.
10
+ */
11
+ class SpiderBody extends WalkerAbstractBody {
12
+
13
+ constructor(scale, motors_torque=100, nb_pairs_of_legs=2,
14
+ nb_steps_under_water=600, reset_on_hull_critical_contact=false){
15
+ super(scale, motors_torque, nb_steps_under_water);
16
+
17
+ this.LEG_DOWN = 4 / this.SCALE;
18
+ this.LEG_W = 6 / this.SCALE;
19
+ this.LEG_H = 20 / this.SCALE;
20
+ this.reset_on_critical_contact = reset_on_hull_critical_contact;
21
+
22
+ this.nb_pairs_of_legs = nb_pairs_of_legs;
23
+
24
+ this.TORQUE_PENALTY = 0.00035 / this.nb_pairs_of_legs;
25
+
26
+ // not exacts but works
27
+ this.AGENT_WIDTH = MAIN_BODY_BOTTOM_WIDTH / this.SCALE + this.LEG_H * 4;
28
+ this.AGENT_HEIGHT = 20 / this.SCALE + this.LEG_H * 2;
29
+ this.AGENT_CENTER_HEIGHT = this.LEG_H + this.LEG_DOWN;
30
+ }
31
+
32
+ draw(world, init_x, init_y, force_to_center){
33
+
34
+ let fd_polygon;
35
+ let vertices;
36
+
37
+ /* Creates the different fixtures */
38
+
39
+ let MAIN_BODY_FIXTURES = [];
40
+ for(let polygon of MAIN_BODY_POLYGONS){
41
+ fd_polygon = new b2.FixtureDef();
42
+ fd_polygon.shape = new b2.PolygonShape();
43
+ vertices = [];
44
+ for(let vertex of polygon){
45
+ vertices.push(new b2.Vec2(vertex[0] / this.SCALE, vertex[1] / this.SCALE));
46
+ }
47
+ fd_polygon.shape.Set(vertices, polygon.length);
48
+ fd_polygon.density = 5.0;
49
+ fd_polygon.friction = 0.1;
50
+ fd_polygon.filter.categoryBits = 0x20;
51
+ fd_polygon.filter.maskBits = 0x000F;
52
+ MAIN_BODY_FIXTURES.push(fd_polygon);
53
+ }
54
+
55
+ let LEG_FD = new b2.FixtureDef();
56
+ LEG_FD.shape = new b2.PolygonShape();
57
+ LEG_FD.shape.SetAsBox(this.LEG_W / 2, this.LEG_H / 2);
58
+ LEG_FD.density = 1.0;
59
+ LEG_FD.restitution = 0.0;
60
+ LEG_FD.filter.categoryBits = 0x20;
61
+ LEG_FD.filter.maskBits = 0x000F;
62
+
63
+ let LOWER_FD = new b2.FixtureDef();
64
+ LOWER_FD.shape = new b2.PolygonShape();
65
+ LOWER_FD.shape.SetAsBox(0.8 * this.LEG_W / 2, this.LEG_H / 2);
66
+ LOWER_FD.density = 1.0;
67
+ LOWER_FD.restitution = 0.0;
68
+ LOWER_FD.filter.categoryBits = 0x20;
69
+ LOWER_FD.filter.maskBits = 0x000F;
70
+
71
+ /* Creates the different bodies */
72
+
73
+ // Main body
74
+ let main_body_bd = new b2.BodyDef();
75
+ main_body_bd.type = b2.Body.b2_dynamicBody;
76
+ main_body_bd.position.Set(init_x, init_y);
77
+ let main_body = world.CreateBody(main_body_bd);
78
+ for(let fd of MAIN_BODY_FIXTURES){
79
+ main_body.CreateFixture(fd);
80
+ }
81
+ main_body.color1 = "#803366"; // [0.5, 0.2, 0.4]
82
+ main_body.color2 = "#4D1A33"; // [0.3, 0.1, 0.2]
83
+ main_body.ApplyForceToCenter(new b2.Vec2(force_to_center, 0), true);
84
+ main_body.SetUserData(new CustomBodyUserData(true, this.reset_on_hull_critical_contact, "main_body"));
85
+ this.body_parts.push(main_body);
86
+ this.reference_head_object = main_body;
87
+
88
+ // Legs bodies and joints
89
+ let legs_coef = [];
90
+ for(let i = 0; i < this.nb_pairs_of_legs; i++){
91
+ legs_coef.push(+1, -1);
92
+ }
93
+ for(let i of legs_coef){
94
+
95
+ // First part of the leg
96
+ let upper_leg_angle = 0.25 * Math.PI * i;
97
+ let upper_leg_x_distance = Math.sin(upper_leg_angle) * this.LEG_H / 2;
98
+ let upper_leg_y_distance = Math.cos(upper_leg_angle) * this.LEG_H / 2;
99
+ let upper_leg_x = init_x - i * MAIN_BODY_BOTTOM_WIDTH / this.SCALE / 2 - upper_leg_x_distance;
100
+ let upper_leg_y = init_y + upper_leg_y_distance - this.LEG_DOWN;
101
+
102
+ let upper_leg_bd = new b2.BodyDef();
103
+ upper_leg_bd.type = b2.Body.b2_dynamicBody;
104
+ upper_leg_bd.position.Set(upper_leg_x, upper_leg_y);
105
+ upper_leg_bd.angle = upper_leg_angle;
106
+ let upper_leg = world.CreateBody(upper_leg_bd);
107
+ upper_leg.CreateFixture(LEG_FD);
108
+ upper_leg.color1 = "#B36699"; // [0.7, 0.4, 0.6]
109
+ upper_leg.color2 = "#804D66"; // [0.5, 0.3, 0.4]
110
+ upper_leg.SetUserData(new CustomBodyUserData(false, false,"upper_leg"));
111
+ this.body_parts.push(upper_leg);
112
+
113
+ // Upper leg joint motor
114
+ let upper_leg_rjd = new b2.RevoluteJointDef();
115
+ upper_leg_rjd.Initialize(main_body, upper_leg, new b2.Vec2(init_x - i * MAIN_BODY_BOTTOM_WIDTH / this.SCALE / 2, init_y - this.LEG_DOWN));
116
+ upper_leg_rjd.enableMotor = true;
117
+ upper_leg_rjd.enableLimit = true;
118
+ upper_leg_rjd.maxMotorTorque = this.MOTORS_TORQUE;
119
+ upper_leg_rjd.motorSpeed = 1;
120
+ upper_leg_rjd.lowerAngle = - 0.1 * Math.PI;
121
+ upper_leg_rjd.upperAngle = 0.1 * Math.PI;
122
+ let joint_motor = world.CreateJoint(upper_leg_rjd);
123
+ joint_motor.SetUserData(new CustomMotorUserData("hip", SPIDER_SPEED_HIP, false));
124
+ this.motors.push(joint_motor);
125
+
126
+ // Second part of the leg
127
+ let middle_leg_angle = 0.7 * Math.PI * i;
128
+ let middle_leg_x_distance = Math.sin(middle_leg_angle) * this.LEG_H / 2;
129
+ let middle_leg_y_distance = - Math.cos(middle_leg_angle) * this.LEG_H / 2;
130
+ let middle_leg_x = upper_leg_x - upper_leg_x_distance - middle_leg_x_distance;
131
+ let middle_leg_y = upper_leg_y + upper_leg_y_distance - middle_leg_y_distance;
132
+
133
+ let middle_leg_bd = new b2.BodyDef();
134
+ middle_leg_bd.type = b2.Body.b2_dynamicBody;
135
+ middle_leg_bd.position.Set(middle_leg_x, middle_leg_y);
136
+ middle_leg_bd.angle = middle_leg_angle;
137
+ let middle_leg = world.CreateBody(middle_leg_bd);
138
+ middle_leg.CreateFixture(LEG_FD);
139
+ middle_leg.color1 = "#B36699"; // [0.7, 0.4, 0.6]
140
+ middle_leg.color2 = "#804D66"; // [0.5, 0.3, 0.4]
141
+ middle_leg.SetUserData(new CustomBodyUserData(false, false,"middle_leg"));
142
+ this.body_parts.push(middle_leg);
143
+
144
+ // middle_leg joint motor
145
+ let middle_leg_rjd = new b2.RevoluteJointDef();
146
+ middle_leg_rjd.Initialize(upper_leg, middle_leg, new b2.Vec2(upper_leg_x - upper_leg_x_distance, upper_leg_y + upper_leg_y_distance));
147
+ middle_leg_rjd.enableMotor = true;
148
+ middle_leg_rjd.enableLimit = true;
149
+ middle_leg_rjd.maxMotorTorque = this.MOTORS_TORQUE;
150
+ middle_leg_rjd.motorSpeed = 1;
151
+ middle_leg_rjd.lowerAngle = - 0.15 * Math.PI;
152
+ middle_leg_rjd.upperAngle = 0.15 * Math.PI;
153
+ joint_motor = world.CreateJoint(middle_leg_rjd);
154
+ joint_motor.SetUserData(new CustomMotorUserData("hip", SPIDER_SPEED_HIP,false));
155
+ this.motors.push(joint_motor);
156
+
157
+ // Third part of the leg
158
+ let lower_leg_angle = 0.9 * Math.PI * i;
159
+ let lower_leg_x_distance = Math.sin(lower_leg_angle) * this.LEG_H / 2;
160
+ let lower_leg_y_distance = - Math.cos(lower_leg_angle) * this.LEG_H / 2;
161
+ let lower_leg_x = middle_leg_x - middle_leg_x_distance - lower_leg_x_distance;
162
+ let lower_leg_y = middle_leg_y - middle_leg_y_distance - lower_leg_y_distance;
163
+
164
+ let lower_leg_bd = new b2.BodyDef();
165
+ lower_leg_bd.type = b2.Body.b2_dynamicBody;
166
+ lower_leg_bd.position.Set(lower_leg_x, lower_leg_y);
167
+ lower_leg_bd.angle = lower_leg_angle;
168
+ let lower_leg = world.CreateBody(lower_leg_bd);
169
+ lower_leg.CreateFixture(LOWER_FD);
170
+ lower_leg.color1 = "#B36699"; // [0.7, 0.4, 0.6]
171
+ lower_leg.color2 = "#804D66"; // [0.5, 0.3, 0.4]
172
+ lower_leg.SetUserData(new CustomBodyUserData(true, false,"lower_leg"));
173
+ this.body_parts.push(lower_leg);
174
+
175
+ // lower_leg joint motor
176
+ let lower_leg_rjd = new b2.RevoluteJointDef();
177
+ lower_leg_rjd.Initialize(middle_leg, lower_leg, new b2.Vec2(middle_leg_x - middle_leg_x_distance, middle_leg_y - middle_leg_y_distance));
178
+ lower_leg_rjd.enableMotor = true;
179
+ lower_leg_rjd.enableLimit = true;
180
+ lower_leg_rjd.maxMotorTorque = this.MOTORS_TORQUE;
181
+ lower_leg_rjd.motorSpeed = 1;
182
+ lower_leg_rjd.lowerAngle = - 0.2 * Math.PI;
183
+ lower_leg_rjd.upperAngle = 0.2 * Math.PI;
184
+ joint_motor = world.CreateJoint(lower_leg_rjd);
185
+ joint_motor.SetUserData(new CustomMotorUserData("knee", SPIDER_SPEED_KNEE,true, 0.0, lower_leg));
186
+ this.motors.push(joint_motor);
187
+ }
188
+ }
189
+ };
js/bodies/walkers/walker_abstract_body.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @classdesc Abstract class for walker morphologies.
3
+ */
4
+ class WalkerAbstractBody extends AbstractBody{
5
+ /**
6
+ * @constructor
7
+ * @param scale {number} - Scale of the environment
8
+ * @param motors_torque {number}
9
+ * @param nb_steps_under_water {number}
10
+ */
11
+ constructor(scale, motors_torque, nb_steps_under_water) {
12
+ super(scale, motors_torque);
13
+ this.body_type = BodyTypesEnum.WALKER;
14
+ this.nb_steps_can_survive_under_water = nb_steps_under_water;
15
+ }
16
+ }
js/box2d.js ADDED
The diff for this file is too large to render. See raw diff
 
js/draw_p5js.js ADDED
@@ -0,0 +1,592 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ p5.disableFriendlyErrors = true; // disables FES
2
+
3
+ // Colors to be used for the joints
4
+ let JOINTS_COLORS = {
5
+ "creeper": "#00B400",
6
+ "hip": "#FF7818",
7
+ "knee": "#F4BE18",
8
+ "neck": "#0000FF",
9
+ "shoulder": "#6CC9FF",
10
+ "elbow": "#FF00AA",
11
+ "hand": "#FF8CFF",
12
+ "grip": "#FF0000",
13
+ };
14
+
15
+ // Secondary off-screen canvas
16
+ let drawing_canvas; // Used to draw the terrain shapes
17
+ let trace_canvas; // Used to draw the erase and assets traces following the mouse
18
+ let forbidden_canvas; // Used to draw the forbidden red area on the terrain startpad
19
+ let tmp_canvas;
20
+
21
+ /**
22
+ * Creates the different canvas and sets them up. Called automatically when the programs starts.
23
+ */
24
+ function setup() {
25
+ let canvas_container = document.querySelector('#canvas_container');
26
+ RENDERING_VIEWER_W = canvas_container.offsetWidth;
27
+ window.canvas = createCanvas(RENDERING_VIEWER_W, RENDERING_VIEWER_H);
28
+ INIT_ZOOM = RENDERING_VIEWER_W / ((TERRAIN_LENGTH + INITIAL_TERRAIN_STARTPAD) * 1.05 * TERRAIN_STEP * SCALE);
29
+ THUMBNAIL_ZOOM = RENDERING_VIEWER_W / ((TERRAIN_LENGTH + INITIAL_TERRAIN_STARTPAD) * 0.99 * TERRAIN_STEP * SCALE);
30
+ canvas.parent("canvas_container");
31
+ canvas.style('display', 'block');
32
+ canvas.style('margin-left', 'auto');
33
+ canvas.style('margin-right', 'auto');
34
+
35
+ // Creates the off-screen canvas. Height is bigger than main canvas' so that one can scroll vertically when drawing.
36
+ drawing_canvas = createGraphics(RENDERING_VIEWER_W + SCROLL_X_MAX, RENDERING_VIEWER_H + 2 * SCROLL_Y_MAX);
37
+ trace_canvas = createGraphics(RENDERING_VIEWER_W + SCROLL_X_MAX, RENDERING_VIEWER_H + 2 * SCROLL_Y_MAX);
38
+ forbidden_canvas = createGraphics(RENDERING_VIEWER_W + SCROLL_X_MAX, RENDERING_VIEWER_H + 2 * SCROLL_Y_MAX);
39
+ tmp_canvas = createGraphics(RENDERING_VIEWER_W + SCROLL_X_MAX, RENDERING_VIEWER_H + 2 * SCROLL_Y_MAX);
40
+
41
+ // Prevents automatic calls the draw() function
42
+ noLoop();
43
+ }
44
+
45
+ /**
46
+ * Converts one rgb component to hexadecimal.
47
+ * @param c {number}
48
+ * @return {string}
49
+ */
50
+ function componentToHex(c) {
51
+ let hex = c.toString(16);
52
+ return hex.length == 1 ? "0" + hex : hex;
53
+ }
54
+
55
+ /**
56
+ * Converts the rgb array to hexadecimal string.
57
+ * @param rgb {Array}
58
+ * @return {string}
59
+ */
60
+ function rgbToHex(rgb) {
61
+ return "#" + componentToHex(rgb[0]) + componentToHex(rgb[1]) + componentToHex(rgb[2]);
62
+ }
63
+
64
+ /**
65
+ * Converts hexadecimal string to rgb array
66
+ * @param hex
67
+ * @return {[number, number, number]}
68
+ */
69
+ function hexToRgb(hex) {
70
+ let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
71
+ let rgb = [
72
+ parseInt(result[1], 16),
73
+ parseInt(result[2], 16),
74
+ parseInt(result[3], 16)
75
+ ];
76
+ return result ? rgb : null;
77
+ }
78
+
79
+ /**
80
+ * Color agent's head depending on its 'dying' state.
81
+ * @param agent {Object}
82
+ * @param c1 {Array}
83
+ * @param c2 {Array}
84
+ * @return {Array}
85
+ */
86
+ function color_agent_head(agent, c1, c2){
87
+ let ratio = 0;
88
+ if(agent.agent_body.body_type == BodyTypesEnum.SWIMMER){
89
+ ratio = agent.nb_steps_outside_water / agent.agent_body.nb_steps_can_survive_outside_water;
90
+ }
91
+ else {
92
+ ratio = agent.nb_steps_under_water / agent.agent_body.nb_steps_can_survive_under_water;
93
+ }
94
+
95
+ let color1 = [
96
+ c1[0] + ratio * (1.0 - c1[0]),
97
+ c1[1] + ratio * (0.0 - c1[1]),
98
+ c1[2] + ratio * (0.0 - c1[2])
99
+ ]
100
+ let color2 = c2;
101
+ return [color1, color2];
102
+ }
103
+
104
+ /**
105
+ * Renders all the elements of the environment.
106
+ */
107
+ function draw() {
108
+ if(window.game != null){
109
+ let env = window.game.env;
110
+ push();
111
+
112
+ drawTerrain(env);
113
+
114
+ // Renders the agents if not drawing mode
115
+ if(!window.is_drawing()){
116
+ for(let agent of env.agents){
117
+
118
+ // Draws the agent morphology
119
+ drawAgent(agent, env.scale);
120
+
121
+ // Draws the agent's lidars
122
+ if(window.draw_lidars){
123
+ drawLidars(agent.lidars, env.scale);
124
+ }
125
+
126
+ // Draws the agent's observation
127
+ if(window.draw_observation){
128
+ drawObservation(agent, env.scale);
129
+ }
130
+
131
+ // Draws the agent's rewards
132
+ if(window.draw_reward){
133
+ drawReward(agent, env.scale);
134
+ }
135
+
136
+ // Draws the agent's joints
137
+ if(window.draw_joints){
138
+
139
+ // Agent motors
140
+ let joints = [...agent.agent_body.motors];
141
+
142
+ // Adds neck joint and grip joints for climbers
143
+ if(agent.agent_body.body_type == BodyTypesEnum.CLIMBER){
144
+ joints.push(agent.agent_body.neck_joint);
145
+ let grip_joints = [...agent.agent_body.sensors.map(s => s.GetUserData().has_joint ? s.GetUserData().joint : null)];
146
+ joints = joints.concat(grip_joints);
147
+ }
148
+ drawJoints(joints, env.scale);
149
+ }
150
+
151
+ // Draws the agent's name
152
+ if(window.draw_names){
153
+ drawName(agent, env.scale);
154
+ }
155
+ }
156
+ }
157
+
158
+ // Draws creepers joints
159
+ if(window.draw_joints) {
160
+ drawJoints(env.creepers_joints, env.scale);
161
+ }
162
+
163
+ pop();
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Draws the given sensors.
169
+ * @param sensors {Array}
170
+ * @param scale {number} - Scale of the environment
171
+ */
172
+ function drawSensors(sensors, scale){
173
+ for(let i = 0; i < sensors.length; i++){
174
+ let radius = sensors[i].GetFixtureList().GetShape().m_radius + 0.01;
175
+ let sensor_world_center = sensors[i].GetPosition()//sensors[i].GetWorldCenter();
176
+ noStroke();
177
+ fill(255, 0, 0, 255);
178
+ //fill("#FFFF00");
179
+ circle(sensor_world_center.x, VIEWPORT_H - sensor_world_center.y, radius);
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Draws the given joints.
185
+ * @param joints
186
+ * @param scale {number} - Scale of the environment
187
+ */
188
+ function drawJoints(joints, scale){
189
+ for(let i = 0; i < joints.length; i++){
190
+ if(joints[i] != null){
191
+ let posA = joints[i].m_bodyA.GetWorldPoint(joints[i].m_localAnchorA);
192
+ let posB = joints[i].m_bodyB.GetWorldPoint(joints[i].m_localAnchorB);
193
+ noStroke();
194
+ let joint_type = joints[i].GetUserData().name;
195
+ fill(JOINTS_COLORS[joint_type]);
196
+ let radius = joint_type == "creeper" ? 5 : 7;
197
+ circle(posA.x, VIEWPORT_H - posA.y, radius/scale);
198
+ circle(posB.x, VIEWPORT_H - posB.y, radius/scale);
199
+ }
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Draws the name of the given agent.
205
+ * @param agent {Object}
206
+ * @param scale {number} - Scale of the environment
207
+ */
208
+ function drawName(agent, scale){
209
+ let pos = agent.agent_body.reference_head_object.GetPosition();
210
+ fill(0);
211
+ noStroke()
212
+ textSize(25 / scale);
213
+ textAlign(CENTER);
214
+ let x_pos = pos.x;
215
+ let y_pos;
216
+ if(agent.morphology == "bipedal"){
217
+ y_pos = pos.y + agent.agent_body.AGENT_HEIGHT/3;
218
+ }
219
+ else if(agent.morphology == "spider"){
220
+ y_pos = pos.y + agent.agent_body.AGENT_HEIGHT / 2;
221
+ }
222
+ else if(agent.morphology == "chimpanzee"){
223
+ y_pos = pos.y + agent.agent_body.AGENT_HEIGHT/2;
224
+ }
225
+ else if(agent.morphology == "fish"){
226
+ y_pos = pos.y + agent.agent_body.AGENT_HEIGHT * 2;
227
+ }
228
+ let age = agent.age == "adult" ? "" : " (" + window.lang_dict[window.get_language()]['morphologies'][agent.age] + ")";
229
+ text(agent.name + age, x_pos, RENDERING_VIEWER_H - y_pos);
230
+ }
231
+
232
+ /**
233
+ * Draws all the body parts of the given agent.
234
+ * @param agent {Object}
235
+ * @param scale {number} - Scale of the environment
236
+ */
237
+ function drawAgent(agent, scale){
238
+ let stroke_coef = 1;
239
+
240
+ if(agent.is_selected){
241
+ stroke_coef = 2;
242
+ }
243
+
244
+ let polys = agent.agent_body.get_elements_to_render();
245
+ for(let poly of polys){
246
+ let shape = poly.GetFixtureList().GetShape();
247
+
248
+ let vertices = [];
249
+ for(let i = 0; i < shape.m_count; i++){
250
+ let world_pos = poly.GetWorldPoint(shape.m_vertices[i]);
251
+ vertices.push([world_pos.x, world_pos.y]);
252
+ }
253
+
254
+ strokeWeight(stroke_coef * 2/scale);
255
+ stroke(poly.color2);
256
+ let color1 = poly.color1;
257
+ if(poly == agent.agent_body.reference_head_object){
258
+ let rgb01 = hexToRgb(poly.color1).map(c => c / 255);
259
+ let rgb255 = color_agent_head(agent, rgb01, poly.color2)[0].map(c => Math.round(c * 255));
260
+ color1 = rgbToHex(rgb255);
261
+ }
262
+ drawPolygon(vertices, color1);
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Draws the given lidars.
268
+ * @param lidars {Array}
269
+ * @param scale {number} - Scale of the environment
270
+ */
271
+ function drawLidars(lidars, scale){
272
+ for(let i = 0; i < lidars.length; i++){
273
+ let lidar = lidars[i];
274
+
275
+ // Draws a red line representing the lidar
276
+ let vertices = [
277
+ [lidar.p1.x, lidar.p1.y],
278
+ [lidar.p2.x, lidar.p2.y]
279
+ ];
280
+ strokeWeight(1/scale);
281
+ drawLine(vertices, "#FF0000");
282
+
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Draws the different parts of the agent's observation.
288
+ * @param agent {Object}
289
+ * @param scale {number} - Scale of the environment
290
+ */
291
+ function drawObservation(agent, scale){
292
+
293
+ // Draws a circle depending to the surface detected by the lidar and the fraction of the lidar
294
+ for(let i = 0; i < agent.lidars.length; i++) {
295
+ let lidar = agent.lidars[i];
296
+ if(lidar.fraction < 1){
297
+ if(lidar.is_water_detected){
298
+ noStroke();
299
+ fill(0, 50 + (1 - lidar.fraction) * 160, 150 + (1 - lidar.fraction) * 105);
300
+ circle(lidar.p2.x, VIEWPORT_H - lidar.p2.y, 5/scale);
301
+ }
302
+ else if(lidar.is_creeper_detected){
303
+ noStroke();
304
+ fill(0, 120 + (1 - lidar.fraction) * 135, 0);
305
+ circle(lidar.p2.x, VIEWPORT_H - lidar.p2.y, 5/scale);
306
+ }
307
+ else{
308
+ noStroke();
309
+ fill(0, 120 + (1 - lidar.fraction) * 135, 0);
310
+ circle(lidar.p2.x, VIEWPORT_H - lidar.p2.y, 5/scale);
311
+ }
312
+ }
313
+ }
314
+
315
+ // Draws a line corresponding to the agent's head angle
316
+ let head = agent.agent_body.reference_head_object;
317
+ let pos = head.GetPosition();
318
+ let angle = head.GetAngle();
319
+ let length = 2 * agent.agent_body.AGENT_WIDTH;
320
+ if(agent.morphology == "spider"){
321
+ length = agent.agent_body.AGENT_WIDTH / 2;
322
+ }
323
+ let vertices = [
324
+ [pos.x - length * Math.cos(angle), pos.y - length * Math.sin(angle)],
325
+ [pos.x + length * Math.cos(angle), pos.y + length * Math.sin(angle)]
326
+ ];
327
+ let color;
328
+ if(Math.abs(angle) > Math.PI / 10){
329
+ color = "#FF0000";
330
+ }
331
+ else if(Math.abs(angle) > Math.PI / 40){
332
+ color = "#F4BE18";
333
+ }
334
+ else{
335
+ color = "#00B400";
336
+ }
337
+ strokeWeight(2/scale);
338
+ drawLine(vertices, color);
339
+
340
+ // Draws an arrow corresponding to the agent's linear velocity
341
+ let vel = head.GetLinearVelocity().Length();
342
+ let x_pos;
343
+ let y_pos;
344
+ if(agent.morphology == "bipedal"){
345
+ x_pos = pos.x - agent.agent_body.AGENT_WIDTH;
346
+ y_pos = pos.y + agent.agent_body.AGENT_HEIGHT / 4;
347
+ }
348
+ else if(agent.morphology == "spider"){
349
+ x_pos = pos.x - agent.agent_body.AGENT_WIDTH / 2;
350
+ y_pos = pos.y + agent.agent_body.AGENT_HEIGHT / 4;
351
+ }
352
+ else if(agent.morphology == "chimpanzee"){
353
+ x_pos = pos.x - agent.agent_body.AGENT_WIDTH;
354
+ y_pos = pos.y + agent.agent_body.AGENT_HEIGHT / 3;
355
+ }
356
+ else if(agent.morphology == "fish"){
357
+ x_pos = pos.x - agent.agent_body.AGENT_WIDTH;
358
+ y_pos = pos.y + agent.agent_body.AGENT_HEIGHT * 1.5;
359
+ }
360
+ vertices = [
361
+ [x_pos, y_pos],
362
+ [x_pos + vel / 2, y_pos]
363
+ ];
364
+ strokeWeight(2/scale);
365
+ drawLine(vertices, "#0070FF");
366
+
367
+ vertices = [
368
+ [x_pos + vel / 2 - 0.25, y_pos + Math.sin(Math.PI / 12)],
369
+ [x_pos + vel / 2, y_pos]
370
+ ]
371
+ drawLine(vertices, "#0070FF");
372
+
373
+ vertices = [
374
+ [x_pos + vel / 2 - 0.25, y_pos - Math.sin(Math.PI / 12)],
375
+ [x_pos + vel / 2, y_pos]
376
+ ]
377
+ drawLine(vertices, "#0070FF");
378
+ }
379
+
380
+ /**
381
+ * Draws the agent's step and episodic reward.
382
+ * @param agent {Object}
383
+ * @param scale {number} - Scale of the environment
384
+ */
385
+ function drawReward(agent, scale){
386
+ // Text reward
387
+ if(window.game.rewards.length > 0){
388
+
389
+ let dict = window.lang_dict[window.get_language()]['advancedOptions'];
390
+
391
+ let pos = agent.agent_body.reference_head_object.GetPosition();
392
+
393
+ let x_pos;
394
+ let y_pos;
395
+ if(agent.morphology == "bipedal"){
396
+ x_pos = pos.x + agent.agent_body.AGENT_WIDTH * 3/2;
397
+ y_pos = pos.y + agent.agent_body.AGENT_HEIGHT;
398
+ }
399
+ else if(agent.morphology == "spider"){
400
+ x_pos = pos.x + agent.agent_body.AGENT_WIDTH / 2;
401
+ y_pos = pos.y + agent.agent_body.AGENT_HEIGHT * 3/2;
402
+ }
403
+ else if(agent.morphology == "chimpanzee"){
404
+ x_pos = pos.x + 8 * agent.agent_body.AGENT_WIDTH;
405
+ y_pos = pos.y - agent.agent_body.AGENT_HEIGHT;
406
+ }
407
+ else if(agent.morphology == "fish"){
408
+ x_pos = pos.x + 9 * agent.agent_body.AGENT_WIDTH;
409
+ y_pos = pos.y;
410
+ }
411
+
412
+ noStroke()
413
+ fill(0);
414
+ textSize(20/ scale);
415
+ textAlign(RIGHT);
416
+ text(dict['stepReward'] + " = ", x_pos, RENDERING_VIEWER_H - y_pos);
417
+ text(dict['totalReward'] + " = ", x_pos, RENDERING_VIEWER_H - (y_pos - 1));
418
+
419
+ let reward = window.game.rewards[window.game.rewards.length - 1][agent.id].toPrecision(3);
420
+ if(reward > 0.35){
421
+ fill("#00B400");
422
+ }
423
+ else if(reward > 0.15){
424
+ fill("#F4BE18");
425
+ }
426
+ else {
427
+ fill("#FF0000");
428
+ }
429
+
430
+ textAlign(LEFT);
431
+ text(reward, x_pos, RENDERING_VIEWER_H - y_pos);
432
+
433
+ let ep_reward = agent.episodic_reward.toPrecision(3);
434
+ if(ep_reward > 230){
435
+ fill("#00B400");
436
+ }
437
+ else {
438
+ fill(0);
439
+ }
440
+ text(ep_reward, x_pos, RENDERING_VIEWER_H - (y_pos - 1));
441
+ }
442
+ }
443
+
444
+ /**
445
+ * Draws the sky and the clouds
446
+ * @param env
447
+ */
448
+ function drawSkyClouds(env){
449
+ push();
450
+
451
+ // Sky
452
+ background("#E6F0FF");
453
+
454
+ // Translation to scroll horizontally and vertically
455
+ translate(- env.scroll[0]/3, env.scroll[1]/3);
456
+
457
+ // Rescaling
458
+ scale(env.scale);
459
+ scale(env.zoom * 3/4);
460
+
461
+ // Translating so that the environment is always horizontally centered
462
+ translate(0, (1 - env.scale * env.zoom) * VIEWPORT_H/(env.scale * env.zoom));
463
+ translate(0, (env.zoom - 1) * (env.ceiling_offset)/env.zoom * 1/3);
464
+
465
+ // Clouds
466
+ for(let cloud of env.cloud_polys){
467
+ noStroke();
468
+ drawPolygon(cloud.poly, "#FFFFFF");
469
+ }
470
+
471
+ pop();
472
+ }
473
+
474
+ /**
475
+ * Draws all the bodies composing the terrain of the given environment.
476
+ * @param env {Object}
477
+ */
478
+ function drawTerrain(env){
479
+ // Updates scroll to stay centered on the agent position
480
+ if(window.agent_followed != null){
481
+ env.set_scroll(window.agent_followed, null, null);
482
+ }
483
+
484
+ // Sky & clouds
485
+ drawSkyClouds(env);
486
+
487
+ // Translation to scroll horizontally and vertically
488
+ translate(- env.scroll[0], env.scroll[1]);
489
+
490
+ // Rescaling
491
+ scale(env.scale);
492
+ scale(env.zoom);
493
+
494
+ // Translating so that the environment is always horizontally centered
495
+ translate(0, (1 - env.scale * env.zoom) * VIEWPORT_H/(env.scale * env.zoom));
496
+ translate(0, (env.zoom - 1) * (env.ceiling_offset)/env.zoom * 1/3);
497
+
498
+ // Water
499
+ let vertices = [
500
+ [-RENDERING_VIEWER_W, -RENDERING_VIEWER_H],
501
+ [-RENDERING_VIEWER_W, env.water_y],
502
+ [2 * RENDERING_VIEWER_W, env.water_y],
503
+ [2 * RENDERING_VIEWER_W, -RENDERING_VIEWER_H]
504
+ ];
505
+ noStroke();
506
+ drawPolygon(vertices, "#77ACE5");
507
+
508
+ // Draws all background elements
509
+ for(let i = 0; i < env.background_polys.length; i++) {
510
+ let poly = env.background_polys[i];
511
+ noStroke();
512
+ drawPolygon(poly.vertices, poly.color);
513
+ }
514
+
515
+ // Draws all terrain elements
516
+ for(let i = 0; i < env.terrain_bodies.length; i++) {
517
+ let poly = env.terrain_bodies[i];
518
+ let shape = poly.body.GetFixtureList().GetShape();
519
+ let vertices = [];
520
+
521
+ if(poly.type == "creeper"){
522
+ for(let i = 0; i < shape.m_count; i++){
523
+ let world_pos = poly.body.GetWorldPoint(shape.m_vertices[i]);
524
+ vertices.push([world_pos.x, world_pos.y]);
525
+ }
526
+ noStroke();
527
+ drawPolygon(vertices, poly.color1);
528
+ }
529
+ else{
530
+ let v1 = poly.body.GetWorldPoint(shape.m_vertex1);
531
+ let v2 = poly.body.GetWorldPoint(shape.m_vertex2);
532
+ vertices = [[v1.x, v1.y], [v2.x, v2.y]];
533
+ strokeWeight(1/env.scale);
534
+ drawLine(vertices, poly.color);
535
+ }
536
+ }
537
+
538
+ // Draws a flag on startpad
539
+ let flag_y1 = TERRAIN_HEIGHT;
540
+ let flag_y2 = flag_y1 + 90 / env.scale;
541
+ let flag_x = TERRAIN_STEP * 3;
542
+ vertices = [
543
+ [flag_x, flag_y1],
544
+ [flag_x, flag_y2]
545
+ ]
546
+ drawLine(vertices, "#000000");
547
+ vertices = [
548
+ [flag_x, flag_y2],
549
+ [flag_x, flag_y2 - 20 / env.scale],
550
+ [flag_x + 40 / env.scale, flag_y2 - 10 / env.scale]
551
+ ]
552
+ drawPolygon(vertices, "#E63300");
553
+
554
+ // Draws all assets
555
+ for(let asset of env.assets_bodies){
556
+ let shape = asset.body.GetFixtureList().GetShape();
557
+
558
+ let stroke_coef = asset.is_selected ? 2 : 1;
559
+
560
+ if(asset.type == "circle"){
561
+ let center = asset.body.GetWorldCenter();
562
+ strokeWeight(stroke_coef * 2/env.scale);
563
+ stroke(asset.color2);
564
+ fill(asset.color1);
565
+ circle(center.x, RENDERING_VIEWER_H - center.y, shape.m_radius * 2);
566
+ }
567
+ }
568
+ }
569
+
570
+ /**
571
+ * Draws a polygon in the canvas with the given vertices.
572
+ * @param vertices {Array}
573
+ * @param color {string}
574
+ */
575
+ function drawPolygon(vertices, color){
576
+ fill(color);
577
+ beginShape();
578
+ for(let v of vertices){
579
+ vertex(v[0], VIEWPORT_H - v[1]);
580
+ }
581
+ endShape(CLOSE);
582
+ }
583
+
584
+ /**
585
+ * Draws a line in the canvas between the two vertices.
586
+ * @param vertices {Array}
587
+ * @param color {string}
588
+ */
589
+ function drawLine(vertices, color){
590
+ stroke(color);
591
+ line(vertices[0][0], VIEWPORT_H - vertices[0][1], vertices[1][0], VIEWPORT_H - vertices[1][1]);
592
+ }
js/envs/multi_agents_continuous_parkour.js ADDED
@@ -0,0 +1,1264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //region Constants
2
+
3
+ const FPS = 50;
4
+ const SCALE = 30; // affects how fast-paced the game is, forces should be adjusted as well
5
+ const VIEWPORT_W = 600;
6
+ const VIEWPORT_H = 400;
7
+
8
+ let RENDERING_VIEWER_W = VIEWPORT_W;
9
+ let RENDERING_VIEWER_H = VIEWPORT_H
10
+
11
+ const NB_LIDAR = 10;
12
+ const LIDAR_RANGE = 160/SCALE;
13
+
14
+ const TERRAIN_STEP = 14/SCALE;
15
+ const TERRAIN_LENGTH = 200; // in steps
16
+ const TERRAIN_HEIGHT = VIEWPORT_H/SCALE/4;
17
+ const TERRAIN_END = 5;
18
+ const INITIAL_TERRAIN_STARTPAD = 20; // in steps
19
+ const FRICTION = 2.5;
20
+ const WATER_DENSITY = 1.0;
21
+ const CREEPER_UNIT = 1;
22
+
23
+ const SCROLL_X_MAX = 500;
24
+ const SCROLL_Y_MAX = 300;
25
+ const INIT_SCROLL_X = -0.035 * RENDERING_VIEWER_W;
26
+ const THUMBNAIL_SCROLL_X = 0;
27
+ let THUMBNAIL_ZOOM = 0.27;
28
+ let INIT_ZOOM = 0.27;
29
+
30
+ //endregion
31
+
32
+ /**
33
+ * @classdesc This environment can host multiple agents and its terrain can be generated either with lists of points for ground and ceiling or with a CPPN.
34
+ */
35
+ class MultiAgentsContinuousParkour {
36
+
37
+ /**
38
+ * @constructor
39
+ * @param agents {{morphologies: Array, policies: Array, positions: Array}} - Morphologies, policies and positions of the agents
40
+ * @param input_CPPN_dim {number} - Dimension of the array that encodes the CPPN
41
+ * @param [terrain_cppn_scale=10] {number} - Smoothing
42
+ * @param [ceiling_offset=200] {number}
43
+ * @param [ceiling_clip_offset=0] {number}
44
+ * @param [water_clip=20] {number}
45
+ * @param [movable_creepers=false] {boolean}
46
+ * @param ground {Array} - List of points {x, y} composing the ground
47
+ * @param ceiling {Array} - List of points {x, y} composing the ceiling
48
+ * @param align_terrain {Object}
49
+ */
50
+ constructor(agents, input_CPPN_dim=3, terrain_cppn_scale=10,
51
+ ceiling_offset=200, ceiling_clip_offset=0, water_clip=20,
52
+ movable_creepers=false, ground, ceiling, align_terrain){
53
+
54
+ // Initializes class attributes
55
+ this.scale = SCALE;
56
+ this.zoom = INIT_ZOOM;
57
+ this.movable_creepers = movable_creepers;
58
+ this.terrain_bodies = [];
59
+ this.background_polys = [];
60
+ this.creepers_joints = [];
61
+ this.ground = ground;
62
+ this.ceiling = ceiling;
63
+ this.align_terrain = align_terrain;
64
+
65
+ // Initializes Box2D
66
+ this.contact_listener = new ContactDetector(this);
67
+ let gravity = new b2.Vec2(0, -10);
68
+ this.world = new b2.World(gravity);
69
+ this.world.SetContactListener(this.contact_listener);
70
+
71
+ // Creates the agents
72
+ this.agents = [];
73
+ console.assert(agents.morphologies.length == agents.policies.length && agents.morphologies.length == agents.positions.length);
74
+ for(let i = 0; i < agents.morphologies.length; i++){
75
+ this.create_agent(agents.morphologies[i], agents.policies[i], agents.positions[i]);
76
+ //this.create_agent("spider", {name: "random", path: null}, null);
77
+ }
78
+
79
+ // Initializes dynamics
80
+ this.water_dynamics = new WaterDynamics(this.world.m_gravity);
81
+ this.climbing_dynamics = new ClimbingDynamics();
82
+
83
+ // Creates the Box2D fixtures
84
+ this.create_terrain_fixtures();
85
+
86
+ // Initializes the CPPN and scales the terrain
87
+ this.terrain_CPPN = new CPPN(TERRAIN_LENGTH, input_CPPN_dim);
88
+ this.set_terrain_cppn_scale(terrain_cppn_scale, ceiling_offset, ceiling_clip_offset);
89
+ }
90
+
91
+ /**
92
+ * Creates an agent with the given parameters.
93
+ * @param morphology {string} - Name of the morphology
94
+ * @param policy {{name: string, age: string, path: string}} - Name and path of the policy model
95
+ * @param init_pos {{x: number, y: number}} - Initial position of the agent
96
+ */
97
+ create_agent(morphology, policy, init_pos){
98
+
99
+ let agent = {
100
+ id: this.agents.length,
101
+ name: policy.name,
102
+ age: policy.age,
103
+ is_selected: false,
104
+ morphology: morphology,
105
+ policy: policy,
106
+ init_pos: init_pos,
107
+ prev_shaping: null,
108
+ episodic_reward: 0,
109
+ };
110
+
111
+ // Initializes the agent's body and lidars according to the morphology
112
+ if(morphology == "bipedal") {
113
+ agent.agent_body = new ClassicBipedalBody(SCALE);
114
+ agent.lidars_config = this.set_lidars_type("down");
115
+ }
116
+ else if(morphology == "chimpanzee") {
117
+ agent.agent_body = new ClimbingProfileChimpanzee(SCALE);
118
+ agent.lidars_config = this.set_lidars_type("up");
119
+ }
120
+ else if(morphology == "fish"){
121
+ agent.agent_body = new FishBody(SCALE, 80, WATER_DENSITY, 600);
122
+ agent.lidars_config = this.set_lidars_type("full");
123
+ }
124
+ else if(morphology == "spider"){
125
+ agent.agent_body = new SpiderBody(SCALE);
126
+ agent.lidars_config = this.set_lidars_type("down");
127
+ }
128
+ else {
129
+ agent.agent_body = new ClassicBipedalBody(SCALE);
130
+ agent.lidars_config = this.set_lidars_type("down");
131
+ }
132
+
133
+ // Adds the new agent to the list of agents
134
+ this.agents.push(agent);
135
+ }
136
+
137
+ /**
138
+ * Seeds the random generator according to the terrain parameters.
139
+ */
140
+ seed(){
141
+ // Creates a string with all the terrain parameters
142
+ let seed = "";
143
+ for(let dim of this.CPPN_input_vector){
144
+ seed += dim;
145
+ }
146
+ seed += this.water_level;
147
+ seed += this.TERRAIN_CPPN_SCALE;
148
+ seed += this.creepers_width;
149
+ seed += this.creepers_height;
150
+ seed += this.creepers_spacing;
151
+ seed += this.movable_creepers;;
152
+
153
+ Math.seedrandom(seed);
154
+ }
155
+
156
+ /**
157
+ * Returns a lidars configuration according to the given type.
158
+ * @param lidars_type {string} - 'down', 'up', or 'full'
159
+ * @returns {{lidar_angle: number, lidar_y_offset: number}}
160
+ */
161
+ set_lidars_type(lidars_type){
162
+ // Use 'down' for walkers, 'up' for climbers and 'full' for swimmers.
163
+ let lidar_config = {};
164
+ if(lidars_type == "down") {
165
+ lidar_config.lidar_angle = 1.5;
166
+ lidar_config.lidar_y_offset = 0;
167
+ }
168
+ else if(lidars_type == "up") {
169
+ lidar_config.lidar_angle = 2.3;
170
+ lidar_config.lidar_y_offset = 1.5;
171
+ }
172
+ else if(lidars_type == "full") {
173
+ lidar_config.lidar_angle = Math.PI;
174
+ lidar_config.lidar_y_offset = 0;
175
+ }
176
+ return lidar_config;
177
+ }
178
+
179
+ /**
180
+ * Scales the terrain according to the smoothing.
181
+ * @param terrain_cppn_scale {number} - Smoothing
182
+ * @param ceiling_offset {number}
183
+ * @param ceiling_clip_offset {number}
184
+ */
185
+ set_terrain_cppn_scale(terrain_cppn_scale, ceiling_offset, ceiling_clip_offset){
186
+ console.assert(terrain_cppn_scale > 1);
187
+ this.TERRAIN_CPPN_SCALE = terrain_cppn_scale;
188
+ this.CEILING_LIMIT = 1000 / this.TERRAIN_CPPN_SCALE;
189
+ this.GROUND_LIMIT = -1000 / INITIAL_TERRAIN_STARTPAD;
190
+ this.ceiling_offset = ceiling_offset / this.TERRAIN_CPPN_SCALE;
191
+ this.ceiling_clip_offset = ceiling_clip_offset / this.TERRAIN_CPPN_SCALE;
192
+ }
193
+
194
+ /**
195
+ * Sets the parameters for terrain generation.
196
+ * Must be called before `reset()`.
197
+ * @param input_vector {Array} - 3-dimensional array that encodes the CPPN
198
+ * @param water_level {number}
199
+ * @param creepers_width {number}
200
+ * @param creepers_height {number}
201
+ * @param creepers_spacing {number}
202
+ * @param terrain_cppn_scale {number} - Smoothing
203
+ * @param movable_creepers {boolean}
204
+ */
205
+ set_environment(input_vector, water_level, creepers_width=null,
206
+ creepers_height=null, creepers_spacing=0.1, terrain_cppn_scale=10, movable_creepers){
207
+
208
+ this.CPPN_input_vector = input_vector;
209
+ this.water_level = water_level > 0 ? water_level : - 0.01;
210
+ this.creepers_width = creepers_width;
211
+ this.creepers_height = creepers_height;
212
+ this.creepers_spacing = Math.max(0.01, creepers_spacing);
213
+ this.movable_creepers = movable_creepers;
214
+ this.set_terrain_cppn_scale(terrain_cppn_scale,
215
+ this.ceiling_offset * this.TERRAIN_CPPN_SCALE,
216
+ this.ceiling_clip_offset * this.TERRAIN_CPPN_SCALE);
217
+ this.seed();
218
+ }
219
+
220
+ /**
221
+ * Destroys all the bodies composing the terrain and the agents
222
+ */
223
+ _destroy(){
224
+ this.world.SetContactListener(null);
225
+ for(let t of this.terrain_bodies){
226
+ this.world.DestroyBody(t.body);
227
+ }
228
+ this.terrain_bodies = [];
229
+ this.creepers_joints = [];
230
+ for(let agent of this.agents){
231
+ agent.agent_body.destroy(this.world);
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Resets the environment.
237
+ * @returns {*[]} - Array that contains the observation state and reward of each agent.
238
+ */
239
+ reset(){
240
+ this._destroy();
241
+ this.contact_listener = new ContactDetector(this);
242
+ this.world.SetContactListener(this.contact_listener);
243
+ this.scroll = [0, 0];
244
+ this.water_y = this.GROUND_LIMIT;
245
+ this.assets_bodies = [];
246
+
247
+ for(let agent of this.agents){
248
+ agent.nb_steps_outside_water = 0;
249
+ agent.nb_steps_under_water = 0;
250
+ agent.critical_contact = false;
251
+ }
252
+
253
+ // Generates the terrain and the agents
254
+ this.generate_game();
255
+
256
+ // Initializes all the agents
257
+ for(let agent of this.agents) {
258
+ this.init_agent(agent);
259
+ }
260
+
261
+ // Runs a simulation step and returns the results
262
+ return this.step();
263
+ }
264
+
265
+ /**
266
+ * Initializes the given agent.
267
+ * @param agent {Object}
268
+ */
269
+ init_agent(agent){
270
+ // Creates the lidars of the agent
271
+ agent.lidars = [];
272
+ for(let i = 0; i < NB_LIDAR; i++){
273
+ agent.lidars.push(new LidarCallback(agent.agent_body.reference_head_object.GetFixtureList().GetFilterData().maskBits));
274
+ }
275
+
276
+ // Initializes the agent with motionless actions
277
+ agent.actions = Array.from({length: agent.agent_body.get_action_size()}, () => 0);
278
+
279
+ agent.nb_steps_outside_water = 0;
280
+ agent.nb_steps_under_water = 0;
281
+ agent.episodic_reward = 0;
282
+ }
283
+
284
+ /**
285
+ * Initializes the position of the given climber agent so that it hangs from the ceiling.
286
+ * @param agent {Object}
287
+ */
288
+ init_climber_pos(agent){
289
+ let y_diff = 0;
290
+ for(let i = 0; i < agent.agent_body.sensors.length; i++){
291
+
292
+ // Tells this sensor to grasp
293
+ agent.actions[agent.actions.length - i - 1] = 1;
294
+
295
+ let sensor = agent.agent_body.sensors[agent.agent_body.sensors.length - i - 1];
296
+ let sensor_position = sensor.GetPosition();
297
+
298
+ // Finds the best y-coordinate of the ceiling according to the x-coordinate of the sensor
299
+ let ceiling_y = find_best_y(sensor_position.x, this.terrain_ceiling);
300
+
301
+ // Computes the vertical offset
302
+ if(y_diff == 0){
303
+ y_diff = ceiling_y - sensor_position.y;
304
+ }
305
+
306
+ // Sets the position of the sensor
307
+ sensor.SetTransform(new b2.Vec2(sensor_position.x, ceiling_y),
308
+ sensor.GetAngle());
309
+ }
310
+
311
+ // Shifts the position of each body part by the vertical offset
312
+ for(let body_part of agent.agent_body.body_parts){
313
+ let body_part_pos = body_part.GetPosition();
314
+ body_part.SetTransform(new b2Vec2(body_part_pos.x, body_part_pos.y + y_diff),
315
+ body_part.GetAngle());
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Runs one step and updates the observation of the agents
321
+ * @returns {Array} - List of return of each agent : [state, reward, done, {success: boolean}]
322
+ */
323
+ step(){
324
+ // Checks if agents are dead according to their morphology
325
+ for(let agent of this.agents){
326
+ if(agent.agent_body.body_type == BodyTypesEnum.SWIMMER){
327
+ if(agent.nb_steps_outside_water > agent.agent_body.nb_steps_can_survive_outside_water){
328
+ agent.is_dead = true;
329
+ agent.actions = Array.from({length: agent.agent_body.get_action_size()}, () => 0);
330
+ }
331
+ else{
332
+ agent.is_dead = false;
333
+ }
334
+ }
335
+ else{
336
+ if(agent.nb_steps_under_water > agent.agent_body.nb_steps_can_survive_under_water){
337
+ agent.is_dead = true;
338
+ agent.actions = Array.from({length: agent.agent_body.get_action_size()}, () => 0);
339
+ }
340
+ else{
341
+ agent.is_dead = false;
342
+ }
343
+ }
344
+
345
+ // Makes the agent moves according to its actions
346
+ agent.agent_body.activate_motors(agent.actions);
347
+
348
+ // Prepares climbing dynamics according to the grasping actions (i.e. readies sensor to grasp or release sensor grip by destroying joint)
349
+ if(agent.agent_body.body_type == BodyTypesEnum.CLIMBER){
350
+ this.climbing_dynamics.before_step_climbing_dynamics(agent.actions, agent.agent_body, this.world);
351
+ }
352
+ }
353
+
354
+ // Updates Box2D world
355
+ //this.world.Step(1.0 / FPS, 45, 15);
356
+ this.world.Step(1.0 / FPS, 3 * 30, 1 * 30);
357
+
358
+ for(let agent of this.agents) {
359
+ // Creates joints between sensors ready to grasp if collision with graspable area was detected
360
+ if(agent.agent_body.body_type == BodyTypesEnum.CLIMBER){
361
+ this.climbing_dynamics.after_step_climbing_dynamics(this.world.m_contactManager.m_contactListener.climbing_contact_detector, this.world);
362
+ }
363
+ }
364
+
365
+ // Filters null fixture pairs to avoid errors with water collisions
366
+ this.world.m_contactManager.m_contactListener.water_contact_detector.fixture_pairs = this.world.m_contactManager.m_contactListener.water_contact_detector.fixture_pairs.filter(function(fp, index, array){
367
+ return fp[0].GetShape() != null && fp[1].GetShape() != null;
368
+ });
369
+ // Calculates water physics
370
+ this.water_dynamics.calculate_forces(this.world.m_contactManager.m_contactListener.water_contact_detector.fixture_pairs);
371
+
372
+ let ret = [];
373
+
374
+ // Observation state for each agent
375
+ for(let agent of this.agents) {
376
+ let head = agent.agent_body.reference_head_object;
377
+ let pos = head.GetPosition();
378
+ let vel = head.GetLinearVelocity();
379
+
380
+ this.update_lidars(agent);
381
+
382
+ let is_under_water = pos.y <= this.water_y;
383
+ if(!agent.is_dead){
384
+ if(is_under_water){
385
+ agent.nb_steps_under_water += 1;
386
+ agent.nb_steps_outside_water = 0;
387
+ }
388
+ else{
389
+ agent.nb_steps_under_water = 0;
390
+ agent.nb_steps_outside_water += 1;
391
+ }
392
+ }
393
+
394
+ let state = [
395
+ head.GetAngle(), // Normal angles up to 0.5 here, but sure more is possible.
396
+ 2.0 * head.GetAngularVelocity() / FPS,
397
+ 0.3 * vel.x * (VIEWPORT_W / SCALE) / FPS, // Normalized to get [-1, 1] range
398
+ 0.3 * vel.y * (VIEWPORT_H / SCALE) / FPS,
399
+ is_under_water ? 1.0 : 0.0,
400
+ agent.is_dead ? 1.0 : 0.0
401
+ ];
402
+
403
+ // Adds motor-related state
404
+ state = state.concat(agent.agent_body.get_motors_state());
405
+
406
+ // Adds sensor-related state for climbers
407
+ if(agent.agent_body.body_type == BodyTypesEnum.CLIMBER){
408
+ state = state.concat(agent.agent_body.get_sensors_state());
409
+ }
410
+
411
+ // Adds lidar-related state with distance and surface detected
412
+ let nb_of_water_detected = 0;
413
+ let surface_detected = [];
414
+ for(let lidar of agent.lidars){
415
+ state.push(lidar.fraction);
416
+ if(lidar.is_water_detected){
417
+ surface_detected.push(-1);
418
+ nb_of_water_detected += 1;
419
+ }
420
+ else if(lidar.is_creeper_detected){
421
+ surface_detected.push(1)
422
+ }
423
+ else{
424
+ surface_detected.push(0);
425
+ }
426
+ }
427
+ state = state.concat(surface_detected)
428
+
429
+ let shaping = 130 * pos.x / SCALE; // moving forward is a way to receive reward (normalized to get 300 on completion)
430
+ if(agent.agent_body.remove_reward_on_head_angle){
431
+ shaping -= 5.0 * Math.abs(state[0]); // keep head straight, other than that and falling, any behavior is unpunished
432
+ }
433
+
434
+ let reward = 0;
435
+ if(agent.prev_shaping != null){
436
+ reward = shaping - agent.prev_shaping;
437
+ }
438
+ agent.prev_shaping = shaping;
439
+
440
+ for(let a of agent.actions){
441
+ reward -= agent.agent_body.TORQUE_PENALTY * 80 * Math.max(0, Math.min(Math.abs(a), 1));
442
+ // normalized to about -50.0 using heuristic, more optimal agent should spend less
443
+ }
444
+
445
+ // Ending conditions
446
+ let done = false;
447
+ if(agent.critical_contact || pos.x < 0){
448
+ reward -= 100;
449
+ done = true;
450
+ }
451
+ if(pos.x > (TERRAIN_LENGTH + INITIAL_TERRAIN_STARTPAD - TERRAIN_END) * TERRAIN_STEP){
452
+ done = true;
453
+ }
454
+ agent.episodic_reward += reward;
455
+
456
+ ret.push([state, reward, done, {"success": agent.episodic_reward > 230}]);
457
+ }
458
+
459
+ return ret;
460
+ }
461
+
462
+ /**
463
+ * Updates the lidars of the given agent by casting a ray for each lidar.
464
+ * @param agent {Object}
465
+ */
466
+ update_lidars(agent){
467
+ let pos = agent.agent_body.reference_head_object.GetPosition();
468
+ for(let i = 0; i < NB_LIDAR; i++){
469
+ agent.lidars[i].fraction = 1.0;
470
+ agent.lidars[i].p1 = pos;
471
+ agent.lidars[i].p2 = new b2.Vec2(
472
+ pos.x + Math.sin(agent.lidars_config.lidar_angle * i / NB_LIDAR + agent.lidars_config.lidar_y_offset) * LIDAR_RANGE,
473
+ pos.y - Math.cos(agent.lidars_config.lidar_angle * i / NB_LIDAR + agent.lidars_config.lidar_y_offset) * LIDAR_RANGE
474
+ );
475
+ this.world.RayCast(agent.lidars[i], agent.lidars[i].p1, agent.lidars[i].p2);
476
+ }
477
+ }
478
+
479
+ /**
480
+ * Closes the environment.
481
+ */
482
+ close(){
483
+ this.world.SetContactListener(null);
484
+ this.contact_listener.Reset();
485
+ this._destroy();
486
+ }
487
+
488
+ // region Rendering
489
+ // ------------------------------------------ RENDERING ------------------------------------------
490
+
491
+ /**
492
+ * Renders the environment.
493
+ */
494
+ render() {
495
+ // Calls p5.js draw function once
496
+ redraw();
497
+ }
498
+
499
+ /**
500
+ * Sets the rendering viewer variables.
501
+ * @param width {number}
502
+ * @param height {number}
503
+ * @param keep_ratio {boolean}
504
+ */
505
+ _SET_RENDERING_VIEWPORT_SIZE(width, height=null, keep_ratio=true){
506
+ RENDERING_VIEWER_W = width;
507
+ if(keep_ratio || height == null){
508
+ RENDERING_VIEWER_H = Math.floor(RENDERING_VIEWER_W / (2 * VIEWPORT_W / VIEWPORT_H));
509
+ }
510
+ else{
511
+ RENDERING_VIEWER_H = height;
512
+ }
513
+ }
514
+ //endregion
515
+
516
+ //region Fixtures Initialization
517
+ // ------------------------------------------ FIXTURES INITIALIZATION ------------------------------------------
518
+
519
+ /**
520
+ * Creates all the Box2D fixtures to be used to generate the terrain.
521
+ */
522
+ create_terrain_fixtures(){
523
+
524
+ // Polygon fixture
525
+ this.fd_polygon = new b2.FixtureDef();
526
+ this.fd_polygon.shape = new b2.PolygonShape();
527
+ let vertices = [
528
+ new b2.Vec2(0, 0),
529
+ new b2.Vec2(1, 0),
530
+ new b2.Vec2(1, -1),
531
+ new b2.Vec2(0, -1)];
532
+ this.fd_polygon.shape.Set(vertices, 4);
533
+ this.fd_polygon.friction = FRICTION;
534
+ this.fd_polygon.filter.categoryBits = 0x1;
535
+ this.fd_polygon.filter.maskBits = 0xFFFF;
536
+
537
+ // Edge fixture
538
+ this.fd_edge = new b2.FixtureDef();
539
+ this.fd_edge.shape = new b2.EdgeShape();
540
+ this.fd_edge.shape.Set(new b2.Vec2(0, 0), new b2.Vec2(1, 1));
541
+ this.fd_edge.friction = FRICTION;
542
+ this.fd_edge.filter.categoryBits = 0x1;
543
+ this.fd_edge.filter.maskBits = 0xFFFF;
544
+
545
+ // Water fixture
546
+ this.fd_water = new b2.FixtureDef();
547
+ this.fd_water.shape = new b2.PolygonShape();
548
+ vertices = [
549
+ new b2.Vec2(0, 0),
550
+ new b2.Vec2(1, 0),
551
+ new b2.Vec2(1, -1),
552
+ new b2.Vec2(0, -1)];
553
+ this.fd_water.shape.Set(vertices, 4);
554
+ this.fd_water.density = WATER_DENSITY;
555
+ this.fd_water.isSensor = true;
556
+
557
+ // Creeper fixture
558
+ this.fd_creeper = new b2.FixtureDef();
559
+ this.fd_creeper.shape = new b2.PolygonShape();
560
+ vertices = [
561
+ new b2.Vec2(0, 0),
562
+ new b2.Vec2(1, 0),
563
+ new b2.Vec2(1, -1),
564
+ new b2.Vec2(0, -1)];
565
+ this.fd_creeper.shape.Set(vertices, 4);
566
+ this.fd_creeper.density = 5.0;
567
+ this.fd_creeper.isSensor = true;
568
+
569
+ // Circle fixture
570
+ this.fd_circle = new b2.FixtureDef();
571
+ this.fd_circle.shape = new b2.CircleShape();
572
+ this.fd_circle.density = 5.0;
573
+ this.fd_circle.friction = FRICTION;
574
+ this.fd_circle.filter.categoryBits = 0x1;
575
+ this.fd_circle.filter.maskBits = 0xFFFF;
576
+
577
+ }
578
+ //endregion
579
+
580
+ // region Game Generation
581
+ // ------------------------------------------ GAME GENERATION ------------------------------------------
582
+
583
+ /**
584
+ * Generates the different elements of the environment.
585
+ */
586
+ generate_game(){
587
+ this._generate_terrain();
588
+ this._generate_clouds();
589
+
590
+ for(let agent of this.agents){
591
+ this._generate_agent(agent);
592
+ }
593
+ }
594
+
595
+ /**
596
+ * Generates all the Box2D bodies composing the terrain.
597
+ */
598
+ _generate_terrain(){
599
+
600
+ // Arrays to contain the actual points of ground and ceiling
601
+ this.terrain_ground = [];
602
+ this.terrain_ceiling = [];
603
+
604
+ // Smooths ground and ceiling by removing points that are too close in order to reduce the number of bodies created
605
+ let ground = smoothTerrainFiler(this.ground, TERRAIN_STEP);
606
+ let ceiling = smoothTerrainFiler(this.ceiling, TERRAIN_STEP);
607
+
608
+ // Creates startpad
609
+ for(let i = 0; i < INITIAL_TERRAIN_STARTPAD; i++){
610
+ this.terrain_ground.push({x: i * TERRAIN_STEP, y: TERRAIN_HEIGHT});
611
+ this.terrain_ceiling.push({x: i * TERRAIN_STEP, y: TERRAIN_HEIGHT + this.ceiling_offset});
612
+ }
613
+
614
+ /* DRAWING GENERATION: generates the terrain from the ground and ceiling arrays of points */
615
+ if(window.is_drawing() || ground.length > 0 || ceiling.length > 0){
616
+
617
+ // Creates ground terrain
618
+ if(ground.length > 0){
619
+
620
+ // Handles smoothing and alignment of the ground with the startpad
621
+ let ground_y_offset = 0;
622
+ if(this.align_terrain.align && this.align_terrain.smoothing != null){
623
+
624
+ // Applies the smoothing as the ratio: current smoothing / previous smoothing
625
+ ground = [...ground.map(p => {
626
+ return {x: p.x, y: p.y / (this.TERRAIN_CPPN_SCALE / this.align_terrain.smoothing)};
627
+ })];
628
+
629
+ // Aligns the ground with the startpad
630
+ if(this.align_terrain.ground_offset == null){
631
+ ground_y_offset = TERRAIN_HEIGHT - ground[0].y;
632
+ }
633
+ // Keeps the same ground alignment (adjusted to fit the smoothing)
634
+ else{
635
+ ground_y_offset = this.align_terrain.ground_offset - ground[0].y;
636
+ }
637
+
638
+ }
639
+
640
+ for(let p of ground){
641
+ this.terrain_ground.push({x: p.x, y: p.y + ground_y_offset});
642
+ }
643
+ }
644
+
645
+ // Creates ceiling terrain
646
+ if(ceiling.length > 0) {
647
+
648
+ // Handles smoothing and alignment of the ceiling with the startpad
649
+ let ceiling_y_offset = 0
650
+ if(this.align_terrain.align && this.align_terrain.smoothing != null){
651
+
652
+ // Applies the smoothing as the ratio: current smoothing / previous smoothing
653
+ ceiling = [...ceiling.map(p => {
654
+ return {x: p.x, y: p.y / (this.TERRAIN_CPPN_SCALE / this.align_terrain.smoothing)};
655
+ })];
656
+
657
+ // Aligns the ceiling with the startpad
658
+ if(this.align_terrain.ceiling_offset == null){
659
+ ceiling_y_offset = TERRAIN_HEIGHT + this.ceiling_offset - ceiling[0].y;
660
+ }
661
+ // Keeps the same ceiling alignment (adjusted to fit the smoothing)
662
+ else{
663
+ ceiling_y_offset = (this.ceiling_offset - ceiling[0].y) - this.align_terrain.ceiling_offset;
664
+ }
665
+ }
666
+
667
+ for(let p of ceiling){
668
+ this.terrain_ceiling.push({x: p.x, y: p.y + ceiling_y_offset});
669
+ }
670
+ }
671
+ }
672
+ /* CPPN GENERATION: generates the terrain from the output of the CPPN model encoded with the input vector */
673
+ else{
674
+ let cppn_y = this.terrain_CPPN.generate(this.CPPN_input_vector).arraySync();
675
+ cppn_y = cppn_y.map(e => [e[0] / this.TERRAIN_CPPN_SCALE, e[1] / this.TERRAIN_CPPN_SCALE]);
676
+
677
+ // Gets y values for the ground and aligns them with the startpad
678
+ let ground_offset = TERRAIN_HEIGHT - cppn_y[0][0];
679
+ let cppn_ground_y = cppn_y.map(e => e[0] + ground_offset);
680
+
681
+ // Gets y values for the ceiling and aligns them with the startpad
682
+ let ceiling_offset = TERRAIN_HEIGHT + this.ceiling_offset - cppn_y[0][1];
683
+ let cppn_ceiling_y = cppn_y.map(e => e[1] + ceiling_offset);
684
+
685
+ // Pushes the terrain values in the lists
686
+ for(let i = 0; i < TERRAIN_LENGTH; i++){
687
+ this.terrain_ground.push({x: (INITIAL_TERRAIN_STARTPAD + i) * TERRAIN_STEP, y: cppn_ground_y[i]});
688
+
689
+ // Clips ceiling so that it does not overlaps the ground
690
+ let ceiling_val = cppn_ground_y[i] + this.ceiling_clip_offset;
691
+ if(cppn_ceiling_y[i] >= ceiling_val){
692
+ ceiling_val = cppn_ceiling_y[i];
693
+ }
694
+ this.terrain_ceiling.push({x: (INITIAL_TERRAIN_STARTPAD + i) * TERRAIN_STEP, y: ceiling_val});
695
+ }
696
+ }
697
+
698
+ // Stores the terrain shapes (without the startpad) in global variables
699
+ window.ground = [...this.terrain_ground];
700
+ window.ground.splice(0, INITIAL_TERRAIN_STARTPAD);
701
+ window.ceiling = [...this.terrain_ceiling];
702
+ window.ceiling.splice(0, INITIAL_TERRAIN_STARTPAD);
703
+
704
+ /* BOX2D TERRAIN CREATION */
705
+ this.terrain_bodies = [];
706
+ this.background_polys = [];
707
+ let poly;
708
+ let poly_data;
709
+
710
+ // Water
711
+ this.min_ground_y = Math.min(...this.terrain_ground.map(p => p.y));
712
+ this.air_max_distance = Math.max(...this.terrain_ceiling.map(p => p.y)) - this.min_ground_y;
713
+ this.water_y = this.min_ground_y + this.water_level * this.air_max_distance;
714
+
715
+ let water_poly = [
716
+ [this.terrain_ground[0].x - 1000, this.GROUND_LIMIT],
717
+ [this.terrain_ground[0].x - 1000, this.water_y],
718
+ [this.terrain_ground[this.terrain_ground.length - 1].x + 1000, this.water_y],
719
+ [this.terrain_ground[this.terrain_ground.length - 1].x + 1000, this.GROUND_LIMIT]
720
+ ];
721
+
722
+ this.fd_water.shape.Set([new b2.Vec2(water_poly[0][0], water_poly[0][1]),
723
+ new b2.Vec2(water_poly[1][0], water_poly[1][1]),
724
+ new b2.Vec2(water_poly[2][0], water_poly[2][1]),
725
+ new b2.Vec2(water_poly[3][0], water_poly[3][1])],
726
+ 4);
727
+ let body_def = new b2.BodyDef();
728
+ body_def.type = b2.Body.b2_staticBody;
729
+ let t = this.world.CreateBody(body_def);
730
+ t.CreateFixture(this.fd_water);
731
+ t.SetUserData(new CustomUserData("water", CustomUserDataObjectTypes.WATER));
732
+ let color = "#77ACE5"; // [0.465, 0.676, 0.898];
733
+ this.water_poly = {
734
+ type : "water",
735
+ color: color,
736
+ vertices: water_poly,
737
+ body : t
738
+ };
739
+
740
+ // Ground
741
+ for(let i = 0; i < this.terrain_ground.length - 1; i++){
742
+ poly = [
743
+ [this.terrain_ground[i].x, this.terrain_ground[i].y],
744
+ [this.terrain_ground[i + 1].x, this.terrain_ground[i + 1].y]
745
+ ];
746
+ this.fd_edge.shape.Set(new b2.Vec2(poly[0][0], poly[0][1]),
747
+ new b2.Vec2(poly[1][0], poly[1][1]));
748
+ let body_def = new b2.BodyDef();
749
+ body_def.type = b2.Body.b2_staticBody;
750
+ let t = this.world.CreateBody(body_def);
751
+ t.CreateFixture(this.fd_edge);
752
+ t.SetUserData(new CustomUserData("grass", CustomUserDataObjectTypes.TERRAIN));
753
+ let color = i % 2 == 0 ? "#4dff4d" : "#4dcc4d"; // [0.3, 1.0, 0.3] : [0.3, 0.8, 0.3]
754
+ poly_data = {
755
+ type : "ground",
756
+ color : color,
757
+ body : t,
758
+ }
759
+ this.terrain_bodies.push(poly_data);
760
+
761
+ // Visual poly to fill the ground
762
+ if(i <= this.terrain_ground.length / 2){
763
+ poly.push([poly[1][0] + 10 * TERRAIN_STEP, 2 * this.GROUND_LIMIT]);
764
+ poly.push([poly[0][0], 2 * this.GROUND_LIMIT]);
765
+ }
766
+ else{
767
+ poly.push([poly[1][0], 2 * this.GROUND_LIMIT]);
768
+ poly.push([poly[0][0] - 10 * TERRAIN_STEP, 2 * this.GROUND_LIMIT]);
769
+ }
770
+
771
+ color = "#66994D"; //[0.4, 0.6, 0.3];
772
+ poly_data = {
773
+ type : "ground",
774
+ color : color,
775
+ vertices : poly,
776
+ }
777
+ this.background_polys.push(poly_data);
778
+ }
779
+
780
+ // Ceiling
781
+ for(let i = 0; i < this.terrain_ceiling.length - 1; i++){
782
+ poly = [
783
+ [this.terrain_ceiling[i].x, this.terrain_ceiling[i].y],
784
+ [this.terrain_ceiling[i + 1].x, this.terrain_ceiling[i + 1].y]
785
+ ];
786
+ this.fd_edge.shape.Set(new b2.Vec2(poly[0][0], poly[0][1]),
787
+ new b2.Vec2(poly[1][0], poly[1][1]));
788
+ body_def = new b2.BodyDef();
789
+ body_def.type = b2.Body.b2_staticBody;
790
+ t = this.world.CreateBody(body_def);
791
+ t.CreateFixture(this.fd_edge);
792
+ t.SetUserData(new CustomUserData("rock", CustomUserDataObjectTypes.GRIP_TERRAIN)); // TODO: CustomUserData
793
+ color = "#004040"; // [0, 0.25, 0.25];
794
+ poly_data = {
795
+ type : "ceiling",
796
+ color : color,
797
+ body : t,
798
+ }
799
+ this.terrain_bodies.push(poly_data);
800
+
801
+ // Visual poly to fill the ceiling
802
+ if(i <= this.terrain_ceiling.length / 2){
803
+ poly.push([poly[1][0] + 10 * TERRAIN_STEP, 2 * this.CEILING_LIMIT]);
804
+ poly.push([poly[0][0], 2 * this.CEILING_LIMIT]);
805
+ }
806
+ else{
807
+ poly.push([poly[1][0], 2 * this.CEILING_LIMIT]);
808
+ poly.push([poly[0][0] - 10 * TERRAIN_STEP, 2 * this.CEILING_LIMIT]);
809
+ }
810
+ color = "#808080"; // [0.5, 0.5, 0.5];
811
+ poly_data = {
812
+ type : "ceiling",
813
+ color : color,
814
+ vertices : poly,
815
+ }
816
+ this.background_polys.push(poly_data);
817
+ }
818
+
819
+ // Creepers
820
+ if(this.creepers_width != null && this.creepers_height != null){
821
+ let creeper_width = Math.max(0.2, this.creepers_width);
822
+ let nb_creepers = Math.floor(this.terrain_ceiling[this.terrain_ceiling.length - 1].x / (this.creepers_spacing + creeper_width));
823
+
824
+ for(let i = 1; i < nb_creepers; i++){
825
+ let creeper_height = Math.max(0.2, Math.random() * (0.1 - (- 0.1)) + this.creepers_height - 0.1);
826
+ let creeper_x_init_pos = i * (this.creepers_spacing + creeper_width);
827
+ let creeper_y_init_pos = find_best_y(creeper_x_init_pos, this.terrain_ceiling);
828
+
829
+ // Breaks creepers in multiple dynamic bodies linked by joints
830
+ if(this.movable_creepers){
831
+
832
+ // Creates a static base to which the creeper is attached
833
+ this.fd_creeper.shape.SetAsBox(creeper_width/2, 0.2);
834
+ body_def = new b2.BodyDef();
835
+ body_def.type = b2.Body.b2_staticBody;
836
+ body_def.position.Set(creeper_x_init_pos, creeper_y_init_pos - 0.1);
837
+ t = this.world.CreateBody(body_def);
838
+ t.CreateFixture(this.fd_creeper);
839
+ t.SetUserData(new CustomUserData("creeper", CustomUserDataObjectTypes.SENSOR_GRIP_TERRAIN));
840
+ let previous_creeper_part = t;
841
+
842
+ // Cuts the creeper in unit parts
843
+ for(let w = 0; w < Math.ceil(creeper_height); w++){
844
+ let h;
845
+ // last iteration: rest of the creeper
846
+ if(w == Math.floor(creeper_height / CREEPER_UNIT)){
847
+ h = Math.max(0.2, creeper_height % CREEPER_UNIT);
848
+ }
849
+ else{
850
+ h = CREEPER_UNIT;
851
+ }
852
+
853
+ this.fd_creeper.shape.SetAsBox(creeper_width/2, h/2);
854
+ body_def = new b2.BodyDef();
855
+ body_def.type = b2.Body.b2_dynamicBody;
856
+ body_def.position.Set(creeper_x_init_pos, creeper_y_init_pos - (w * CREEPER_UNIT) - h/2);
857
+ t = this.world.CreateBody(body_def);
858
+ t.CreateFixture(this.fd_creeper);
859
+ t.SetUserData(new CustomUserData("creeper", CustomUserDataObjectTypes.SENSOR_GRIP_TERRAIN));
860
+ color = "#6F8060"; // [0.437, 0.504, 0.375];
861
+ poly_data = {
862
+ type : "creeper",
863
+ color1 : color,
864
+ color2 : color,
865
+ body : t,
866
+ }
867
+ this.terrain_bodies.push(poly_data);
868
+
869
+ let rjd_def = new b2.RevoluteJointDef();
870
+ let anchor = new b2.Vec2(creeper_x_init_pos, creeper_y_init_pos - (w * CREEPER_UNIT));
871
+ rjd_def.Initialize(previous_creeper_part, t, anchor);
872
+ rjd_def.enableMotor = false;
873
+ rjd_def.enableLimit = true;
874
+ rjd_def.lowerAngle = -0.4 * Math.PI;
875
+ rjd_def.upperAngle = 0.4 * Math.PI;
876
+ let joint = this.world.CreateJoint(rjd_def);
877
+ joint.SetUserData(new CustomMotorUserData("creeper", 6, false));
878
+ this.creepers_joints.push(joint);
879
+ previous_creeper_part = t;
880
+ }
881
+ }
882
+
883
+ // Creates only one static creeper body
884
+ else{
885
+ this.fd_creeper.shape.SetAsBox(creeper_width/2, creeper_height/2);
886
+ body_def = new b2.BodyDef();
887
+ body_def.type = b2.Body.b2_staticBody;
888
+ body_def.position.Set(creeper_x_init_pos, creeper_y_init_pos - creeper_height/2);
889
+ t = this.world.CreateBody(body_def);
890
+ t.CreateFixture(this.fd_creeper);
891
+ t.SetUserData(new CustomUserData("creeper", CustomUserDataObjectTypes.SENSOR_GRIP_TERRAIN));
892
+ color = "#6F8060"; // [0.437, 0.504, 0.375];
893
+ poly_data = {
894
+ type : "creeper",
895
+ color1 : color,
896
+ body : t,
897
+ }
898
+ this.terrain_bodies.push(poly_data);
899
+ }
900
+ }
901
+ }
902
+ }
903
+
904
+ /**
905
+ * Generates random clouds.
906
+ */
907
+ _generate_clouds(){
908
+ this.cloud_polys = [];
909
+ for(let i = 0; i < Math.ceil(TERRAIN_LENGTH/10); i++){
910
+ let x = (Math.random() * 5 * TERRAIN_LENGTH - TERRAIN_LENGTH) * TERRAIN_STEP;
911
+ let y = Math.random() * RENDERING_VIEWER_H/SCALE + RENDERING_VIEWER_H/SCALE * 1/5;
912
+ let poly = [];
913
+ for(let a = 0; a < 10; a++){
914
+ poly.push([
915
+ x + 15 * TERRAIN_STEP * Math.sin(Math.PI * 2 * a / 5) + Math.random() * (0 - 5 * TERRAIN_STEP) + 5 * TERRAIN_STEP,
916
+ y + 5 * TERRAIN_STEP * Math.cos(Math.PI * 2 * a / 5) + Math.random() * (0 - 5 * TERRAIN_STEP) + 5 * TERRAIN_STEP
917
+ ])
918
+ }
919
+ let x1 = Math.min(...poly.map(p => p[0]));
920
+ let x2 = Math.max(...poly.map(p => p[0]));
921
+ this.cloud_polys.push({poly: poly, x1: x1, x2: x2});
922
+ }
923
+ }
924
+
925
+ /**
926
+ * Generates the given agent by computing its initial position and creating its physics body.
927
+ * @param agent {Object}
928
+ * @param init_x {number}
929
+ * @param init_y {number}
930
+ */
931
+ _generate_agent(agent, init_x=null, init_y=null){
932
+
933
+ if(init_x == null){
934
+ // If an init_pos is given for the agent (reset due to terrain reshaping), init_y is computed accordingly
935
+ if(agent.init_pos != null){
936
+ init_x = agent.init_pos.x;
937
+
938
+ // Computes the best y position corresponding to init_x to always generate the walkers on the ground
939
+ if (agent.agent_body.body_type == BodyTypesEnum.WALKER) {
940
+ init_y = find_best_y(init_x, this.terrain_ground) + agent.agent_body.AGENT_CENTER_HEIGHT;
941
+ }
942
+
943
+ // Computes the best y position corresponding to init_x to always generate the swimmers between the ground and the ceiling
944
+ else if(agent.agent_body.body_type == BodyTypesEnum.SWIMMER){
945
+ let y_ground = find_best_y(init_x, this.terrain_ground, agent.agent_body.AGENT_WIDTH);
946
+ if(y_ground == null){
947
+ y_ground = -Infinity;
948
+ }
949
+ let y_ceiling = find_best_y(init_x, this.terrain_ceiling, agent.agent_body.AGENT_WIDTH);
950
+ if(y_ceiling == null){
951
+ y_ceiling = Infinity;
952
+ }
953
+ init_y = Math.max(y_ground + 4 * agent.agent_body.AGENT_CENTER_HEIGHT, Math.min(y_ceiling - 4 * agent.agent_body.AGENT_CENTER_HEIGHT, agent.init_pos.y));
954
+ }
955
+ }
956
+
957
+ // If no init_pos is given (add_agent), the agent is generated on the startpad
958
+ else{
959
+ init_x = TERRAIN_STEP * INITIAL_TERRAIN_STARTPAD / 2;
960
+
961
+ // Sets init_y position according to the agent
962
+ init_y = TERRAIN_HEIGHT + agent.agent_body.AGENT_CENTER_HEIGHT;
963
+ if(agent.agent_body.body_type == BodyTypesEnum.SWIMMER){
964
+ init_y = TERRAIN_HEIGHT + 4 * agent.agent_body.AGENT_CENTER_HEIGHT;
965
+ }
966
+ }
967
+ }
968
+
969
+ // Creates the Box2D bodies of the agent's morphology
970
+ agent.agent_body.draw(this.world, init_x, init_y, 0);
971
+ agent.actions = Array.from({length: agent.agent_body.get_action_size()}, () => 0);
972
+
973
+ // If the agent is a climber, initializes its position
974
+ if(agent.agent_body.body_type == BodyTypesEnum.CLIMBER){
975
+ this.init_climber_pos(agent);
976
+ }
977
+ }
978
+
979
+ /**
980
+ * Sets the position of an agent.
981
+ * @param agent {Object}
982
+ * @param init_x {number}
983
+ * @param init_y {number}
984
+ */
985
+ set_agent_position(agent, init_x, init_y) {
986
+ agent.agent_body.destroy(this.world);
987
+
988
+ if (agent.agent_body.body_type == BodyTypesEnum.WALKER) {
989
+ // Computes the best y position corresponding to init_x to always generate the walkers on the ground
990
+ init_y = find_best_y(init_x, this.terrain_ground) + agent.agent_body.AGENT_CENTER_HEIGHT;
991
+ }
992
+
993
+ // Computes the best y position corresponding to init_x to always generate the swimmers between the ground and the ceiling
994
+ else if(agent.agent_body.body_type == BodyTypesEnum.SWIMMER){
995
+ let y_ground = find_best_y(init_x, this.terrain_ground, agent.agent_body.AGENT_WIDTH);
996
+ if(y_ground == null){
997
+ y_ground = -Infinity;
998
+ }
999
+ let y_ceiling = find_best_y(init_x, this.terrain_ceiling, agent.agent_body.AGENT_WIDTH);
1000
+ if(y_ceiling == null){
1001
+ y_ceiling = Infinity;
1002
+ }
1003
+ init_y = Math.max(y_ground + 4 * agent.agent_body.AGENT_CENTER_HEIGHT, Math.min(y_ceiling - 4 * agent.agent_body.AGENT_CENTER_HEIGHT, init_y));
1004
+ }
1005
+
1006
+ this._generate_agent(agent, init_x, init_y);
1007
+ this.update_lidars(agent);
1008
+ }
1009
+
1010
+ /**
1011
+ * Sets the scroll to follow the given agent or to the given values.
1012
+ * @param agent {Object}
1013
+ * @param h {number} - Horizontal scroll
1014
+ * @param v {number} - Vertical scroll
1015
+ */
1016
+ set_scroll(agent=null, h=null, v=null){
1017
+ let terrain_length = Math.max(this.terrain_ground[this.terrain_ground.length - 1].x, this.terrain_ceiling[this.terrain_ceiling.length - 1].x);
1018
+
1019
+ // Sets the scroll to follow the agent
1020
+ if(agent != null){
1021
+ let x = agent.agent_body.reference_head_object.GetPosition().x;
1022
+ let y = agent.agent_body.reference_head_object.GetPosition().y;
1023
+
1024
+ this.scroll = [
1025
+ (x * this.scale - RENDERING_VIEWER_W/5) * this.zoom,
1026
+ (y * this.scale - RENDERING_VIEWER_H * 2/5) * this.zoom
1027
+ ];
1028
+ }
1029
+
1030
+ // Adjusts the scroll when dragging an agent outside of the canvas
1031
+ else if(window.is_dragging_agent){
1032
+
1033
+ if(window.dragging_side == "left"){
1034
+ this.scroll[0] = window.agent_selected.agent_body.reference_head_object.GetPosition().x * this.scale * this.zoom - RENDERING_VIEWER_W * (0.1 + 0.05)
1035
+ }
1036
+ else if(window.dragging_side == "right"){
1037
+ this.scroll[0] = window.agent_selected.agent_body.reference_head_object.GetPosition().x * this.scale * this.zoom - RENDERING_VIEWER_W * (0.85 + 0.05)
1038
+ }
1039
+
1040
+ // Adjusts the vertical scroll to follow the vertical position of the agent
1041
+ this.scroll[1] = (window.agent_selected.agent_body.reference_head_object.GetPosition().y * this.scale - RENDERING_VIEWER_H * 2/5) * this.zoom;
1042
+ }
1043
+
1044
+ // Sets the scroll to the given values
1045
+ else{
1046
+ this.scroll = [h, v];
1047
+ }
1048
+
1049
+ // Clamps scroll both horizontally and vertically when drawing
1050
+ if(window.is_drawing()){
1051
+ this.scroll[0] = Math.max(INIT_SCROLL_X, Math.min(this.scroll[0], TERRAIN_LENGTH * TERRAIN_STEP * this.scale * this.zoom - RENDERING_VIEWER_W * 0.9 + SCROLL_X_MAX));
1052
+ this.scroll[1] = Math.max(-SCROLL_Y_MAX, Math.min(this.scroll[1], SCROLL_Y_MAX));
1053
+ }
1054
+ else{
1055
+ // Clamps scroll only horizontally when not drawing
1056
+ this.scroll[0] = Math.max(INIT_SCROLL_X, Math.min(this.scroll[0], terrain_length * this.scale * this.zoom - RENDERING_VIEWER_W * 0.9));
1057
+ }
1058
+ }
1059
+
1060
+ /**
1061
+ * Sets the zoom to the given value and clamps it in the authorized range.
1062
+ * @param zoom
1063
+ */
1064
+ set_zoom(zoom){
1065
+ this.zoom = Math.max(0.2, Math.min(parseFloat(zoom), 1.5));
1066
+ }
1067
+
1068
+ /**
1069
+ * Adds an agent to the environment, initializes it and runs one step.
1070
+ * @param morphology {string}
1071
+ * @param policy {{name: string, path: string}}
1072
+ * @param pos {{x: number, y: number}}
1073
+ */
1074
+ add_agent(morphology, policy, pos){
1075
+ this.create_agent(morphology, policy, pos);
1076
+ if(pos != null){
1077
+ this._generate_agent(this.agents[this.agents.length - 1], pos.x, pos.y);
1078
+ }
1079
+ else{
1080
+ this._generate_agent(this.agents[this.agents.length - 1]);
1081
+ }
1082
+ this.init_agent(this.agents[this.agents.length - 1]);
1083
+ let step_rets = this.step();
1084
+ window.game.obs.push([...step_rets.map(e => e[0])]);
1085
+ window.game.rewards.push([...step_rets.map(e => e[1])]);
1086
+ }
1087
+
1088
+ /**
1089
+ * Deletes the agent corresponding to the given index in the list of agents.
1090
+ * @param agent_index {number}
1091
+ */
1092
+ delete_agent(agent_index){
1093
+ if(this.agents.length > 0 && agent_index < this.agents.length){
1094
+
1095
+ // Removes the agent from the list and destroys its body
1096
+ let agent = this.agents[agent_index];
1097
+ this.agents.splice(agent_index, 1);
1098
+ agent.agent_body.destroy(this.world);
1099
+
1100
+ // Adjusts the id of the other agents
1101
+ for(let i = 0; i < this.agents.length; i++){
1102
+ this.agents[i].id = i;
1103
+ }
1104
+
1105
+ // Removes the observation of this agent from the list of observations.
1106
+ window.game.obs[window.game.obs.length - 1].splice(agent_index, 1);
1107
+ window.game.rewards[window.game.rewards.length - 1].splice(agent_index, 1);
1108
+ }
1109
+ }
1110
+
1111
+ /**
1112
+ * Creates a circle body at the given position and with the given radius.
1113
+ * @param pos {{x: number, y: number}}
1114
+ * @param radius {number}
1115
+ */
1116
+ create_circle_asset(pos, radius){
1117
+ // Computes the best y position corresponding to pos.x to always generate the assets between the ground and the ceiling
1118
+ let y_ground = find_best_y(pos.x, this.terrain_ground, radius);
1119
+ if(y_ground == null){
1120
+ y_ground = -Infinity;
1121
+ }
1122
+ let y_ceiling = find_best_y(pos.x, this.terrain_ceiling, radius);
1123
+ if(y_ceiling == null){
1124
+ y_ceiling = Infinity;
1125
+ }
1126
+ let y = Math.max(y_ground + radius, Math.min(y_ceiling - radius, pos.y));
1127
+
1128
+ // Box2D creation
1129
+ this.fd_circle.shape.m_radius = radius;
1130
+ let body_def = new b2.BodyDef();
1131
+ body_def.type = b2.Body.b2_dynamicBody;
1132
+ body_def.position.Assign(new b2.Vec2(pos.x, y));
1133
+ let t = this.world.CreateBody(body_def);
1134
+ t.CreateFixture(this.fd_circle);
1135
+ t.SetUserData(new CustomUserData("circle", CustomUserDataObjectTypes.TERRAIN));
1136
+ let poly_data = {
1137
+ type : "circle",
1138
+ color1 : "#885C00", // [136, 92, 0];
1139
+ color2 : "#5F3D0E", // [95, 61, 14];
1140
+ body : t,
1141
+ is_selected: false
1142
+ }
1143
+ this.assets_bodies.push(poly_data);
1144
+ }
1145
+
1146
+ /**
1147
+ * Sets the position of the given asset to the given position.
1148
+ * @param asset {Object}
1149
+ * @param pos {{x: number, y: number}}
1150
+ */
1151
+ set_asset_position(asset, pos){
1152
+ let shape = asset.body.GetFixtureList().GetShape();
1153
+ let radius;
1154
+ // Only supports circle assets for now
1155
+ if(shape.m_type == b2.Shape.e_circle){
1156
+ radius = shape.m_radius;
1157
+ }
1158
+
1159
+ // Computes the best y position corresponding to pos.x to always generate the assets between the ground and the ceiling
1160
+ let y_ground = find_best_y(pos.x, this.terrain_ground, radius);
1161
+ if(y_ground == null){
1162
+ y_ground = -Infinity;
1163
+ }
1164
+ let y_ceiling = find_best_y(pos.x, this.terrain_ceiling, radius);
1165
+ if(y_ceiling == null){
1166
+ y_ceiling = Infinity;
1167
+ }
1168
+ let y = Math.max(y_ground + radius, Math.min(y_ceiling - radius, pos.y));
1169
+ asset.body.SetTransform(new b2.Vec2(pos.x, y),
1170
+ asset.body.GetAngle());
1171
+ asset.body.SetLinearVelocity(new b2.Vec2(0, -0.1));
1172
+ }
1173
+
1174
+ /**
1175
+ * Deletes the given asset.
1176
+ * @param asset {Object}
1177
+ */
1178
+ delete_asset(asset){
1179
+ if(this.assets_bodies.length > 0){
1180
+ let index = this.assets_bodies.indexOf(asset);
1181
+ if(index != -1){
1182
+ // Removes the asset from the list and destroys its body
1183
+ this.assets_bodies.splice(index, 1);
1184
+ this.world.DestroyBody(asset.body);
1185
+ }
1186
+ }
1187
+ }
1188
+ //endregion
1189
+ }
1190
+
1191
+ /**
1192
+ * Finds the best y value corresponding to the given x value according to the points in the given array.
1193
+ * @param x {number}
1194
+ * @param array {Array}
1195
+ * @param [max_dist=null] {number} - Maximum horizontal distance to consider if no good y position has been found
1196
+ * @return {number}
1197
+ */
1198
+ function find_best_y(x, array, max_dist=null){
1199
+ // Finds the closest point to x in array according to the x-coordinate
1200
+ let p1 = array.reduce(function(prev, curr) {
1201
+ return (Math.abs(curr.x - x) < Math.abs(prev.x - x) ? curr : prev);
1202
+ });
1203
+
1204
+ // Gets the index of p1
1205
+ let i1 = array.indexOf(p1);
1206
+
1207
+ // Gets p2 so that x in [p1.x, p2.x] or x in [p2.x, p1.x]
1208
+ let p2;
1209
+
1210
+ // Case x > p1.x --> x in [p1.x, p2.x]
1211
+ if(x > p1.x){
1212
+ if(i1 < array.length - 1){
1213
+ p2 = array[i1 + 1];
1214
+ }
1215
+ else{
1216
+ p2 = p1;
1217
+ }
1218
+ }
1219
+ // Case x <= p1.x --> x in [p2.x, p1.x]
1220
+ else{
1221
+ if(i1 > 0){
1222
+ p2 = array[i1 - 1];
1223
+ }
1224
+ else{
1225
+ p2 = p1;
1226
+ }
1227
+ }
1228
+
1229
+ let y = p1.y;
1230
+ // Computes the equation of the line between p1 and p2 and finds y corresponding to x
1231
+ if(p1.x != p2.x){
1232
+ let a = (p2.y - p1.y) / (p2.x - p1.x);
1233
+ let b = p1.y - a * p1.x;
1234
+ y = a * x + b;
1235
+ }
1236
+
1237
+ // If p1 and p2 are the same point and x is too distant from it, returns null to indicate that no good point has been found
1238
+ else if(max_dist != null && Math.abs(x - p1.x) > max_dist){
1239
+ y = null;
1240
+ }
1241
+ return y;
1242
+ }
1243
+
1244
+ /**
1245
+ * Smoothes the terrain by filtering points that are horizontally too close according to the given epsilon value.
1246
+ * @param terrain {Array} - Array of points
1247
+ * @param epsilon {number} - Minimal horizontal distance between two points
1248
+ * @return {Array} - Filtered array of points
1249
+ */
1250
+ function smoothTerrainFiler(terrain, epsilon){
1251
+ let smooth_terrain = [];
1252
+ if(terrain.length > 0){
1253
+ smooth_terrain.push(terrain[0]);
1254
+ let n = 0;
1255
+ for(let i = 1; i < terrain.length - 1; i++){
1256
+ if(terrain[i].x >= smooth_terrain[n].x + epsilon){
1257
+ smooth_terrain.push(terrain[i]);
1258
+ n += 1;
1259
+ }
1260
+ }
1261
+ smooth_terrain.push(terrain[terrain.length - 1]);
1262
+ }
1263
+ return smooth_terrain;
1264
+ }
js/game.js ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @classdesc Class that handles the simulation.
3
+ */
4
+ class Game {
5
+ /**
6
+ * @constructor
7
+ * @param agents {{morphologies: Array, policies: Array, positions: Array}} - Morphologies, policies and positions of the agents
8
+ * @param cppn_input_vector {Array} - 3-dimensional array that encodes the CPPN
9
+ * @param water_level {number}
10
+ * @param creepers_width {number}
11
+ * @param creepers_height {number}
12
+ * @param creepers_spacing {number}
13
+ * @param smoothing {number}
14
+ * @param creepers_type {boolean}
15
+ * @param ground {Array} - List of points {x, y} composing the ground
16
+ * @param ceiling {Array} - List of points {x, y} composing the ceiling
17
+ * @param align_terrain {Object}
18
+ */
19
+ constructor(agents, cppn_input_vector, water_level, creepers_width, creepers_height,
20
+ creepers_spacing, smoothing, creepers_type, ground, ceiling, align_terrain) {
21
+
22
+ this.run_fps = 60;
23
+ this.obs = [];
24
+ this.rewards = [];
25
+ this.initWorld(agents, cppn_input_vector, water_level, creepers_width, creepers_height, creepers_spacing, smoothing,
26
+ creepers_type, ground, ceiling, align_terrain);
27
+ this.running = false;
28
+ }
29
+
30
+ /**
31
+ * Initializes the environment.
32
+ * @param agents {{morphologies: Array, policies: Array, positions: Array}} - Morphologies, policies and positions of the agents
33
+ * @param cppn_input_vector {Array} - 3-dimensional array that encodes the CPPN
34
+ * @param water_level {number}
35
+ * @param creepers_width {number}
36
+ * @param creepers_height {number}
37
+ * @param creepers_spacing {number}
38
+ * @param smoothing {number}
39
+ * @param creepers_type {boolean}
40
+ * @param ground {Array} - List of points {x, y} composing the ground
41
+ * @param ceiling {Array} - List of points {x, y} composing the ceiling
42
+ * @param align_terrain {Object}
43
+ */
44
+ initWorld(agents, cppn_input_vector, water_level, creepers_width, creepers_height, creepers_spacing,
45
+ smoothing, creepers_type, ground, ceiling, align_terrain) {
46
+
47
+ this.env = new MultiAgentsContinuousParkour(
48
+ agents,
49
+ 3,
50
+ smoothing,
51
+ 200,
52
+ 90,
53
+ 20,
54
+ creepers_type,
55
+ ground,
56
+ ceiling,
57
+ align_terrain);
58
+
59
+ this.env.set_environment(cppn_input_vector, water_level, creepers_width, creepers_height, creepers_spacing, smoothing, creepers_type);
60
+ let step_rets = this.env.reset();
61
+ this.obs.push([...step_rets.map(e => e[0])]);
62
+ this.rewards.push([...step_rets.map(e => e[1])]);
63
+ }
64
+
65
+ /**
66
+ * Pauses the simulation.
67
+ */
68
+ pause(){
69
+ clearInterval(this.runtime);
70
+ this.running = false;
71
+ }
72
+
73
+ /**
74
+ * Runs the simulation.
75
+ * @returns {Promise<void>}
76
+ */
77
+ async run(){
78
+
79
+ // Loads the policy for each agent before launching the simulation
80
+ for(let agent of window.game.env.agents){
81
+ if(agent.policy.path != null){
82
+ agent.model = await tf.loadGraphModel(agent.policy.path + '/model.json');
83
+ }
84
+ else{
85
+ agent.model = null;
86
+ }
87
+ }
88
+
89
+ // Creates a repeated interval over time
90
+ this.runtime = setInterval(() => {
91
+ this.play();
92
+ }, 1000 / this.run_fps);
93
+ this.running = true;
94
+ }
95
+
96
+ /**
97
+ *
98
+ * @param agents {{morphologies: Array, policies: Array, positions: Array}} - Morphologies, policies and positions of the agents
99
+ * @param cppn_input_vector {Array} - 3-dimensional array that encodes the CPPN
100
+ * @param water_level {number}
101
+ * @param creepers_width {number}
102
+ * @param creepers_height {number}
103
+ * @param creepers_spacing {number}
104
+ * @param smoothing {number}
105
+ * @param creepers_type {boolean}
106
+ * @param ground {Array} - List of points {x, y} composing the ground
107
+ * @param ceiling {Array} - List of points {x, y} composing the ceiling
108
+ * @param align_terrain {Object}
109
+ */
110
+ reset(agents, cppn_input_vector, water_level, creepers_width, creepers_height,
111
+ creepers_spacing, smoothing, creepers_type, ground, ceiling, align_terrain){
112
+
113
+ this.pause();
114
+ let zoom = window.game.env.zoom;
115
+ let scroll = [...window.game.env.scroll];
116
+ this.initWorld(agents, cppn_input_vector, water_level, creepers_width, creepers_height, creepers_spacing, smoothing,
117
+ creepers_type, ground, ceiling, align_terrain);
118
+
119
+ // Keeps the previous zoom and scroll
120
+ window.game.env.set_zoom(zoom);
121
+ window.game.env.set_scroll(null, scroll[0], scroll[1]);
122
+ this.env.render();
123
+ }
124
+
125
+ /**
126
+ * Plays one simulation step.
127
+ */
128
+ play() {
129
+
130
+ // Gets the actions to execute for each agent
131
+ for(let agent of window.game.env.agents){
132
+ let state = this.obs[this.obs.length - 1][agent.id];
133
+
134
+ // Generates the actions thanks to the agent's policy model
135
+ if(agent.model != null){
136
+ let envState = tf.tensor(state,[1, state.length]);
137
+
138
+ let inputs = {
139
+ "Placeholder_1:0": envState
140
+ };
141
+
142
+ let output = 'main/mul:0'
143
+
144
+ agent.actions = agent.model.execute(inputs, output).arraySync()[0];
145
+ }
146
+
147
+ // Generates random actions
148
+ else /*if(agent.policy.name == "random")*/{
149
+ agent.actions = Array.from({length: agent.agent_body.get_action_size()}, () => Math.random() * 2 - 1);
150
+ }
151
+
152
+ // Generates motionless actions
153
+ /*else{
154
+ agent.actions = Array.from({length: agent.agent_body.get_action_size()}, () => 0);
155
+ }*/
156
+ }
157
+
158
+ // Runs one step and stores the resulted states for each agent
159
+ let step_rets = this.env.step();
160
+ this.obs.push([...step_rets.map(e => e[0])]);
161
+ this.rewards.push([...step_rets.map(e => e[1])]);
162
+
163
+ this.env.render();
164
+ }
165
+ }
js/i18n.js ADDED
@@ -0,0 +1,599 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Internationalization dictionary
2
+ window.lang_dict = {
3
+
4
+ // English
5
+ 'EN': {
6
+ 'introTour': {
7
+ 'nextLabel': "Next",
8
+ 'prevLabel': "Back",
9
+ 'doneLabel': "Done",
10
+ 'welcomeTitle': "Welcome!",
11
+ 'welcomeText': "Here you can play with a simulation where autonomously trained agents are trying to navigate through a 2D environment.",
12
+ 'viewportTitle': "Viewport simulation",
13
+ 'viewportText': "Here is the viewport where the simulation is rendered in real time. It allows you to see the environment and visualize live how the agents are dealing with it.<br><br> You can also interact with the simulation using the mouse in order to scroll, zoom or even drag and drop the agents. <br><br> <em>Try it now!</em> ",
14
+ 'runTitle': "Run the simulation",
15
+ 'runText': 'Click the <span style="color: green"><i class="fas fa-play"></i></span> button to run the simulation. <br> Then, click the <span style="color: #FFC700"><i class="fas fa-pause"></i></span> button to pause it or the <span style="color: red"><i class="fas fa-undo-alt"></i></span> to reset it. <br><br> <em>Try it now!</em>',
16
+ 'baseEnvsTitle': "Some environments",
17
+ 'baseEnvsText': "Here are some basic environments that will let you become more familiar with the different morphologies of agents. <br> You will be able to load them into the simulation to visualize the behaviour of the different agents.",
18
+ 'morphologiesTitle': "Agents morphologies",
19
+ 'morphologiesText': "Here are all the morphologies available for the agents. You can select one of several agents for each morphology and add it to the simulation. <br><br> Each agent has been trained to learn an optimal behaviour to navigate through the environment according to its morphology. Try to compare them!",
20
+ 'agentsListTitle': "List of running agents",
21
+ 'agentsListText': "In this section you can find all the agents that are currently running in the simulation.",
22
+ 'customEnvsTitle': "Custom environments",
23
+ 'customEnvsText': "Here you can save and download your custom environments but also upload them from previously downloaded files. Try to share them with your friends!",
24
+ 'furtherTitle': "Going further...",
25
+ 'furtherText': "If you want to customize the environment, access more advanced options or learn more about the context of this demo, open these tabs. <br><br> Enjoy!",
26
+ },
27
+
28
+ 'introHints': {
29
+ 'buttonLabel': "Got it",
30
+ 'tips': `<strong>Tips</strong>
31
+ <ul>
32
+ <li>You can scroll horizontally and vertically in the environment by dragging the mouse.</li>
33
+ <li>You can zoom in or out using the mouse wheel.</li>
34
+ <li>You can select an agent or an asset by clicking on it, and then delete it by pressing the delete key of your keyboard.</li>
35
+ <li>You can drag and drop an agent or an asset using the mouse.</li>
36
+ <li>You can change the eraser and assets radius using the mouse wheel.</li>
37
+ </ul>
38
+ <br>`
39
+ },
40
+
41
+ 'agentsList': {
42
+ 'title': "List of running agents",
43
+ 'follow': "Follow",
44
+ 'followTooltip' : "Center the viewport on the agent",
45
+ 'savePosTooltip': "Save the agent's position",
46
+ 'resetPosTooltip': "Reset the agent's position",
47
+ 'deleteAgentTooltip': "Delete the agent",
48
+ },
49
+
50
+ 'mainButtons':{
51
+ 'runBtnTooltip': "Run the simulation",
52
+ 'pauseBtnTooltip': "Pause the simulation",
53
+ 'resetBtnTooltip': "Reset the simulation",
54
+ 'saveBtnTooltip': "Save the current environment"
55
+ },
56
+
57
+ 'drawingMode': {
58
+ 'intro': 'Here you can draw your own parkour!',
59
+ 'text': `Select the <strong style="color: green"><i class="fas fa-pencil-alt"></i> Ground</strong> or <strong style="color: dimgrey"><i class="fas fa-pencil-alt"></i> Ceiling</strong> button to start drawing the corresponding terrain shape with the mouse.<br>
60
+ Be careful not to draw more than one line at different heights if you want the result to be optimal. <br>
61
+ You can use the <strong style="color: #FFC700"><i class="fas fa-eraser"></i> Erase</strong> button if you need to correct your drawing or the <strong style="color: red"><i class="fas fa-times"></i> Clear</strong> one to clear all your drawing.<br>
62
+ When you are satisfied with the result, just click the <strong style="color: green">Generate Terrain</strong> button.`,
63
+ 'ground': "Ground",
64
+ 'ceiling': "Ceiling",
65
+ 'erase': "Erase",
66
+ 'clear': "Clear",
67
+ 'generateTerrain': "Generate Terrain",
68
+ 'draw': "Draw",
69
+ },
70
+
71
+ 'parkourConfig': {
72
+ 'terrainGeneration': "<strong>Terrain Generation</strong>",
73
+ 'generalParameters': "General Parameters",
74
+ 'creepers': "Creepers",
75
+ 'drawTabBtn': "Draw Yourself!",
76
+ 'procGenTabBtn': "Procedural Generation",
77
+ 'procGenText': `You can also use these three sliders to generate the <strong>terrain shapes</strong> automatically.`,
78
+ 'smoothing': "Smoothing",
79
+ 'waterLevel': "Water level",
80
+ 'creepersWidth': "Width",
81
+ 'creepersHeight': "Height",
82
+ 'creepersSpacing': "Spacing",
83
+ //'creepersType': "Type",
84
+ 'rigid': "Rigid",
85
+ 'swingable': "Swingable",
86
+ },
87
+
88
+ 'morphologies': {
89
+ 'title': "<strong>Add an agent</strong>",
90
+ 'text': "Here you can add an agent to the simulation with the morphology of your choice.",
91
+ 'policySelectTooltip': "Select an agent",
92
+ 'addBtnTooltip': "Add the agent to the simulation",
93
+ 'baby': "baby",
94
+ 'teenager': "teenager",
95
+ 'adult': "adult",
96
+ 'bipedal': {
97
+ 'title': "Bipedal",
98
+ 'description': "This morphology is composed of a head and two legs which allow it to walk on the floor."
99
+ },
100
+ 'spider': {
101
+ 'title': "Spider",
102
+ 'description': "This morphology is composed of a body and four legs which allow it to walk on the floor."
103
+ },
104
+ 'chimpanzee': {
105
+ 'title': "Chimpanzee",
106
+ 'description': "This morphology is composed of a head, a torso and two arms and legs. It can only move by climbing the ceiling and grasping the creepers.",
107
+ },
108
+ 'fish': {
109
+ 'title': "Fish",
110
+ 'description': "This morphology is composed of a head, a tail and a fin, allowing it to swim in the water.",
111
+ },
112
+ },
113
+
114
+ 'envsSets': {
115
+ 'baseSetText': `To begin you can select one of the following environments to load it into the simulation, and then run it by clicking the <span style="color: green"><i class="fas fa-play"></i></span> button.`,
116
+ 'customSetText': `In this section you can store your own custom environments by saving them thanks to the <span style="color: blue"><i class="far fa-save fa-lg"></i></span> button above or by uploading them from a JSON file.`,
117
+ 'uploadCard': {
118
+ 'title': "Upload an environment",
119
+ 'text': `Choose a JSON file then click the <span style="color: orange;"><i class="fas fa-upload"></i></span> button below to save the corresponding environment in your collection.`,
120
+ 'uploadBtnTooltip': "Upload the environment from the selected file",
121
+ },
122
+ 'downloadBtnTooltip': "Download the environment",
123
+ 'deleteBtnTooltip': "Delete the environment",
124
+ },
125
+
126
+ 'advancedOptions': {
127
+ 'renderingOptions': `<strong> Rendering Options </strong>`,
128
+ 'drawJoints': "Draw joints",
129
+ 'drawJointsTooltip': "Joints are what bind the different bodies together. They also act as motors for the agents morphologies.",
130
+ 'drawLidars': "Draw lidars",
131
+ 'drawLidarsTooltip': "Lidars are sensors which enable agents to detect nearby obstacles and water.",
132
+ 'drawNames': "Draw names",
133
+ 'drawNamesTooltip': "Name of the agents.",
134
+ 'drawObservation': "Draw observations",
135
+ 'drawObservationTooltip': "The observation is a partial view of the environment state at each step on which the agents rely to take an action.",
136
+ 'drawReward': "Draw rewards",
137
+ 'drawRewardTooltip': "The reward is a value that indicates how good or bad the current state is, according to the objective of the agent.",
138
+ 'stepReward': "Step reward",
139
+ 'totalReward': "Cumulative reward",
140
+ 'assetsTitle': `<strong> Assets </strong>`,
141
+ 'assetsText': "Here you can find several types of assets, which are objects that you can add to the simulation using the mouse.",
142
+ 'circle': `<i class="fas fa-circle"></i> Ball`,
143
+ 'comingSoon': "More assets coming soon...",
144
+ },
145
+
146
+ 'globalElements': {
147
+ 'demoTitle': "Interactive Deep Reinforcement Learning Demo",
148
+ 'gettingStarted': "Getting Started",
149
+ 'parkourCustomization': "Parkour Customization",
150
+ 'advancedOptions': "Advanced Options",
151
+ 'about': "About...",
152
+ 'saveEnvModal': {
153
+ 'title': `Please enter a name and a description for the current environment.`,
154
+ 'text': "This environment will be saved in your collection of custom environments so that you could reload it later or download it to share it.",
155
+ 'nameLabel': "Name",
156
+ 'descriptionLabel': "Description",
157
+ 'cancelBtn': "Cancel",
158
+ 'confirmBtn': "Save",
159
+ },
160
+ },
161
+
162
+ 'aboutTab': {
163
+ 'purposeTitle': "Purpose of the demo",
164
+ 'purposeText': `<p class="mb-2">
165
+ The goal of this demo is to showcase the challenge of <strong>generalization</strong> to unknown tasks
166
+ for <strong>Deep Reinforcement Learning (DRL)</strong> agents.
167
+ </p>
168
+
169
+ <p class="mb-2">
170
+ <strong>DRL</strong> is a <strong>Machine Learning</strong> approach for teaching <strong>virtual agents</strong>
171
+ how to solve tasks by combining <strong>Reinforcement Learning</strong> and <strong>Deep Learning</strong> methods.
172
+ This approach has been used for a diverse set of applications including robotics (e.g. <a href="https://openai.com/blog/solving-rubiks-cube/">Solving Rubik's Cube</a> <a href="#ref1">[1]</a>),
173
+ video games and boardgames (e.g. <a href="https://deepmind.com/research/case-studies/alphago-the-story-so-far">AlphaGo</a> <a href="#ref2">[2]</a>).
174
+ </p>
175
+
176
+ <p class="mb-2">
177
+ In this demo, all the agents have been <strong>autonomously trained</strong> to learn an efficient behaviour to navigate through a 2D environment,
178
+ combining a DRL algorithm and a teacher algorithm (see <a href="#acl-title">below</a>) so that they can be able to <strong>generalize their behaviour to never-seen-before situations</strong>.
179
+ </p>
180
+
181
+ <p class="mb-4">
182
+ The demo provides different tools to customize the environment in order to test and challenge the
183
+ <strong>robustness</strong> of the agents on different situations.
184
+ </p>`,
185
+ 'rlTitle': "Reinforcement Learning",
186
+ 'rlText': `<p>
187
+ <strong>Reinforcement Learning (RL)</strong> is the study of agents and how they learn by <strong>trial and error</strong>.
188
+ The main idea is to <strong>reward or punish</strong> an agent according to the actions it takes in order to make it learn an efficient behavior to reach an objective.
189
+ <br>
190
+ The RL approaches generally feature an <strong>agent</strong> which evolves and interacts with a <strong>world</strong>.
191
+ At each interaction step, the agent sees a partial <strong>observation</strong> of the current state of the environment and decides of an action to take.
192
+ Each action taken by the agent changes the state of the world.
193
+ The agent also receives a <strong>reward</strong> signal at each step, that indicates how good or bad the current state is
194
+ according to the objective the agent has to reach.
195
+ </p>
196
+
197
+ <div class="row align-items-center mb-4">
198
+ <div class="col-12 col-md-6">
199
+ <p>
200
+ The diagram on the right presents this interaction process between the <strong>agent</strong> and the <strong>environment</strong>,
201
+ with the different information they exchange at each step.
202
+ <br>
203
+ <strong>Maximizing the reward</strong> over steps is a way for the agent to learn a behaviour, also called <strong>policy</strong>,
204
+ to achieve its objective.
205
+ </p>
206
+ </div>
207
+ <div class="col-12 col-md-6">
208
+ <img id="rl-diagram" class="w-100" src="images/about/rl_diagram_transparent_bg.png" alt="RL diagram">
209
+ </div>
210
+ </div>`,
211
+ 'drlTitle': "Deep RL",
212
+ 'drlText': `<p class="mb-2">
213
+ In order to remember and improve the actions taken by the agent, DRL algorithms utilize <strong>artificial neural networks</strong>.
214
+ With <strong>training</strong>, these neural networks are able to <strong>learn to predict an optimal action to take at each step from the observation received</strong>,
215
+ thanks to all the observations and rewards previously received after each action during training.
216
+ Thanks to this, DRL algorithms are able to produce behaviours that are very effective in situations similar to those they were trained on.
217
+ </p>
218
+
219
+ <div class="row justify-content-center my-4">
220
+ <img id="rl-demo_diagram" class="w-50" src="images/about/rl_demo_diagram_EN.png" alt="RL demo diagram">
221
+ </div>
222
+
223
+ <p class="mb-4">
224
+ However, in real-world applications, the environment rarely remains still and frequently evolves. Therefore one would
225
+ want DRL agents to be able to <strong>generalize their behaviour</strong> to previously unseen changes of the environment so that
226
+ they can <strong>adapt to a large range of situations</strong>.
227
+ </p>`,
228
+ 'aclTitle': "Automatic Curriculum Learning",
229
+ 'aclText': `<p class="mb-2">
230
+ One solution to handle this challenge is to train DRL agents on <strong>procedurally generated environments</strong>.
231
+ <br>
232
+ <strong>Procedural generation</strong> is a method of automatically creating environments according to some parameters.
233
+ Using this method, DRL agents can be trained on a <strong>very wide range of environments</strong>, hence allowing them
234
+ to <strong>generalize their behaviour</strong> to more different situations.
235
+ </p>
236
+
237
+ <p class="mb-4">
238
+ However, randomly generating environments during training implies the risk to generate environments that are too difficult or too easy to resolve
239
+ for the agents, preventing them to continuously learn in an efficient way.
240
+ <br>
241
+ Therefore, one would need <strong>smarter training strategies</strong> that propose relevant environments tailored to the current <strong>learning progress</strong> of the <strong>student</strong> (DRL agent).
242
+ This method is called <strong>Automatic Curriculum Learning (ACL)</strong> <a href="#ref3">[3]</a> and is embodied by a <strong>teacher algorithm</strong> which is trained to learn to generate
243
+ the most relevant environments throughout the entire training process according to the student performances.
244
+ <br>
245
+ This way, the teacher proposes easy environments to the student at the beginning and <strong>gradually increases the difficulty
246
+ and the diversity</strong> of the tasks in order to guarantee that the <strong>student is progressing while not always facing the same situation or forgetting what it has already learned</strong>.
247
+ </p>`,
248
+ 'aboutDemoTitle': "About the demo",
249
+ 'aboutDemoText': `
250
+ <p class="mb-2">
251
+ In this demo, all the available agents were trained using <a href="https://spinningup.openai.com/en/latest/algorithms/sac.html">Soft Actor Critic</a> <a href="#ref4">[4]</a>
252
+ as the <strong>DRL student algorithm</strong> alongside different <strong>ACL teacher algorithms</strong> such as <a href="https://arxiv.org/abs/1910.07224">ALP-GMM</a> <a href="#ref5">[5]</a>.
253
+ <br>
254
+ They successfully learned efficient behaviours to move through the environment and to <strong>generalize</strong> to never-seen-before situations.
255
+ </p>
256
+
257
+ <p class="mb-4">
258
+ The physics of the simulation are handled by the <a href="https://github.com/kripken/box2d.js">box2d.js</a> physics engine
259
+ which is a direct port to JavaScript of the <a href="https://github.com/erincatto/box2d">Box2D</a> physics engine.
260
+ <br>
261
+ The <strong>pre-trained policies</strong> (agents behaviours) are loaded in the browser thanks to <a href="https://www.tensorflow.org/js">TensorFlow.js</a>.
262
+ </p>`,
263
+ 'creditsTitle': "Credits",
264
+ 'creditsText': `<p class="mb-4">
265
+ This demo was designed by <a href="https://github.com/pgermon">Paul Germon</a> as part of an internship within <a href="https://flowers.inria.fr/">Flowers</a>
266
+ research team at <a href="https://www.inria.fr/fr">Inria</a>. This internship was monitored by Rémy Portelas and Clément Romac,
267
+ and supervised by Pierre-Yves Oudeyer. Special thanks to Nikita Melkozerov for its very helpful contribution.
268
+ Recommended citation format:
269
+ <a name="germon2021demo"></a><pre>
270
+ @misc{germon2021demo,
271
+ title={Interactive Deep Reinforcement Learning Demo},
272
+ author={Germon, Paul and Romac, Clément and Portelas, Rémy and Pierre-Yves, Oudeyer},
273
+ url={https://developmentalsystems.org/Interactive_DeepRL_Demo/},
274
+ year={2021}
275
+ }
276
+ </pre>
277
+ </p>
278
+
279
+ <ul class="px-3" style="list-style-type: disc">
280
+ <li>The code of this demo is open-source and can be found on this <a href="https://github.com/flowersteam/Interactive_DeepRL_Demo">github repository.</a></li>
281
+ <li>The code of the environment and agents is adapted from the <a href="http://developmentalsystems.org/TeachMyAgent/">TeachMyAgent</a> <a href="#ref6">[6]</a> benchmark's Python code to JavaScript.</li>
282
+ </ul>`,
283
+ 'referencesTitle': "References",
284
+ 'referencesText': `<ul class="mb-4">
285
+ <li id="ref1">[1] OpenAI, Ilge Akkaya, Marcin Andrychowicz, Maciek Chociej, Mateusz Litwin, Bob McGrew, Arthur Petron, Alex Paino, Matthias Plappert, Glenn Powell, Raphael Ribas, Jonas Schneider, Nikolas Tezak, Jerry Tworek, Peter Welinder, Lilian Weng, Qiming Yuan, Wojciech Zaremba, Lei Zhang:
286
+ Solving Rubik's Cube with a Robot Hand (2019). <a href="https://arxiv.org/abs/1910.07113">https://arxiv.org/abs/1910.07113</a></li>
287
+ <li id="ref2">[2] Silver, D., Huang, A., Maddison, C. et al. Mastering the game of Go with deep neural networks and tree search. Nature 529, 484–489 (2016). <a href="https://doi.org/10.1038/nature16961">https://doi.org/10.1038/nature16961</a></li>
288
+ <li id="ref3">[3] Portelas, R., Colas, C., Weng, L., Hofmann, K., & Oudeyer, P. Y. (2020). Automatic curriculum learning for deep rl: A short survey (2020). <a href="https://arxiv.org/abs/2003.04664">https://arxiv.org/abs/2003.04664</a></li>
289
+ <li id="ref4">[4] Haarnoja, T., Zhou, A., Abbeel, P., & Levine, S. (2018, July). Soft actor-critic: Off-policy maximum entropy deep reinforcement learning with a stochastic actor. <em>In International conference on machine learning</em> (pp. 1861-1870). PMLR <a href="https://arxiv.org/abs/1801.01290">https://arxiv.org/abs/1801.01290</a></li>
290
+ <li id="ref5">[5] Portelas, R., Colas, C., Hofmann, K., & Oudeyer, P. Y. (2020, May). Teacher algorithms for curriculum learning of deep rl in continuously parameterized environments. <em>In Conference on Robot Learning</em> (pp. 835-853). PMLR. <a href="https://arxiv.org/abs/1910.07224">https://arxiv.org/abs/1910.07224</a></li>
291
+ <li id="ref6">[6] Romac, C., Portelas, R., Hofmann, K., & Oudeyer, P.-Y. (2021). TeachMyAgent: A Benchmark for Automatic Curriculum Learning in Deep RL. <em>International Conference on Machine Learning</em>, 9052–9063. <a href="https://arxiv.org/abs/2103.09815">https://arxiv.org/abs/2103.09815</a></li>
292
+ </ul>`,
293
+
294
+ }
295
+ },
296
+
297
+ // French
298
+ 'FR': {
299
+ 'introTour': {
300
+ 'nextLabel': "Suivant",
301
+ 'prevLabel': "Précédent",
302
+ 'doneLabel': "Fermer",
303
+ 'welcomeTitle': "Bienvenue !",
304
+ 'welcomeText': "Ici tu peux jouer avec une simulation dans laquelle des agents entraînés de manière autonome essayent de se déplacer au travers d'un environnement 2D.",
305
+ 'viewportTitle': "Fenêtre d'affichage",
306
+ 'viewportText': "C'est dans cet espace que la simulation est affichée en temps réel. Cela va te permettre de visualiser en direct comment les agents essayent de s'adapter à leur environment.<br><br> Tu peux aussi interagir avec la simulation à l'aide de la souris pour faire défiler l'environnement, zoomer ou encore déplacer les agents. <br><br> <em>Tu peux essayer dès maintenant !</em>",
307
+ 'runTitle': "Lancer la simulation",
308
+ 'runText': 'Clique sur le bouton <span style="color: green"><i class="fas fa-play"></i></span> pour lancer la simulation. <br> Tu peux ensuite cliquer sur le bouton <span style="color: #FFC700"><i class="fas fa-pause"></i></span> pour la mettre en pause ou sur le bouton <span style="color: red"><i class="fas fa-undo-alt"></i></span> pour la réinitialiser. <br><br> <em>Tu peux essayer dès maintenant !</em>',
309
+ 'baseEnvsTitle': "Quelques environnements",
310
+ 'baseEnvsText': "Voici quelques environnements de base qui t'aideront à te familiariser avec les différentes morphologies d'agents. <br> Tu pourras les charger dans la simulation pour voir les comportements des différents agents.",
311
+ 'morphologiesTitle': "Les différentes morphologies d'agents",
312
+ 'morphologiesText': "Voici toutes les morphologies disponibles pour les agents. Tu peux choisir parmi plusieurs agents différents pour chaque morphologie et l'ajouter à la simulation. <br><br> Chaque agent a été entraîné pour apprendre un comportement efficace pour se déplacer à travers l'environnement en fonction de sa morphologie. Essaye de les comparer !",
313
+ 'agentsListTitle': "List des agents actifs",
314
+ 'agentsListText': "Dans cette section sont affichés tous les agents actuellement présents dans la simulation.",
315
+ 'customEnvsTitle': "Environnements personnalisés",
316
+ 'customEnvsText': "Ici tu peux sauvegarder et télécharger tes environnements personnalisés mais aussi en importer depuis des fichiers précédemment téléchargés. Essaye de les échanger avec tes amis !",
317
+ 'furtherTitle': "Pour aller plus loin...",
318
+ 'furtherText': "Si tu veux personnaliser ton propre environnement, accéder à des options avancées ou en apprendre davantage à propos du contexte autour de cette démo, ouvre ces onglets. <br><br> Amuse-toi bien !",
319
+ },
320
+
321
+ 'introHints': {
322
+ 'buttonLabel': "Ok",
323
+ 'tips': `<strong>Astuces</strong>
324
+ <ul>
325
+ <li>Tu peux faire défiler l'environnement horizontalement et verticalement à l'aide de la souris.</li>
326
+ <li>Tu peux zoomer ou dézoomer avec la molette de la souris.</li>
327
+ <li>Tu peux sélectionner un agent ou un objet en cliquant dessus, puis le supprimer en appuyant sur la touche suppr de ton clavier.</li>
328
+ <li>Tu peux déplacer un agent ou un objet en le faisant glisser avec la souris.</li>
329
+ <li>Tu peux changer le rayon de la gomme ou la taille des objets avec la molette de la souris.</li>
330
+ </ul>
331
+ <br>`
332
+ },
333
+
334
+ 'agentsList': {
335
+ 'title': "Liste des agents actifs",
336
+ 'follow': "Suivre",
337
+ 'followTooltip' : "Centrer la fenêtre d'affichage sur l'agent",
338
+ 'savePosTooltip': "Sauvegarder la position de l'agent",
339
+ 'resetPosTooltip': "Réinitiliaser la position de l'agent",
340
+ 'deleteAgentTooltip': "Supprimer l'agent",
341
+ },
342
+
343
+ 'mainButtons':{
344
+ 'runBtnTooltip': "Lancer la simulation",
345
+ 'pauseBtnTooltip': "Mettre la simulation en pause",
346
+ 'resetBtnTooltip': "Réinitialiser la simulation",
347
+ 'saveBtnTooltip': "Sauvegarder l'environnement actuel"
348
+ },
349
+
350
+ 'drawingMode': {
351
+ 'intro': 'Ici tu peux dessiner ton propre parkour !',
352
+ 'text': `Selectionne les boutons <strong style="color: green"><i class="fas fa-pencil-alt"></i> Sol</strong> ou <strong style="color: dimgrey"><i class="fas fa-pencil-alt"></i> Plafond</strong> pour commencer à dessiner l'élément du terrain correspondant avec la souris.<br>
353
+ Fais attention à ne pas superposer plusieurs traits à différentes hauteurs pour obtenir un résultat optimal. <br>
354
+ Tu peux utiliser le bouton <strong style="color: #FFC700"><i class="fas fa-eraser"></i> Gommer</strong> pour corriger ton dessin ou le bouton <strong style="color: red"><i class="fas fa-times"></i> Effacer</strong> pour tout effacer.<br>
355
+ Une fois que tu es satisfait du résultat, clique sur le bouton <strong style="color: green"> Générer le terrain</strong>.`,
356
+ 'ground': "Sol",
357
+ 'ceiling': "Plafond",
358
+ 'erase': "Gommer",
359
+ 'clear': "Effacer",
360
+ 'generateTerrain': "Générer le terrain",
361
+ 'draw': "Dessiner",
362
+ },
363
+
364
+ 'parkourConfig': {
365
+ 'terrainGeneration': "<strong>Génération du terrain</strong>",
366
+ 'generalParameters': "Paramètres Généraux",
367
+ 'creepers': "Lianes",
368
+ 'drawTabBtn': "Dessine par toi-même !",
369
+ 'procGenTabBtn': "Génération procédurale",
370
+ 'procGenText': `Tu peux aussi utiliser ces trois curseurs pour générer <strong>les formes du terrain</strong> de manière automatique.`,
371
+ 'smoothing': "Lissage",
372
+ 'waterLevel': "Niveau d'eau",
373
+ 'creepersWidth': "Largeur",
374
+ 'creepersHeight': "Hauteur",
375
+ 'creepersSpacing': "Espacement",
376
+ //'creepersType': "Type",
377
+ 'rigid': "Rigides",
378
+ 'swingable': "Flexibles",
379
+ },
380
+
381
+ 'morphologies': {
382
+ 'title': "<strong>Ajouter un agent</strong>",
383
+ 'text': "Ici tu peux ajouter un agent à la simulation avec la morphologie de ton choix.",
384
+ 'policySelectTooltip': "Sélectionner un agent",
385
+ 'addBtnTooltip': "Ajouter l'agent à la simulation",
386
+ 'baby': "bébé",
387
+ 'teenager': "adolescent",
388
+ 'adult': "adulte",
389
+ 'bipedal': {
390
+ 'title': "Bipède",
391
+ 'description': "Cette morphologie est composée d'une tête et de deux jambes qui lui permettent de marcher sur le sol."
392
+ },
393
+ 'spider': {
394
+ 'title': "Araignée",
395
+ 'description': "Cette morphologie est composée d'un corps et de quatre pattes qui lui permettent de marcher sur le sol."
396
+ },
397
+ 'chimpanzee': {
398
+ 'title': "Chimpanzé",
399
+ 'description': "Cette morphologie est composée d'une tête, d'un corps ainsi que de deux bras et deux jambes. Elle peut uniquement se déplacer en s'accrochant au plafond ou en se balançant de lianes en lianes.",
400
+ },
401
+ 'fish': {
402
+ 'title': "Poisson",
403
+ 'description': "Cette morphologie est composée d'une tête, d'une queue et d'une nageoire, ce qui lui permet de nager dans l'eau.",
404
+ },
405
+ },
406
+
407
+ 'envsSets': {
408
+ 'baseSetText': `Pour commencer tu peux sélectionner un des environnments suivants en cliquant dessus pour le charger dans la simulation, puis lancer celle-ci en cliquant sur le bouton <span style="color: green"><i class="fas fa-play"></i></span>.`,
409
+ 'customSetText': `Dans cette section tu peux stocker tes propres environnements personnalisés en les sauvegardant grâce au bouton <span style="color: blue"><i class="far fa-save fa-lg"></i></span> ci-dessus ou en les important depuis un fichier JSON.`,
410
+ 'uploadCard': {
411
+ 'title': "Importer un environnement",
412
+ 'text': `Choisis un fichier JSON puis clique sur le bouton <span style="color: orange;"><i class="fas fa-upload"></i></span> ci-dessous pour importer l'environnement correspondant dans ta collection.`,
413
+ 'uploadBtnTooltip': "Importer l'environnement depuis le fichier sélectionné",
414
+ },
415
+ 'downloadBtnTooltip': "Télécharger l'environnement",
416
+ 'deleteBtnTooltip': "Supprimer l'environnement",
417
+ },
418
+
419
+ 'advancedOptions': {
420
+ 'renderingOptions': `<strong> Options d'affichage </strong>`,
421
+ 'drawJoints': "Afficher les joints",
422
+ 'drawJointsTooltip': "Les joints sont les liens qui attachent les différents objets physiques ensemble. Ils servent aussi de moteurs pour les morphologies des agents.",
423
+ 'drawLidars': "Afficher les lidars",
424
+ 'drawLidarsTooltip': "Les lidars sont des capteurs qui permettent aux agents de détecter les obstacles et l'eau à proximité.",
425
+ 'drawNames': "Afficher les noms",
426
+ 'drawNamesTooltip': "Nom des agents.",
427
+ 'drawObservation': "Afficher les observations",
428
+ 'drawObservationTooltip': "L'observation d'un agent est une vue partielle de l'état actuel de l'environnement à chaque pas de temps, sur laquelle les agents s'appuient pour effectuer une action.",
429
+ 'drawReward': "Afficher les récompenses",
430
+ 'drawRewardTooltip': "La récompense est une valeur qui indique la qualité de l'état actuel de l'environnement selon l'objectif que l'agent doit atteindre.",
431
+ 'stepReward': "Récompense courante",
432
+ 'totalReward': "Récompense cumulée",
433
+ 'assetsTitle': `<strong> Objets </strong>`,
434
+ 'assetsText': "Ici tu peux trouver plusieurs types d'objets que tu peux ajouter à la simulation avec la souris.",
435
+ 'circle': `<i class="fas fa-circle"></i> Boule`,
436
+ 'comingSoon': "Plus d'objets à venir...",
437
+ },
438
+
439
+ 'globalElements': {
440
+ 'demoTitle': "Démo Interactive de Deep Reinforcement Learning",
441
+ 'gettingStarted': "Commencer",
442
+ 'parkourCustomization': "Personnalisation du parkour",
443
+ 'advancedOptions': "Options avancées",
444
+ 'about': "À propos...",
445
+ 'saveEnvModal': {
446
+ 'title': `Saisis un nom et une description pour l'environnement actuel`,
447
+ 'text': "Cet environnement sera sauvegardé dans ta collection d'environnements personnalisés pour que tu puisses le recharger plus tard ou le télécharger pour le partager.",
448
+ 'nameLabel': "Nom",
449
+ 'descriptionLabel': "Description",
450
+ 'cancelBtn': "Annuler",
451
+ 'confirmBtn': "Sauvegarder",
452
+ },
453
+ },
454
+
455
+ 'aboutTab': {
456
+ 'purposeTitle': "Objectif de la démo",
457
+ 'purposeText': `<p class="mb-2">
458
+ Le but de cette démonstration est de mettre en évidence le challenge de <strong>généralisation</strong>
459
+ à des nouvelles tâches pour des agents entraînés par <strong>Apprentissage par Renforcement Profond (ARP)</strong>.
460
+ </p>
461
+
462
+ <p class="mb-2">
463
+ L'<strong>ARP</strong> est une approche d'<strong>Apprentissage automatique</strong> (<em>Machine Learning</em>)
464
+ qui permet d'apprendre à des <strong>agents virtuels</strong> à résoudre des tâches en combinant des méthodes
465
+ d'<strong>Apprentissage par renforcement</strong> (<em>Reinforcement Learning</em>) et d'<strong>Apprentissage profond</strong> (<em>Deep Learning</em>).
466
+ Cette approche a été utilisée pour diverses applications dont notamment la robotique (e.g. <a href="https://openai.com/blog/solving-rubiks-cube/">Solving Rubik's Cube</a>) <a href="#ref1">[1]</a>,
467
+ les jeux vidéo et les jeux de plateau (e.g. <a href="https://deepmind.com/research/case-studies/alphago-the-story-so-far">AlphaGo</a>) <a href="#ref2">[2]</a>.
468
+ </p>
469
+
470
+ <p class="mb-2">
471
+ Dans cette démo, tous les agents ont été <strong>entraînés de manière autonome</strong> dans le but d'apprendre un
472
+ comportement efficace pour traverser un environnement 2D.
473
+ Ils ont été entraînés en combinant un algorithme d'ARP et un algorithme enseignant (voir <a href="#acl-title">ci-dessous</a>) pour qu'ils soient capables
474
+ de <strong>généraliser leur comportement à des situations inédites</strong>.
475
+ </p>
476
+
477
+ <p class="mb-4">
478
+ La démo fournit différents outils pour personnaliser l'environnement dans le but de tester et mettre la
479
+ <strong>robustesse</strong> des agents à l'épreuve lorsqu'ils font face à différentes situations.
480
+ </p>`,
481
+ 'rlTitle': "Apprentissage par Renforcement",
482
+ 'rlText': `<p>
483
+ L'<strong>Apprentissage par renforcement (AR)</strong> est l'étude des agents virtuels et de leur apprentissage par <strong>essais et erreurs</strong>.
484
+ L'idée principale est de <strong>récompenser ou punir</strong> un agent en fonction des actions qu'il effectue pour qu'il apprenne un comportement efficace pour atteindre un objectif.
485
+ <br>
486
+ Les approches d'<strong>AR</strong> sont généralement composées d'un <strong>agent</strong> qui évolue dans un <strong>monde</strong> ou <strong>environnement</strong> et interagit avec.
487
+ À chaque pas de temps, l'agent voit une <strong>observation</strong> partielle de l'état actuel de l'environnement et décide d'effectuer une action.
488
+ Chaque action effectuée par l'agent change l'état de l'environnement. L'agent reçoit également une <strong>récompense</strong>
489
+ à chaque pas de temps, qui indique à quel point l'état actuel du monde est bon ou mauvais selon l'objectif que l'agent doit atteindre.
490
+ </p>
491
+
492
+ <div class="row align-items-center mb-4">
493
+ <div class="col-12 col-md-6">
494
+ <p>
495
+ Le schéma ci-contre présente ce processus d'interaction entre l'<strong>agent</strong> et l'<strong>environment</strong>,
496
+ avec les différentes informations qu'ils échangent à chaque pas de temps.
497
+ <br>
498
+ <strong>Maximiser la récompense</strong> au cours du temps est un moyen pour l'agent d'apprendre un comportement optimal (ou <strong>politique</strong>)
499
+ pour atteindre son objectif.
500
+ </p>
501
+ </div>
502
+ <div class="col-12 col-md-6">
503
+ <img id="rl-diagram" class="w-100" src="images/about/rl_diagram_fr.png" alt="Schéma AR">
504
+ </div>
505
+ </div>`,
506
+ 'drlTitle': "Apprentissage par renforcement profond",
507
+ 'drlText': `<p class="mb-2">
508
+ Pour améliorer continuellement les actions effectuées par les agents, les algorithmes d'<strong>ARP</strong> utilisent
509
+ des <strong>réseaux de neurones artificiels</strong>.
510
+ Avec de l'<strong>entraînement</strong>, ces réseaux de neurones sont capables d'<strong>apprendre à prédire une action optimale
511
+ à effectuer à chaque étape à partir de l'observation reçue</strong> grâce à toutes les
512
+ observations et les récompenses préalablement reçues après chaque action effectuée au cours de l'entraînement.
513
+ Grâce à cela, les algorithmes d'<strong>ARP</strong> sont capables de produire des comportements très efficaces
514
+ dans des situations similaires à celles sur lesquelles ils ont été entraînés.
515
+ </p>
516
+
517
+ <div class="row justify-content-center my-4">
518
+ <img id="rl-demo_diagram" class="w-50" src="images/about/rl_demo_diagram_FR.png" alt="RL demo diagram">
519
+ </div>
520
+
521
+ <p class="mb-4">
522
+ Cependant, dans des applications réelles, l'environnement reste rarement identique en permanence mais évolue. Par conséquent,
523
+ l'idéal serait que les agents d'<strong>ARP</strong> soient capables de <strong>généraliser leur comportement</strong>
524
+ à des changements inédits du monde pour qu'ils puissent <strong>s'adapter à un large éventail de situations</strong>.
525
+ </p>`,
526
+ 'aclTitle': "Programme d'apprentissage automatique",
527
+ 'aclText': `<p class="mb-2">
528
+ Une solution pour résoudre en partie ce problème est d'entraîner les agents d'<strong>ARP</strong> sur des <strong>environnements générés procéduralement</strong>.
529
+ <br>
530
+ La <strong>génération procédurale</strong> est une méthode permettant de créer automatiquement des environnements en fonction de quelques paramètres.
531
+ En utilisant cette méthode, les agents peuvent être entraînés sur un <strong>très large éventail d'environnements</strong>, leur permettant ainsi
532
+ de <strong>généraliser leur comportement</strong> à plus de situations différentes.
533
+ </p>
534
+
535
+ <p class="mb-4">
536
+ Néanmoins, le fait de générer des environnements aléatoires au cours de l'entraînement implique le risque de générer
537
+ des environnements trop difficiles ou trop faciles à résoudre pour les agents, les empêchant par conséquent d'apprendre en continu de manière efficace.
538
+ <br>
539
+ L'idéal serait donc de disposer de <strong>stratégies d'entraînement plus intelligentes</strong> qui proposeraient des
540
+ environnements pertinents adaptés à l'<strong>avancement de l'apprentissage</strong> de l'<strong>élève</strong> (agent).
541
+ Cette méthode est appelée <strong>programme d'apprentissage automatique</strong> (<em>Automatic Curriculum Learning</em>) <a href="#ref3">[3]</a>
542
+ et est incarnée par un <strong>algorithme enseignant</strong> qui est entraîné dans le but d'apprendre à générer des environnements
543
+ les plus pertinents possibles tout au long du processus d'entraînement de l'élève en fonction de l'évolution de ses performances.
544
+ <br>
545
+ De cette manière, l'enseignant commence par proposer des environnements faciles à l'élève puis <strong>augmente leur difficulté et leur diversité petit à petit</strong>
546
+ pour garantir que <strong>l'élève progresse continuellement tout en ne rencontrant pas toujours la même situation et en n'oubliant pas
547
+ ce qu'il a déjà appris</strong>.
548
+ </p>`,
549
+ 'aboutDemoTitle': "À propos de la démo",
550
+ 'aboutDemoText': `
551
+ <p class="mb-2">
552
+ Dans cette démo, tous les agents ont été entraînés avec <a href="https://spinningup.openai.com/en/latest/algorithms/sac.html">Soft Actor Critic</a> <a href="#ref4">[4]</a>
553
+ comme <strong>algorithme élève d'ARP</strong> aux côtés de différents <strong>algorithmes enseignants</strong> comme par exemple <a href="https://arxiv.org/abs/1910.07224">ALP-GMM</a> <a href="#ref5">[5]</a>.
554
+ <br>
555
+ Les agents ont ainsi appris avec succès des comportements efficaces pour se déplacer à travers les envirionnements 2D en
556
+ <strong>généralisant</strong> leur comportement à des situations inédites.
557
+ </p>
558
+
559
+ <p class="mb-4">
560
+ La physique de la simulation est gérée par le moteur physique <a href="https://github.com/kripken/box2d.js">box2d.js</a>
561
+ qui est un portage direct en JavaScript du moteur physique <a href="https://github.com/erincatto/box2d">Box2D</a>.
562
+ <br>
563
+ Les <strong>politiques pré-entraînées</strong> (comportements des agents) sont chargées dans le navigateur grâce à <a href="https://www.tensorflow.org/js">TensorFlow.js</a>.
564
+ </p>`,
565
+ 'creditsTitle': "Crédits",
566
+ 'creditsText': `<p class="mb-4">
567
+ Cette démo a été conçue par <a href="https://github.com/pgermon">Paul Germon</a> dans le cadre d'un stage au sein de
568
+ l'équipe de recherche <a href="https://flowers.inria.fr/">Flowers</a> au laboratoire <a href="https://www.inria.fr/fr">Inria</a>.
569
+ Ce stage était encadré par Rémy Portelas et Clément Romac, et supervisé par Pierre-Yves Oudeyer.
570
+ Remerciements particuliers à Nikita Melkozerov pour sa contribution.
571
+ Citation recommandée:
572
+ <a name="germon2021demo"></a><pre>
573
+ @misc{germon2021demo,
574
+ title={Interactive Deep Reinforcement Learning Demo},
575
+ author={Germon, Paul and Romac, Clément and Portelas, Rémy and Pierre-Yves, Oudeyer},
576
+ url={https://developmentalsystems.org/Interactive_DeepRL_Demo/},
577
+ year={2021}
578
+ }
579
+ </pre>
580
+ </p>
581
+
582
+ <ul class="px-3" style="list-style-type: disc">
583
+ <li>Le code source de cette démo est libre d'accès sur ce <a href="https://github.com/flowersteam/Interactive_DeepRL_Demo">dépôt github.</a></li>
584
+ <li>Le code de l'environnement et des agents a été adapté en JavaScript à partir du code Python de <a href="http://developmentalsystems.org/TeachMyAgent/">TeachMyAgent</a> <a href="#ref6">[6]</a>.</li>
585
+ </ul>`,
586
+ 'referencesTitle': "Références",
587
+ 'referencesText': `<ul class="mb-4">
588
+ <li id="ref1">[1] OpenAI, Ilge Akkaya, Marcin Andrychowicz, Maciek Chociej, Mateusz Litwin, Bob McGrew, Arthur Petron, Alex Paino, Matthias Plappert, Glenn Powell, Raphael Ribas, Jonas Schneider, Nikolas Tezak, Jerry Tworek, Peter Welinder, Lilian Weng, Qiming Yuan, Wojciech Zaremba, Lei Zhang:
589
+ Solving Rubik's Cube with a Robot Hand (2019). <a href="https://arxiv.org/abs/1910.07113">https://arxiv.org/abs/1910.07113</a></li>
590
+ <li id="ref2">[2] Silver, D., Huang, A., Maddison, C. et al. Mastering the game of Go with deep neural networks and tree search. Nature 529, 484–489 (2016). <a href="https://doi.org/10.1038/nature16961">https://doi.org/10.1038/nature16961</a></li>
591
+ <li id="ref3">[3] Portelas, R., Colas, C., Weng, L., Hofmann, K., & Oudeyer, P. Y. (2020). Automatic curriculum learning for deep rl: A short survey (2020). <a href="https://arxiv.org/abs/2003.04664">https://arxiv.org/abs/2003.04664</a></li>
592
+ <li id="ref4">[4] Haarnoja, T., Zhou, A., Abbeel, P., & Levine, S. (2018, July). Soft actor-critic: Off-policy maximum entropy deep reinforcement learning with a stochastic actor. <em>In International conference on machine learning</em> (pp. 1861-1870). PMLR <a href="https://arxiv.org/abs/1801.01290">https://arxiv.org/abs/1801.01290</a></li>
593
+ <li id="ref5">[5] Portelas, R., Colas, C., Hofmann, K., & Oudeyer, P. Y. (2020, May). Teacher algorithms for curriculum learning of deep rl in continuously parameterized environments. <em>In Conference on Robot Learning</em> (pp. 835-853). PMLR. <a href="https://arxiv.org/abs/1910.07224">https://arxiv.org/abs/1910.07224</a></li>
594
+ <li id="ref6">[6] Romac, C., Portelas, R., Hofmann, K., & Oudeyer, P.-Y. (2021). TeachMyAgent: A Benchmark for Automatic Curriculum Learning in Deep RL. <em>International Conference on Machine Learning</em>, 9052–9063. <a href="https://arxiv.org/abs/2103.09815">https://arxiv.org/abs/2103.09815</a></li>
595
+ </ul>`,
596
+
597
+ }
598
+ }
599
+ }
js/ui_state/components/about_tab.js ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Component from '../lib/component.js';
2
+ import store from '../store/index.js';
3
+
4
+ /**
5
+ * @classdesc UI component for "About..." tab.
6
+ */
7
+ export default class AboutTab extends Component {
8
+
9
+ /**
10
+ * @constructor
11
+ */
12
+ constructor() {
13
+ super({
14
+ store,
15
+ element: document.querySelector('#about-tab'),
16
+ eventName: 'aboutTabChange'
17
+ });
18
+ }
19
+
20
+ /**
21
+ * Renders the global UI elements.
22
+ */
23
+ render() {
24
+ let dict = window.lang_dict[store.state.language]['aboutTab'];
25
+
26
+ // Purpose section
27
+ this.element.querySelector('#purpose-title').innerHTML = dict['purposeTitle'];
28
+ this.element.querySelector('#purpose-text').innerHTML = dict['purposeText'];
29
+
30
+ // RL section
31
+ this.element.querySelector('#rl-title').innerHTML = dict['rlTitle'];
32
+ this.element.querySelector('#rl-text').innerHTML = dict['rlText'];
33
+
34
+ // DRL section
35
+ this.element.querySelector('#drl-title').innerHTML = dict['drlTitle'];
36
+ this.element.querySelector('#drl-text').innerHTML = dict['drlText'];
37
+
38
+ // ACL section
39
+ this.element.querySelector('#acl-title').innerHTML = dict['aclTitle'];
40
+ this.element.querySelector('#acl-text').innerHTML = dict['aclText'];
41
+
42
+ // About demo section
43
+ this.element.querySelector('#about-demo-title').innerHTML = dict['aboutDemoTitle'];
44
+ this.element.querySelector('#about-demo-text').innerHTML = dict['aboutDemoText'];
45
+
46
+ // Credits section
47
+ this.element.querySelector('#credits-title').innerHTML = dict['creditsTitle'];
48
+ this.element.querySelector('#credits-text').innerHTML = dict['creditsText'];
49
+
50
+ // References section
51
+ this.element.querySelector('#references-title').innerHTML = dict['referencesTitle'];
52
+ this.element.querySelector('#references-text').innerHTML = dict['referencesText'];
53
+ }
54
+ };
js/ui_state/components/advanced_options.js ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Component from '../lib/component.js';
2
+ import store from '../store/index.js';
3
+
4
+ /**
5
+ * @classdesc UI component for advanced options.
6
+ */
7
+ export default class AdvancedOptions extends Component {
8
+
9
+ /**
10
+ * @constructor
11
+ */
12
+ constructor() {
13
+ super({
14
+ store,
15
+ element: document.querySelector('#advancedOptions'),
16
+ eventName: 'advancedOptionsChange'
17
+ });
18
+ }
19
+
20
+ /**
21
+ * Renders the advanced options UI elements.
22
+ */
23
+ render() {
24
+
25
+ const state = store.state.advancedOptionsState;
26
+
27
+ let dict = window.lang_dict[store.state.language]['advancedOptions'];
28
+
29
+ /* Rendering Options */
30
+ this.element.querySelector('#renderingOptionsTitle').innerHTML = dict['renderingOptions'];
31
+
32
+
33
+ let drawJointsSwitch = this.element.querySelector('#drawJointsSwitch');
34
+ let drawLidarsSwitch = this.element.querySelector('#drawLidarsSwitch');
35
+ let drawNamesSwitch = this.element.querySelector('#drawNamesSwitch');
36
+ let drawObservationSwitch = this.element.querySelector('#drawObservationSwitch');
37
+ let drawRewardSwitch = this.element.querySelector('#drawRewardSwitch');
38
+
39
+ // Checks the draw switches
40
+ drawJointsSwitch.checked = state.drawJoints;
41
+ drawLidarsSwitch.checked = state.drawLidars;
42
+ drawNamesSwitch.checked = state.drawNames;
43
+ drawObservationSwitch.checked = state.drawObservation;
44
+ drawRewardSwitch.checked = state.drawReward;
45
+
46
+ // Tooltips
47
+ drawJointsSwitch.title = dict['drawJointsTooltip'];
48
+ drawLidarsSwitch.title = dict['drawLidarsTooltip'];
49
+ drawNamesSwitch.title = dict['drawNamesTooltip'];
50
+ drawObservationSwitch.title = dict['drawObservationTooltip'];
51
+ drawRewardSwitch.title = dict['drawRewardTooltip'];
52
+
53
+ // Switches labels
54
+ this.element.querySelector('#drawJointsLabel').innerText = dict['drawJoints'];
55
+ this.element.querySelector('#drawLidarsLabel').innerText = dict['drawLidars'];
56
+ this.element.querySelector('#drawNamesLabel').innerText = dict['drawNames'];
57
+ this.element.querySelector('#drawObservationLabel').innerText = dict['drawObservation'];
58
+ this.element.querySelector('#drawRewardLabel').innerText = dict['drawReward'];
59
+
60
+ /* Assets */
61
+
62
+ this.element.querySelector('#assetsTitle').innerHTML = dict['assetsTitle'];
63
+ this.element.querySelector('#assetsText').innerText = dict['assetsText'];
64
+ this.element.querySelector('#comingSoon').innerText = dict['comingSoon'];
65
+
66
+ // Renders the assets buttons
67
+ let circleAssetButton = this.element.querySelector('#circleAssetButton');
68
+ circleAssetButton.innerHTML = dict['circle'];
69
+ if(state.assets.circle){
70
+ circleAssetButton.className = "btn btn-asset";
71
+ }
72
+ else{
73
+ circleAssetButton.className = "btn btn-outline-asset";
74
+ }
75
+
76
+ /* Initializes tooltips */
77
+ this.element.querySelectorAll('[data-bs-toggle="tooltip"]').forEach((el, index) => {
78
+ return new bootstrap.Tooltip(el, {
79
+ trigger: 'hover'
80
+ });
81
+ });
82
+ }
83
+ };