Commit
•
09a6f7f
1
Parent(s):
a791ca7
Added interactive demo with some policies
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +0 -30
- base_envs_set.json +1 -0
- base_envs_set/00_Flat_parkour_+_Bipedal_Walker.json +1 -0
- base_envs_set/01_Easy_parkour_+_Chimpanzee.json +0 -0
- base_envs_set/02_Underwater_parkour_+_Fish.json +0 -0
- base_envs_set/03_Hard_parkour.json +0 -0
- demo.css +94 -0
- images/about/rl_demo_diagram_EN.png +0 -0
- images/about/rl_demo_diagram_FR.png +0 -0
- images/about/rl_diagram_fr.png +0 -0
- images/about/rl_diagram_transparent_bg.png +0 -0
- images/agents_thumbnails/bipedal_thumbnail.png +0 -0
- images/agents_thumbnails/chimpanzee_thumbnail.png +0 -0
- images/agents_thumbnails/fish_thumbnail.png +0 -0
- images/agents_thumbnails/spider_thumbnail.png +0 -0
- images/favicon.ico +0 -0
- index.html +609 -21
- index.js +661 -0
- js/Box2D_dynamics/climbing_dynamics.js +175 -0
- js/Box2D_dynamics/contact_detector.js +88 -0
- js/Box2D_dynamics/water_dynamics.js +244 -0
- js/CPPN/cppn.js +33 -0
- js/CPPN/weights/ground_cppn/.data-00000-of-00001 +0 -0
- js/CPPN/weights/ground_cppn/.index +0 -0
- js/CPPN/weights/ground_cppn/.meta +0 -0
- js/CPPN/weights/ground_cppn/checkpoint +3 -0
- js/CPPN/weights/same_ground_ceiling_cppn/.data-00000-of-00001 +0 -0
- js/CPPN/weights/same_ground_ceiling_cppn/.index +0 -0
- js/CPPN/weights/same_ground_ceiling_cppn/.meta +0 -0
- js/CPPN/weights/same_ground_ceiling_cppn/checkpoint +3 -0
- js/CPPN/weights/same_ground_ceiling_cppn/frozen_model/saved_model.pb +0 -0
- js/CPPN/weights/same_ground_ceiling_cppn/tfjs_model/group1-shard1of1.bin +3 -0
- js/CPPN/weights/same_ground_ceiling_cppn/tfjs_model/model.json +1 -0
- js/bodies/abstract_body.js +161 -0
- js/bodies/bodies_enum.js +7 -0
- js/bodies/climbers/climber_abstract_body.js +60 -0
- js/bodies/climbers/climbing_profile_chimpanzee.js +293 -0
- js/bodies/swimmers/fish_body.js +248 -0
- js/bodies/swimmers/swimmer_abstract_body.js +26 -0
- js/bodies/walkers/classic_bipedal_body.js +140 -0
- js/bodies/walkers/old_classic_bipedal_body.js +140 -0
- js/bodies/walkers/spider_body.js +189 -0
- js/bodies/walkers/walker_abstract_body.js +16 -0
- js/box2d.js +0 -0
- js/draw_p5js.js +592 -0
- js/envs/multi_agents_continuous_parkour.js +1264 -0
- js/game.js +165 -0
- js/i18n.js +599 -0
- js/ui_state/components/about_tab.js +54 -0
- 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 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
};
|