ClementRomac's picture
ClementRomac HF staff
Policies are now loaded from the hub
fb118db
p5.disableFriendlyErrors = true; // disables FES
// Colors to be used for the joints
let JOINTS_COLORS = {
"creeper": "#00B400",
"hip": "#FF7818",
"knee": "#F4BE18",
"neck": "#0000FF",
"shoulder": "#6CC9FF",
"elbow": "#FF00AA",
"hand": "#FF8CFF",
"grip": "#FF0000",
};
// Secondary off-screen canvas
let drawing_canvas; // Used to draw the terrain shapes
let trace_canvas; // Used to draw the erase and assets traces following the mouse
let forbidden_canvas; // Used to draw the forbidden red area on the terrain startpad
let tmp_canvas;
/**
* Creates the different canvas and sets them up. Called automatically when the programs starts.
*/
function setup() {
let canvas_container = document.querySelector('#canvas_container');
RENDERING_VIEWER_W = canvas_container.offsetWidth;
window.canvas = createCanvas(RENDERING_VIEWER_W, RENDERING_VIEWER_H);
INIT_ZOOM = RENDERING_VIEWER_W / ((TERRAIN_LENGTH + INITIAL_TERRAIN_STARTPAD) * 1.05 * TERRAIN_STEP * SCALE);
THUMBNAIL_ZOOM = RENDERING_VIEWER_W / ((TERRAIN_LENGTH + INITIAL_TERRAIN_STARTPAD) * 0.99 * TERRAIN_STEP * SCALE);
canvas.parent("canvas_container");
canvas.style('display', 'block');
canvas.style('margin-left', 'auto');
canvas.style('margin-right', 'auto');
// Creates the off-screen canvas. Height is bigger than main canvas' so that one can scroll vertically when drawing.
drawing_canvas = createGraphics(RENDERING_VIEWER_W + SCROLL_X_MAX, RENDERING_VIEWER_H + 2 * SCROLL_Y_MAX);
trace_canvas = createGraphics(RENDERING_VIEWER_W + SCROLL_X_MAX, RENDERING_VIEWER_H + 2 * SCROLL_Y_MAX);
forbidden_canvas = createGraphics(RENDERING_VIEWER_W + SCROLL_X_MAX, RENDERING_VIEWER_H + 2 * SCROLL_Y_MAX);
tmp_canvas = createGraphics(RENDERING_VIEWER_W + SCROLL_X_MAX, RENDERING_VIEWER_H + 2 * SCROLL_Y_MAX);
// Prevents automatic calls the draw() function
noLoop();
}
/**
* Converts one rgb component to hexadecimal.
* @param c {number}
* @return {string}
*/
function componentToHex(c) {
let hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
/**
* Converts the rgb array to hexadecimal string.
* @param rgb {Array}
* @return {string}
*/
function rgbToHex(rgb) {
return "#" + componentToHex(rgb[0]) + componentToHex(rgb[1]) + componentToHex(rgb[2]);
}
/**
* Converts hexadecimal string to rgb array
* @param hex
* @return {[number, number, number]}
*/
function hexToRgb(hex) {
let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
let rgb = [
parseInt(result[1], 16),
parseInt(result[2], 16),
parseInt(result[3], 16)
];
return result ? rgb : null;
}
/**
* Color agent's head depending on its 'dying' state.
* @param agent {Object}
* @param c1 {Array}
* @param c2 {Array}
* @return {Array}
*/
function color_agent_head(agent, c1, c2){
let ratio = 0;
if(agent.agent_body.body_type == BodyTypesEnum.SWIMMER){
ratio = agent.nb_steps_outside_water / agent.agent_body.nb_steps_can_survive_outside_water;
}
else {
ratio = agent.nb_steps_under_water / agent.agent_body.nb_steps_can_survive_under_water;
}
let color1 = [
c1[0] + ratio * (1.0 - c1[0]),
c1[1] + ratio * (0.0 - c1[1]),
c1[2] + ratio * (0.0 - c1[2])
]
let color2 = c2;
return [color1, color2];
}
/**
* Renders all the elements of the environment.
*/
function draw() {
if(window.game != null){
let env = window.game.env;
push();
drawTerrain(env);
// Renders the agents if not drawing mode
if(!window.is_drawing()){
for(let agent of env.agents){
// Draws the agent morphology
drawAgent(agent, env.scale);
// Draws the agent's lidars
if(window.draw_lidars){
drawLidars(agent.lidars, env.scale);
}
// Draws the agent's observation
if(window.draw_observation){
drawObservation(agent, env.scale);
}
// Draws the agent's rewards
if(window.draw_reward){
drawReward(agent, env.scale);
}
// Draws the agent's joints
if(window.draw_joints){
// Agent motors
let joints = [...agent.agent_body.motors];
// Adds neck joint and grip joints for climbers
if(agent.agent_body.body_type == BodyTypesEnum.CLIMBER){
joints.push(agent.agent_body.neck_joint);
let grip_joints = [...agent.agent_body.sensors.map(s => s.GetUserData().has_joint ? s.GetUserData().joint : null)];
joints = joints.concat(grip_joints);
}
drawJoints(joints, env.scale);
}
// Draws the agent's name
if(window.draw_names){
drawName(agent, env.scale);
}
}
}
// Draws creepers joints
if(window.draw_joints) {
drawJoints(env.creepers_joints, env.scale);
}
pop();
}
}
/**
* Draws the given sensors.
* @param sensors {Array}
* @param scale {number} - Scale of the environment
*/
function drawSensors(sensors, scale){
for(let i = 0; i < sensors.length; i++){
let radius = sensors[i].GetFixtureList().GetShape().m_radius + 0.01;
let sensor_world_center = sensors[i].GetPosition()//sensors[i].GetWorldCenter();
noStroke();
fill(255, 0, 0, 255);
//fill("#FFFF00");
circle(sensor_world_center.x, VIEWPORT_H - sensor_world_center.y, radius);
}
}
/**
* Draws the given joints.
* @param joints
* @param scale {number} - Scale of the environment
*/
function drawJoints(joints, scale){
for(let i = 0; i < joints.length; i++){
if(joints[i] != null){
let posA = joints[i].m_bodyA.GetWorldPoint(joints[i].m_localAnchorA);
let posB = joints[i].m_bodyB.GetWorldPoint(joints[i].m_localAnchorB);
noStroke();
let joint_type = joints[i].GetUserData().name;
fill(JOINTS_COLORS[joint_type]);
let radius = joint_type == "creeper" ? 5 : 7;
circle(posA.x, VIEWPORT_H - posA.y, radius/scale);
circle(posB.x, VIEWPORT_H - posB.y, radius/scale);
}
}
}
/**
* Draws the name of the given agent.
* @param agent {Object}
* @param scale {number} - Scale of the environment
*/
function drawName(agent, scale){
let pos = agent.agent_body.reference_head_object.GetPosition();
fill(0);
noStroke()
textSize(25 / scale);
textAlign(CENTER);
let x_pos = pos.x;
let y_pos;
if(agent.morphology == "bipedal"){
y_pos = pos.y + agent.agent_body.AGENT_HEIGHT/3;
}
else if(agent.morphology == "spider"){
y_pos = pos.y + agent.agent_body.AGENT_HEIGHT / 2;
}
else if(agent.morphology == "chimpanzee"){
y_pos = pos.y + agent.agent_body.AGENT_HEIGHT/2;
}
else if(agent.morphology == "fish"){
y_pos = pos.y + agent.agent_body.AGENT_HEIGHT * 2;
}
text(agent.name, x_pos, RENDERING_VIEWER_H - y_pos);
}
/**
* Draws all the body parts of the given agent.
* @param agent {Object}
* @param scale {number} - Scale of the environment
*/
function drawAgent(agent, scale){
let stroke_coef = 1;
if(agent.is_selected){
stroke_coef = 2;
}
let polys = agent.agent_body.get_elements_to_render();
for(let poly of polys){
let shape = poly.GetFixtureList().GetShape();
let vertices = [];
for(let i = 0; i < shape.m_count; i++){
let world_pos = poly.GetWorldPoint(shape.m_vertices[i]);
vertices.push([world_pos.x, world_pos.y]);
}
strokeWeight(stroke_coef * 2/scale);
stroke(poly.color2);
let color1 = poly.color1;
if(poly == agent.agent_body.reference_head_object){
let rgb01 = hexToRgb(poly.color1).map(c => c / 255);
let rgb255 = color_agent_head(agent, rgb01, poly.color2)[0].map(c => Math.round(c * 255));
color1 = rgbToHex(rgb255);
}
drawPolygon(vertices, color1);
}
}
/**
* Draws the given lidars.
* @param lidars {Array}
* @param scale {number} - Scale of the environment
*/
function drawLidars(lidars, scale){
for(let i = 0; i < lidars.length; i++){
let lidar = lidars[i];
// Draws a red line representing the lidar
let vertices = [
[lidar.p1.x, lidar.p1.y],
[lidar.p2.x, lidar.p2.y]
];
strokeWeight(1/scale);
drawLine(vertices, "#FF0000");
}
}
/**
* Draws the different parts of the agent's observation.
* @param agent {Object}
* @param scale {number} - Scale of the environment
*/
function drawObservation(agent, scale){
// Draws a circle depending to the surface detected by the lidar and the fraction of the lidar
for(let i = 0; i < agent.lidars.length; i++) {
let lidar = agent.lidars[i];
if(lidar.fraction < 1){
if(lidar.is_water_detected){
noStroke();
fill(0, 50 + (1 - lidar.fraction) * 160, 150 + (1 - lidar.fraction) * 105);
circle(lidar.p2.x, VIEWPORT_H - lidar.p2.y, 5/scale);
}
else if(lidar.is_creeper_detected){
noStroke();
fill(0, 120 + (1 - lidar.fraction) * 135, 0);
circle(lidar.p2.x, VIEWPORT_H - lidar.p2.y, 5/scale);
}
else{
noStroke();
fill(0, 120 + (1 - lidar.fraction) * 135, 0);
circle(lidar.p2.x, VIEWPORT_H - lidar.p2.y, 5/scale);
}
}
}
// Draws a line corresponding to the agent's head angle
let head = agent.agent_body.reference_head_object;
let pos = head.GetPosition();
let angle = head.GetAngle();
let length = 2 * agent.agent_body.AGENT_WIDTH;
if(agent.morphology == "spider"){
length = agent.agent_body.AGENT_WIDTH / 2;
}
let vertices = [
[pos.x - length * Math.cos(angle), pos.y - length * Math.sin(angle)],
[pos.x + length * Math.cos(angle), pos.y + length * Math.sin(angle)]
];
let color;
if(Math.abs(angle) > Math.PI / 10){
color = "#FF0000";
}
else if(Math.abs(angle) > Math.PI / 40){
color = "#F4BE18";
}
else{
color = "#00B400";
}
strokeWeight(2/scale);
drawLine(vertices, color);
// Draws an arrow corresponding to the agent's linear velocity
let vel = head.GetLinearVelocity().Length();
let x_pos;
let y_pos;
if(agent.morphology == "bipedal"){
x_pos = pos.x - agent.agent_body.AGENT_WIDTH;
y_pos = pos.y + agent.agent_body.AGENT_HEIGHT / 4;
}
else if(agent.morphology == "spider"){
x_pos = pos.x - agent.agent_body.AGENT_WIDTH / 2;
y_pos = pos.y + agent.agent_body.AGENT_HEIGHT / 4;
}
else if(agent.morphology == "chimpanzee"){
x_pos = pos.x - agent.agent_body.AGENT_WIDTH;
y_pos = pos.y + agent.agent_body.AGENT_HEIGHT / 3;
}
else if(agent.morphology == "fish"){
x_pos = pos.x - agent.agent_body.AGENT_WIDTH;
y_pos = pos.y + agent.agent_body.AGENT_HEIGHT * 1.5;
}
vertices = [
[x_pos, y_pos],
[x_pos + vel / 2, y_pos]
];
strokeWeight(2/scale);
drawLine(vertices, "#0070FF");
vertices = [
[x_pos + vel / 2 - 0.25, y_pos + Math.sin(Math.PI / 12)],
[x_pos + vel / 2, y_pos]
]
drawLine(vertices, "#0070FF");
vertices = [
[x_pos + vel / 2 - 0.25, y_pos - Math.sin(Math.PI / 12)],
[x_pos + vel / 2, y_pos]
]
drawLine(vertices, "#0070FF");
}
/**
* Draws the agent's step and episodic reward.
* @param agent {Object}
* @param scale {number} - Scale of the environment
*/
function drawReward(agent, scale){
// Text reward
if(window.game.rewards.length > 0){
let dict = window.lang_dict[window.get_language()]['advancedOptions'];
let pos = agent.agent_body.reference_head_object.GetPosition();
let x_pos;
let y_pos;
if(agent.morphology == "bipedal"){
x_pos = pos.x + agent.agent_body.AGENT_WIDTH * 3/2;
y_pos = pos.y + agent.agent_body.AGENT_HEIGHT;
}
else if(agent.morphology == "spider"){
x_pos = pos.x + agent.agent_body.AGENT_WIDTH / 2;
y_pos = pos.y + agent.agent_body.AGENT_HEIGHT * 3/2;
}
else if(agent.morphology == "chimpanzee"){
x_pos = pos.x + 8 * agent.agent_body.AGENT_WIDTH;
y_pos = pos.y - agent.agent_body.AGENT_HEIGHT;
}
else if(agent.morphology == "fish"){
x_pos = pos.x + 9 * agent.agent_body.AGENT_WIDTH;
y_pos = pos.y;
}
noStroke()
fill(0);
textSize(20/ scale);
textAlign(RIGHT);
text(dict['stepReward'] + " = ", x_pos, RENDERING_VIEWER_H - y_pos);
text(dict['totalReward'] + " = ", x_pos, RENDERING_VIEWER_H - (y_pos - 1));
let reward = window.game.rewards[window.game.rewards.length - 1][agent.id].toPrecision(3);
if(reward > 0.35){
fill("#00B400");
}
else if(reward > 0.15){
fill("#F4BE18");
}
else {
fill("#FF0000");
}
textAlign(LEFT);
text(reward, x_pos, RENDERING_VIEWER_H - y_pos);
let ep_reward = agent.episodic_reward.toPrecision(3);
if(ep_reward > 230){
fill("#00B400");
}
else {
fill(0);
}
text(ep_reward, x_pos, RENDERING_VIEWER_H - (y_pos - 1));
}
}
/**
* Draws the sky and the clouds
* @param env
*/
function drawSkyClouds(env){
push();
// Sky
background("#E6F0FF");
// Translation to scroll horizontally and vertically
translate(- env.scroll[0]/3, env.scroll[1]/3);
// Rescaling
scale(env.scale);
scale(env.zoom * 3/4);
// Translating so that the environment is always horizontally centered
translate(0, (1 - env.scale * env.zoom) * VIEWPORT_H/(env.scale * env.zoom));
translate(0, (env.zoom - 1) * (env.ceiling_offset)/env.zoom * 1/3);
// Clouds
for(let cloud of env.cloud_polys){
noStroke();
drawPolygon(cloud.poly, "#FFFFFF");
}
pop();
}
/**
* Draws all the bodies composing the terrain of the given environment.
* @param env {Object}
*/
function drawTerrain(env){
// Updates scroll to stay centered on the agent position
if(window.agent_followed != null){
env.set_scroll(window.agent_followed, null, null);
}
// Sky & clouds
drawSkyClouds(env);
// Translation to scroll horizontally and vertically
translate(- env.scroll[0], env.scroll[1]);
// Rescaling
scale(env.scale);
scale(env.zoom);
// Translating so that the environment is always horizontally centered
translate(0, (1 - env.scale * env.zoom) * VIEWPORT_H/(env.scale * env.zoom));
translate(0, (env.zoom - 1) * (env.ceiling_offset)/env.zoom * 1/3);
// Water
let vertices = [
[-RENDERING_VIEWER_W, -RENDERING_VIEWER_H],
[-RENDERING_VIEWER_W, env.water_y],
[2 * RENDERING_VIEWER_W, env.water_y],
[2 * RENDERING_VIEWER_W, -RENDERING_VIEWER_H]
];
noStroke();
drawPolygon(vertices, "#77ACE5");
// Draws all background elements
for(let i = 0; i < env.background_polys.length; i++) {
let poly = env.background_polys[i];
noStroke();
drawPolygon(poly.vertices, poly.color);
}
// Draws all terrain elements
for(let i = 0; i < env.terrain_bodies.length; i++) {
let poly = env.terrain_bodies[i];
let shape = poly.body.GetFixtureList().GetShape();
let vertices = [];
if(poly.type == "creeper"){
for(let i = 0; i < shape.m_count; i++){
let world_pos = poly.body.GetWorldPoint(shape.m_vertices[i]);
vertices.push([world_pos.x, world_pos.y]);
}
noStroke();
drawPolygon(vertices, poly.color1);
}
else{
let v1 = poly.body.GetWorldPoint(shape.m_vertex1);
let v2 = poly.body.GetWorldPoint(shape.m_vertex2);
vertices = [[v1.x, v1.y], [v2.x, v2.y]];
strokeWeight(1/env.scale);
drawLine(vertices, poly.color);
}
}
// Draws a flag on startpad
let flag_y1 = TERRAIN_HEIGHT;
let flag_y2 = flag_y1 + 90 / env.scale;
let flag_x = TERRAIN_STEP * 3;
vertices = [
[flag_x, flag_y1],
[flag_x, flag_y2]
]
drawLine(vertices, "#000000");
vertices = [
[flag_x, flag_y2],
[flag_x, flag_y2 - 20 / env.scale],
[flag_x + 40 / env.scale, flag_y2 - 10 / env.scale]
]
drawPolygon(vertices, "#E63300");
// Draws all assets
for(let asset of env.assets_bodies){
let shape = asset.body.GetFixtureList().GetShape();
let stroke_coef = asset.is_selected ? 2 : 1;
if(asset.type == "circle"){
let center = asset.body.GetWorldCenter();
strokeWeight(stroke_coef * 2/env.scale);
stroke(asset.color2);
fill(asset.color1);
circle(center.x, RENDERING_VIEWER_H - center.y, shape.m_radius * 2);
}
}
}
/**
* Draws a polygon in the canvas with the given vertices.
* @param vertices {Array}
* @param color {string}
*/
function drawPolygon(vertices, color){
fill(color);
beginShape();
for(let v of vertices){
vertex(v[0], VIEWPORT_H - v[1]);
}
endShape(CLOSE);
}
/**
* Draws a line in the canvas between the two vertices.
* @param vertices {Array}
* @param color {string}
*/
function drawLine(vertices, color){
stroke(color);
line(vertices[0][0], VIEWPORT_H - vertices[0][1], vertices[1][0], VIEWPORT_H - vertices[1][1]);
}