ClementRomac's picture
ClementRomac HF staff
Added interactive demo with some policies
09a6f7f
raw
history blame
27.1 kB
/* GLOBAL VARIABLES */
window.erasing_radius = 15;
window.asset_size = 8;
// Lists of points {x, y} composing the terrain shapes
window.ground = [];
window.ceiling = [];
// Lists of raw points {x, y} drawn by the user for the terrain shapes
window.terrain = {
ground: [],
ceiling: []
};
// Parameters to handle the alignment of the terrain to the startpad according to the situation
window.align_terrain = {
align: true,
ceiling_offset: null,
ground_offset: null,
smoothing: null
};
/* INIT FUNCTIONS */
/**
* Initializes the game.
* @param cppn_input_vector {Array} - 3-dimensional array that encodes the CPPN
* @param water_level {number}
* @param creepers_width {number}
* @param creepers_height {number}
* @param creepers_spacing {number}
* @param smoothing {number}
* @param creepers_type {boolean}
* @param ground {Array} - List of points {x, y} composing the ground
* @param ceiling {Array} - List of points {x, y} composing the ceiling
* @param align {Object}
* @param zoom {number} - Zoom to apply to the environment
* @param scroll {{x: number, y:number}} - Scroll to apply to the environment
*/
function init_game(cppn_input_vector, water_level, creepers_width, creepers_height, creepers_spacing,
smoothing, creepers_type, ground, ceiling, align, zoom=null, scroll=null) {
let agents = {
morphologies: [],
policies: [],
positions: []
}
// Pauses the game if it already exists and gets the information about the running agents
if(window.game != null){
window.game.pause();
agents.morphologies = [...window.game.env.agents.map(a => a.morphology)];
agents.policies = [...window.game.env.agents.map(a => a.policy)];
agents.positions = [...window.game.env.agents.map(agent => agent.agent_body.reference_head_object.GetPosition())];
}
window.game = new Game(agents, cppn_input_vector, water_level, creepers_width, creepers_height,
creepers_spacing, smoothing, creepers_type, ground, ceiling, align);
window.set_agent_selected(-1);
window.asset_selected = null;
if(zoom == null){
window.game.env.set_zoom(INIT_ZOOM);
}
else {
window.game.env.set_zoom(zoom);
}
if(scroll == null){
window.game.env.set_scroll(window.agent_selected, INIT_SCROLL_X, 0);
}
else{
window.game.env.set_scroll(window.agent_selected, scroll[0], scroll[1]);
}
window.game.env.render();
}
/**
* Indicates if the creepers type is 'Swingable' or not.
* @returns {boolean}
*/
function getCreepersType() {
return document.getElementById("creepersType").value == 'Swingable';
}
/**
* First function called after the code is entirely loaded.
* Loads the model of the CPPN, initializes the game by default, loads the default environmnent and starts the language selection.
* @returns {Promise<void>}
*/
async function onLoadInit() {
window.cppn_model = await tf.loadGraphModel('./js/CPPN/weights/same_ground_ceiling_cppn/tfjs_model/model.json');
window.init_default();
window.loadDefaultEnv();
window.langIntroSetUp();
}
// Calls onLoadInit() when all the files are loaded
window.addEventListener("load", onLoadInit, false);
/* IN-CANVAS MOUSE INTERACTIONS */
/**
* Converts the given position relative to the canvas to the environment scale.
* @param x_pos {number} - X-coordinate inside the canvas.
* @param y_pos {number} - Y-coordinate inside the canvas.
* @returns {{x: number, y: number}} - Position inside the environment.
*/
function convertPosCanvasToEnv(x_pos, y_pos){
let x = Math.max(-window.canvas.width * 0.01, Math.min(x_pos, window.canvas.width * 1.01));
let y = Math.max(0, Math.min(y_pos, window.canvas.height));
x += window.game.env.scroll[0];
y = -(y - window.game.env.scroll[1]);
x = x / (window.game.env.scale * window.game.env.zoom);
y = y / (window.game.env.scale * window.game.env.zoom);
y += (1 - window.game.env.scale * window.game.env.zoom) * RENDERING_VIEWER_H/(window.game.env.scale * window.game.env.zoom)
+ (window.game.env.zoom - 1) * (window.game.env.ceiling_offset)/window.game.env.zoom * 1/3 + RENDERING_VIEWER_H;
return {x: x, y: y};
}
/**
* Converts the given position relative to the environment to the canvas scale.
* @param x_pos {number} - X-coordinate inside the environment.
* @param y_pos {number} - Y-coordinate inside the environment.
* @returns {{x: number, y: number}} - Position inside the canvas.
*/
function convertPosEnvToCanvas(x_pos, y_pos){
let x = x_pos * window.game.env.scale * window.game.env.zoom - window.game.env.scroll[0];
let y = window.game.env.scroll[1] - (y_pos - RENDERING_VIEWER_H) * window.game.env.scale * window.game.env.zoom
+ (1 - window.game.env.scale * window.game.env.zoom) * RENDERING_VIEWER_H
+ (window.game.env.zoom - 1) * window.game.env.ceiling_offset * window.game.env.scale * 1/3;
return {x: x, y: y};
}
/**
* Checks if the given position is inside the given body.
* Used for clicking on assets.
* @param pos {{x: number, y: number}}
* @param body {b2Body} - A Box2D body
* @returns {boolean}
*/
function isPosInsideBody(pos, body){
let shape = body.GetFixtureList().GetShape();
if(shape.m_type == b2.Shape.e_circle){
let center = body.GetWorldCenter();
return Math.pow(center.x - pos.x, 2) + Math.pow(center.y - pos.y, 2) <= Math.pow(shape.m_radius, 2);
}
}
/**
* Handles actions when mouse is pressed.
*/
function mousePressed(){
// Hides all the tooltips when mouse pressed
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach((el, index) => {
let tooltip = bootstrap.Tooltip.getInstance(el);
tooltip.hide();
});
// Case mouse is pressed inside the canvas
if(mouseX >= 0 && mouseX <= window.canvas.width
&& mouseY >= 0 && mouseY <= window.canvas.height){
// Stores the current position of the mouse, used when dragging
window.prevMouseX = mouseX;
window.prevMouseY = mouseY;
// Creates a circle asset at the mouse position and render the environment
if(window.is_drawing_circle()){
let mousePos = convertPosCanvasToEnv(mouseX, mouseY);
window.game.env.create_circle_asset(mousePos, window.asset_size * 2 / window.game.env.scale);
if(window.agent_selected != null){
window.agent_selected.is_selected = false;
window.set_agent_selected(-1);
}
window.game.env.render();
}
// Handles agents and assets selection
else if(!window.is_drawing()){
let mousePos = convertPosCanvasToEnv(mouseX, mouseY);
// Selects an agent in the canvas if the mouse is clicked over its body
let one_agent_touched = false;
for(let i = 0; i < window.game.env.agents.length; i++){
let agent = window.game.env.agents[i];
// Checks if the agent is touched by the mouse
let is_agent_touched = agent.agent_body.isPosInside(mousePos);
// If the agent is touched and not selected yet, it is now selected and all other agents are deselected
if(is_agent_touched){
one_agent_touched = true;
if(!agent.is_selected) {
agent.is_selected = true;
window.set_agent_selected(i);
for (let other_agent of window.game.env.agents) {
if (other_agent != agent) {
other_agent.is_selected = false;
}
}
}
break;
}
// If the agent is not touched it is deselected
else {
agent.is_selected = false;
}
}
// If no agent is touched, the selected agent is set to null
if(!one_agent_touched && window.agent_selected != null){
window.set_agent_selected(-1);
}
// Selects an asset in the canvas if the mouse is clicked over its body and no agent has been touched
if(!one_agent_touched){
let one_asset_touched = false;
for(let asset of window.game.env.assets_bodies){
// Checks if the asset is touched by the mouse
let is_asset_touched = isPosInsideBody(mousePos, asset.body);
// If the asset is touched and not selected yet, it is now selected and all other assets are deselected
if(is_asset_touched){
one_asset_touched = true;
if(!asset.is_selected){
asset.is_selected = true;
window.asset_selected = asset;
for(let other_asset of window.game.env.assets_bodies){
if(other_asset != asset){
other_asset.is_selected = false;
}
}
break;
}
}
// If the asset is not touched it is deselected
else if(!is_asset_touched){
asset.is_selected = false;
}
}
// If no asset is touched, the selected asset is set to null
if(!one_asset_touched && window.asset_selected != null){
window.asset_selected = null;
}
}
window.game.env.render();
}
}
}
// Handles clicks outside canvas when drawing (deselect drawing buttons)
document.addEventListener('mousedown', (event) => {
if(window.is_drawing() || window.is_drawing_circle()){
let canvas_id = "#" + window.canvas.canvas.id;
// Elements that can be clicked without deselecting drawing buttons: canvas + ground, ceiling, erase buttons
let authorized_elements = [
document.querySelector(canvas_id),
document.querySelector('#drawGroundButton'),
document.querySelector('#drawCeilingButton'),
document.querySelector('#eraseButton')
];
// If
if(authorized_elements.indexOf(event.target) == -1) {
window.deselectDrawingButtons();
}
}
});
/**
* Handles actions when mouse is dragged.
* @returns {boolean}
*/
function mouseDragged(){
// Case mouse is dragged inside the canvas
if(mouseX >= 0 && mouseX <= window.canvas.width
&& mouseY >= 0 && mouseY <= window.canvas.height) {
// DRAWING
if(window.is_drawing()) {
// Gets the position of the mouse in the environment scale
let mousePos = convertPosCanvasToEnv(mouseX, mouseY);
// Vertical offset to shift the drawing, trace and forbidden canvas in order to align them to the environment
let y_offset = SCROLL_Y_MAX - window.game.env.scroll[1];
// Drawing ground to the right of the terrain startpad
if(window.is_drawing_ground() && mousePos.x > (INITIAL_TERRAIN_STARTPAD - 1) * TERRAIN_STEP){
drawing_canvas.push();
drawing_canvas.stroke("#66994D");
drawing_canvas.strokeWeight(4);
// Draws a ground line between the current and previous positions of the mouse
drawing_canvas.line(mouseX, mouseY + y_offset, window.prevMouseX, window.prevMouseY + y_offset);
drawing_canvas.pop();
window.terrain.ground.push(mousePos);
}
// Drawing ceiling to the right of the terrain startpad
else if(window.is_drawing_ceiling() && mousePos.x > (INITIAL_TERRAIN_STARTPAD - 1) * TERRAIN_STEP){
drawing_canvas.push();
drawing_canvas.stroke("#808080");
drawing_canvas.strokeWeight(4);
// Draws a ceiling line between the current and previous positions of the mouse
drawing_canvas.line(mouseX, mouseY + y_offset, window.prevMouseX, window.prevMouseY + y_offset);
drawing_canvas.pop();
window.terrain.ceiling.push(mousePos);
}
// Erasing to the right of the terrain startpad
else if(window.is_erasing() && mousePos.x > INITIAL_TERRAIN_STARTPAD * TERRAIN_STEP){
// Draws a circle trace at the mouse position to show the erasing radius
trace_canvas.clear();
trace_canvas.noStroke();
trace_canvas.fill(255);
trace_canvas.circle(mouseX, mouseY + y_offset, window.erasing_radius * 2);
// Removes the points that are within the circle's radius from the ground and ceiling lists
window.terrain.ground = window.terrain.ground.filter(function(point, index, array){
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);
});
window.terrain.ceiling = window.terrain.ceiling.filter(function(point, index, array){
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);
});
// Erases the drawing canvas inside the circle's radius
drawing_canvas.erase();
drawing_canvas.circle(mouseX, mouseY + y_offset, window.erasing_radius * 2);
drawing_canvas.noErase();
}
// Dragging to scroll
else{
cursor(MOVE);
window.game.env.set_scroll(null, window.game.env.scroll[0] + window.prevMouseX - mouseX, window.game.env.scroll[1] + mouseY - prevMouseY);
// Re-draws the terrain shapes according to the new scroll
window.refresh_drawing();
y_offset = SCROLL_Y_MAX - window.game.env.scroll[1];
}
// Renders the environment and displays the off-screen canvas on top of it
window.game.env.render();
image(drawing_canvas, 0, -y_offset);
image(trace_canvas, 0, -y_offset);
image(forbidden_canvas, 0, -y_offset);
}
// DRAGGING
else{
cursor(MOVE);
// Dragging an agent
for (let agent of window.game.env.agents) {
// Drags the selected agent
if (agent.is_selected) {
// Computes the terrain's length according to the agent's morphology
let terrain_length;
if (agent.agent_body.body_type == BodyTypesEnum.CLIMBER) {
terrain_length = window.game.env.terrain_ceiling[window.game.env.terrain_ceiling.length - 1].x;
}
else if (agent.agent_body.body_type == BodyTypesEnum.WALKER) {
terrain_length = window.game.env.terrain_ground[window.game.env.terrain_ground.length - 1].x;
}
else if(agent.agent_body.body_type == BodyTypesEnum.SWIMMER){
terrain_length = Math.max(window.game.env.terrain_ground[window.game.env.terrain_ground.length - 1].x,
window.game.env.terrain_ceiling[window.game.env.terrain_ceiling.length - 1].x);
}
// Gets the mouse position inside the environment and clamps it horizontally to the edges of the terrain
let mousePos = convertPosCanvasToEnv(mouseX, mouseY);
let x = Math.max(0.02, Math.min(0.98, mousePos.x / terrain_length)) * terrain_length;
// Sets the position of the agent to the mouse position
window.game.env.set_agent_position(agent, x, mousePos.y);
window.game.env.render();
window.is_dragging_agent = true;
break;
}
}
// Dragging an asset
for(let asset of window.game.env.assets_bodies){
// Drags the selected asset
if (asset.is_selected && !window.is_dragging_agent) {
let terrain_length = Math.max(window.game.env.terrain_ground[window.game.env.terrain_ground.length - 1].x,
window.game.env.terrain_ceiling[window.game.env.terrain_ceiling.length - 1].x);
// Gets the mouse position inside the environment and clamps it horizontally to the edges of the terrain
let mousePos = convertPosCanvasToEnv(mouseX, mouseY);
mousePos.x = Math.max(0.02, Math.min(0.98, mousePos.x / terrain_length)) * terrain_length;
// Sets the position of the asset to the mouse position
window.game.env.set_asset_position(asset, mousePos);
window.game.env.render();
window.is_dragging_asset = true;
}
}
// Dragging to scroll
if(!window.is_dragging_agent && !window.is_dragging_asset){
// Scrolling manually cancels agent following
if(window.agent_followed != null){
window.set_agent_followed(-1);
}
window.game.env.set_scroll(null, window.game.env.scroll[0] + window.prevMouseX - mouseX, window.game.env.scroll[1] + mouseY - prevMouseY);
window.game.env.render();
}
}
}
// Dragging an agent horizontally out of canvas
else if(window.is_dragging_agent
&& mouseY >= 0 && mouseY < window.canvas.height){
if(mouseX < 0){
window.dragging_side = "left";
}
else if(mouseX > window.canvas.width){
window.dragging_side = "right";
}
cursor(MOVE);
// Dragging an agent
for (let agent of window.game.env.agents) {
// Drags the selected agent
if (agent.is_selected) {
// Scrolls horizontally according to the dragging side to follow the agent
window.game.env.set_scroll(null);
// Computes the terrain's length according to the agent's morphology
let terrain_length;
if (agent.agent_body.body_type == BodyTypesEnum.CLIMBER) {
terrain_length = window.game.env.terrain_ceiling[window.game.env.terrain_ceiling.length - 1].x;
}
else if (agent.agent_body.body_type == BodyTypesEnum.WALKER) {
terrain_length = window.game.env.terrain_ground[window.game.env.terrain_ground.length - 1].x;
}
else if(agent.agent_body.body_type == BodyTypesEnum.SWIMMER){
terrain_length = Math.max(window.game.env.terrain_ground[window.game.env.terrain_ground.length - 1].x,
window.game.env.terrain_ceiling[window.game.env.terrain_ceiling.length - 1].x);
}
// Gets the mouse position inside the environment and clamps it horizontally to the edges of the terrain
let mousePos = convertPosCanvasToEnv(mouseX, mouseY);
let x = Math.max(0.02, Math.min(0.98, mousePos.x / terrain_length)) * terrain_length;
// Sets the position of the agent to the mouse position
window.game.env.set_agent_position(agent, x, mousePos.y);
window.game.env.render();
break;
}
}
// Prevents default behaviour when dragging the mouse
return false;
}
window.prevMouseX = mouseX;
window.prevMouseY = mouseY;
}
/**
* Handles actions when mouse is released.
*/
function mouseReleased(){
cursor();
window.is_dragging_agent = false;
window.is_dragging_asset = false;
window.dragging_side = null;
}
/**
* Handles actions when mouse is moved.
*/
function mouseMoved(){
// Draws the trace of the circle asset at the mouse position
if(window.is_drawing_circle()){
trace_canvas.clear();
if(mouseX >= 0 && mouseX <= window.canvas.width
&& mouseY >= 0 && mouseY <= window.canvas.height) {
trace_canvas.noStroke();
trace_canvas.fill(136, 92, 0, 180);
trace_canvas.circle(mouseX, mouseY + SCROLL_Y_MAX - window.game.env.scroll[1], window.asset_size * 4 * window.game.env.zoom);
}
window.game.env.render();
image(trace_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
}
// Draws the trace of the eraser at the mouse position
else if (window.is_erasing()) {
trace_canvas.clear();
if (mouseX >= 0 && mouseX <= window.canvas.width
&& mouseY >= 0 && mouseY <= window.canvas.height) {
trace_canvas.noStroke();
trace_canvas.fill(255, 180);
trace_canvas.circle(mouseX, mouseY + SCROLL_Y_MAX - window.game.env.scroll[1], window.erasing_radius * 2);
}
window.game.env.render();
image(drawing_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
image(trace_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
image(forbidden_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
}
}
/**
* Handles actions when a mouse wheel event is detected (actual mouse wheel or touchpad).
* @param event {WheelEvent}
* @returns {boolean}
*/
function mouseWheel(event){
if(mouseX >= 0 && mouseX <= window.canvas.width
&& mouseY >= 0 && mouseY <= window.canvas.height) {
trace_canvas.clear();
// Resizes circle asset radius
if(window.is_drawing_circle()){
window.asset_size = Math.max(3, Math.min(window.asset_size - event.delta / 100, 30));
trace_canvas.noStroke();
trace_canvas.fill(136, 92, 0, 180);
trace_canvas.circle(mouseX, mouseY + SCROLL_Y_MAX - window.game.env.scroll[1], window.asset_size * 4 * window.game.env.zoom);
window.game.env.render();
image(trace_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
}
// Resizes erasing radius
else if(window.is_erasing()){
window.erasing_radius = Math.max(5, Math.min(window.erasing_radius - event.delta / 100, 30));
trace_canvas.noStroke();
trace_canvas.fill(255, 180);
trace_canvas.circle(mouseX, mouseY + SCROLL_Y_MAX - window.game.env.scroll[1], window.erasing_radius * 2);
window.game.env.render();
image(drawing_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
image(trace_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
image(forbidden_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
}
// Zooms in or out
else {
window.game.env.set_zoom(window.game.env.zoom - event.delta / 2000);
// TODO: scroll on the mouse position
window.game.env.set_scroll(null, window.game.env.scroll[0], window.game.env.scroll[1]);
// If drawing mode, re-draws the terrain shapes according to the new zoom
if(window.is_drawing()){
window.refresh_drawing();
window.game.env.render();
image(drawing_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
image(forbidden_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
}
else{
window.game.env.render();
}
}
// Prevents default behaviour for mouse wheel events inside the canvas
return false;
}
}
/**
* Handles actions when a key is pressed.
* @returns {boolean}
*/
function keyPressed(){
// Deletes the agent or asset selected when pressing the delete key
if(keyCode == DELETE){
if(window.agent_selected != null){
window.delete_agent(agent_selected);
window.agent_selected(null);
return false;
}
else if(window.asset_selected != null){
window.game.env.delete_asset(window.asset_selected);
window.asset_selected = null;
window.game.env.render();
return false;
}
}
}
/**
* Handles actions when the window is resized.
*/
function windowResized(){
let canvas_container = document.querySelector('#canvas_container');
// Recomputes RENDERING_VIEWER_W, INIT_ZOOM and THUMBNAIL_ZOOM
RENDERING_VIEWER_W = canvas_container.offsetWidth;
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);
// Resizes the main canvas
resizeCanvas(RENDERING_VIEWER_W, RENDERING_VIEWER_H);
drawing_canvas.resizeCanvas(RENDERING_VIEWER_W + SCROLL_X_MAX, RENDERING_VIEWER_H + 2 * SCROLL_Y_MAX);
trace_canvas.resizeCanvas(RENDERING_VIEWER_W + SCROLL_X_MAX, RENDERING_VIEWER_H + 2 * SCROLL_Y_MAX);
forbidden_canvas.resizeCanvas(RENDERING_VIEWER_W + SCROLL_X_MAX, RENDERING_VIEWER_H + 2 * SCROLL_Y_MAX);
// Generates the terrain from the drawing
if(is_drawing()){
window.refresh_drawing();
window.game.env.render();
image(drawing_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
image(forbidden_canvas, 0, -SCROLL_Y_MAX + window.game.env.scroll[1]);
}
// Re-initializes the environment
else{
window.init_default();
}
}
window.downloadObjectAsJson = (exportObj, exportName) => {
let dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportObj));
let downloadAnchorNode = document.createElement('a');
downloadAnchorNode.setAttribute("href", dataStr);
downloadAnchorNode.setAttribute("download", exportName + ".json");
document.body.appendChild(downloadAnchorNode); // required for firefox
downloadAnchorNode.click();
downloadAnchorNode.remove();
}
window.strUcFirst = (a) => {
return (a+'').charAt(0).toUpperCase()+a.substr(1);
}
window.draw_forbidden_area = () => {
forbidden_canvas.clear();
forbidden_canvas.stroke("#FF0000");
forbidden_canvas.strokeWeight(3);
forbidden_canvas.fill(255, 50, 0, 75);
let w = convertPosEnvToCanvas((INITIAL_TERRAIN_STARTPAD - 1) * TERRAIN_STEP, 0).x;
forbidden_canvas.rect(0, 0, w, RENDERING_VIEWER_H + 2 * SCROLL_Y_MAX);
}