Please enter a name and a description for the current environment.
+
+
+
+
This environment will be saved in your collection of custom environments so that you could reload it later or download it to share it.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
To begin you can select one of the following environments to load it into the simulation.
+
+
+
+
+
+
+
+
In this section you can store your own custom environments by saving them thanks to the button above or by uploading them from a JSON file.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Terrain Generation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Here you can draw your own parkour!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Select the Ground or Ceiling button to start drawing the corresponding terrain shape with the mouse.
+ Be careful not to draw more than one line at different heights if you want the result to be optimal.
+ You can use the Erase button if you need to correct your drawing or the Clear one to clear all your drawing.
+ When you are satisfied with the result, just click the Generate Terrain button.
+
+
+
+
+
+
+
+
+ You can also use these three sliders to generate the terrain shapes automatically.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ General parameters
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Creepers
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Rendering Options
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Assets
+
Here you can find several types of assets, which are objects that you can add to the simulation using the mouse.
+
+
+
+ More assets coming soon...
+
+
+
+
+
+
+
+
+
Purpose of the demo
+
+
+
+ The goal of this demo is to showcase the challenge of generalization to unknown tasks
+ for Deep Reinforcement Learning (DRL) agents.
+
+
+
+ DRL is a machine learning approach for teaching virtual agents
+ how to solve tasks by combining Reinforcement Learning and Deep Learning methods.
+ This approach has been used for a diverse set of applications including robotics (e.g. Solving Rubik's Cube),
+ video games and boardgames (e.g. AlphaGo).
+
+
+
+ In this demo, all the agents have been autonomously trained to learn an efficient behaviour to navigate through a 2D environment,
+ combining different methods so that they can be able to generalize their behaviour to never-seen-before situations.
+
+
+
+ The demo provides different tools to customize the environment in order to test and challenge the
+ robustness of the agents on different situations.
+
+
+
+
+
Reinforcement Learning
+
+
+
+ Reinforcement Learning (RL) is the study of agents and how they learn by trial and error.
+ The main idea is to reward or punish an agent according to the actions it takes in order to teach it an efficient behavior to reach an objective.
+
+ The RL approaches generally feature an agent which evolves and interacts with a world.
+ At each interaction step, the agent sees a partial observation of the current state of the environment and decides of an action to take.
+ Each action taken by the agent changes the state of the world.
+ The agent also receives a reward signal at each step, that indicates how good or bad the current state is
+ according to the objective the agent has to reach.
+
+
+
+
+
+ The diagram on the right presents this interaction process between the agent and the environment,
+ with the different information they exchange at each step.
+
+ Maximizing the reward over steps is a way for the agent to learn a behaviour, also called policy,
+ to achieve its objective.
+
+
+
+
+
+
+
+
+
Deep RL
+
+
+
+ In order to remember and improve the actions taken by the agent, DRL algorithms utilizes artificial neural networks.
+ With training, these neural networks are able to learn to predict an optimal action to take at each step from the observation received,
+ and relying on all the observations and rewards previously received after each action during training.
+ Thanks to this, DRL algorithms are able to produce behaviours that are very effective in situations similar to those they were trained on.
+
+
+
+
+
+
+
+ However, in real-world applications, the environment rarely remains still and frequently evolves. Therefore one would
+ want DRL agents to be able to generalize their behaviour to previously unseen changes of the environment so that
+ they can adapt to a large range of situations.
+
+
+
+
Automatic Curriculum Learning
+
+
+
+ One solution to handle this challenge is to train DRL agents on procedurally generated environments.
+
+ Procedural generation is a method of automatically creating environments according to some parameters.
+ Using this method, DRL agents can be trained on a very wide range of environments, hence allowing them
+ to generalize their behaviour to more different situations.
+
+
+
+ However, randomly generating environments during training implies the risk to generate environments that are too difficult or too easy to resolve
+ for the agents, preventing them to continuously learn in an efficient way.
+
+ Therefore, one would need smarter training strategies that propose relevant environments tailored to the current learning progress of the student (DRL agent).
+ This method is called Automatic Curriculum Learning (ACL) and is embodied by a teacher algorithm which is trained to learn to generate
+ the most relevant environments throughout the entire training process according to the student performances.
+
+ This way, the teacher proposes easy environments to the student at the beginning and gradually increases the difficulty
+ and the diversity of the tasks in order to guarantee that the student is progressing while not always facing the same situation or forgetting what it has already learned.
+
+
+
+
About the demo
+
+
+
+ In this demo, all the available agents were trained using Soft Actor Critic
+ as the DRL student algorithm alongside different ACL teacher algorithms such as ALP-GMM.
+
+ They successfully learned efficient behaviours to move through the environment and to generalize to never-seen-before situations.
+
+
+
+ The physics of the simulation are supported by box2d.js
+ which is a direct port of the Box2D physics engine to JavaScript.
+
+ The pre-trained policies (agents behaviours) are loaded in the browser thanks to TensorFlow.js.
+
+
+
+
Credits
+
+
+
+ This demo was designed by Paul Germon as part of an internship within Flowers
+ research team at Inria. This internship was monitored by Rémy Portelas and Clément Romac,
+ and supervised by Pierre-Yves Oudeyer. Special thanks to Nikita Melkozerov for its very helpful contribution.
+ Recommended citation format:
+
+ @misc{germon2021demo,
+ title={Interactive Deep Reinforcement Learning Demo},
+ author={Germon, Paul and Romac, Clément and Portelas, Rémy and Pierre-Yves, Oudeyer},
+ url={https://developmentalsystems.org/Interactive_DeepRL_Demo/},
+ year={2021}
+ }
+
+
+
+
+
The code of this demo is open-source and can be found on this github repository.
+
The code of the environment and agents is adapted from the TeachMyAgent benchmark's Python code to JavaScript.
+
+
+
+
References
+
+
+
+
[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:
+ Solving Rubik's Cube with a Robot Hand (2019). https://arxiv.org/abs/1910.07113
+
[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). https://doi.org/10.1038/nature16961
+
[3] Portelas, R., Colas, C., Weng, L., Hofmann, K., & Oudeyer, P. Y. (2020). Automatic curriculum learning for deep rl: A short survey (2020). https://arxiv.org/abs/2003.04664
+
[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. In International conference on machine learning (pp. 1861-1870). PMLR https://arxiv.org/abs/1801.01290
+
[5] Portelas, R., Colas, C., Hofmann, K., & Oudeyer, P. Y. (2020, May). Teacher algorithms for curriculum learning of deep rl in continuously parameterized environments. In Conference on Robot Learning (pp. 835-853). PMLR. https://arxiv.org/abs/1910.07224
+
[6] Romac, C., Portelas, R., Hofmann, K., & Oudeyer, P. Y. (2021). TeachMyAgent: a Benchmark for Automatic Curriculum Learning in Deep RL. https://arxiv.org/abs/2103.09815
+
+
+
+
+
+
+
+
+
+
Add an agent
+
Here you can add an agent to the simulation with the morphology of your choice.
+
+
+
+
+
+
+
+
+
+
diff --git a/index.js b/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..109fd3133051b93143a26da59a5d3664c586128a
--- /dev/null
+++ b/index.js
@@ -0,0 +1,661 @@
+/* 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}
+ */
+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);
+}
diff --git a/js/Box2D_dynamics/climbing_dynamics.js b/js/Box2D_dynamics/climbing_dynamics.js
new file mode 100644
index 0000000000000000000000000000000000000000..66c14f8ce29f43bb668d0415b81da888dfe6a30f
--- /dev/null
+++ b/js/Box2D_dynamics/climbing_dynamics.js
@@ -0,0 +1,175 @@
+/**
+ * @classdesc Class that handles the climbing dynamics.
+ */
+class ClimbingDynamics {
+ constructor(){};
+
+ /**
+ * Prepares the agent's sensors to grasp or release according to the actions.
+ * @param actions {Array} - Actions of the agent
+ * @param agent_body {Object} - Climber morphology
+ * @param world - {Object} - Box2D world
+ */
+ before_step_climbing_dynamics(actions, agent_body, world){
+ for(let i = 0; i < agent_body.sensors.length; i++){
+ let action_to_check = actions[actions.length - i - 1];
+ let sensor_to_check = agent_body.sensors[agent_body.sensors.length - i - 1];
+ if(action_to_check > 0){ // Check whether the sensor should grasp or release
+ sensor_to_check.GetUserData().ready_to_attach = true;
+ }
+ else {
+ sensor_to_check.GetUserData().ready_to_attach = false;
+ if(sensor_to_check.GetUserData().has_joint){ // if released and it had a joint => destroys it
+ sensor_to_check.GetUserData().has_joint = false;
+
+ // Gets a list of all the joints of the sensor body
+ let sensor_joints = [];
+ let _joint = sensor_to_check.GetJointList();
+ while(_joint != null){
+ sensor_joints.push(_joint.joint);
+ _joint = _joint.next;
+ }
+ // Finds the index of the first revolute joint
+ const isRevolute = (s) => s.m_type == b2.Joint.e_revoluteJoint;
+ let idx_to_destroy = sensor_joints.findIndex(isRevolute);
+ if(idx_to_destroy != -1){
+ world.DestroyJoint(sensor_joints[idx_to_destroy]);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates joints between sensors ready to grasp if collision with graspable area was detected
+ * @param contact_detector {Object}
+ * @param world {Object} - Box2D world
+ */
+ after_step_climbing_dynamics(contact_detector, world){
+ // Adds climbing joints if needed
+ for(let i = 0; i < contact_detector.contact_dictionaries.sensors.length; i++){
+ let sensor = contact_detector.contact_dictionaries.sensors[i];
+ if(contact_detector.contact_dictionaries.bodies[i].length > 0
+ && sensor.GetUserData().ready_to_attach
+ && !sensor.GetUserData().has_joint){
+ let other_bodies = [...contact_detector.contact_dictionaries.bodies[i]];
+ for(let other_body of other_bodies){
+
+ // Checks if still overlapping after solver
+ // Super coarse yet fast way, mainly useful for creepers
+ let other_body_shape = other_body.GetFixtureList().GetShape();
+ let x_values = [];
+ let y_values = [];
+ if(other_body_shape.m_type == b2.Shape.e_polygon){
+ for(let i = 0; i < other_body_shape.m_count; i++) {
+ x_values.push(other_body.GetWorldPoint(other_body_shape.m_vertices[i]).x);
+ y_values.push(other_body.GetWorldPoint(other_body_shape.m_vertices[i]).y);
+ }
+ }
+ else if(other_body_shape.m_type == b2.Shape.e_edge){
+ x_values = [other_body_shape.m_vertex1.x, other_body_shape.m_vertex2.x];
+ y_values = [other_body_shape.m_vertex1.y, other_body_shape.m_vertex2.y];
+ }
+
+ let radius = sensor.GetFixtureList().GetShape().m_radius + 0.01;
+ let sensor_world_center = sensor.GetWorldCenter();
+
+ if(sensor_world_center.x + radius > Math.min(...x_values)
+ && sensor_world_center.x - radius < Math.max(...x_values)
+ && sensor_world_center.y + radius > Math.min(...y_values)
+ && sensor_world_center.y - radius < Math.max(...y_values)){
+
+ let rjd = new b2.RevoluteJointDef();
+ rjd.Initialize(sensor, other_body, sensor_world_center);
+ let joint = world.CreateJoint(rjd);
+ joint.SetUserData(new CustomBodyUserData(false, false, "grip"));
+ joint.GetBodyA().GetUserData().joint = joint;
+ sensor.GetUserData().has_joint = true;
+ break;
+ }
+ else {
+ // Removes other_body from the list of bodies in contact with the sensor
+ let sensor_idx = contact_detector.contact_dictionaries.sensors.indexOf(sensor);
+ if(sensor_idx != -1){
+ let other_idx = contact_detector.contact_dictionaries.bodies[sensor_idx].indexOf(other_body);
+ contact_detector.contact_dictionaries.bodies[sensor_idx].splice(other_idx, 1);
+
+ if(contact_detector.contact_dictionaries.bodies[sensor_idx].length == 0){
+ sensor.GetUserData().has_contact = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * @classdesc Stores contacts between sensors and graspable surfaces in a dictionaries associated to the sensor.
+ * @constructor
+ */
+function ClimbingContactDetector() {
+ b2.ContactListener.call(this);
+ this.contact_dictionaries = {
+ sensors: [],
+ bodies: []
+ };
+}
+
+ClimbingContactDetector.prototype = Object.create(b2.ContactListener.prototype);
+ClimbingContactDetector.prototype.constructor = ClimbingContactDetector;
+ClimbingContactDetector.prototype.BeginContact = function (contact) {
+ let bodies = [contact.GetFixtureA().GetBody(), contact.GetFixtureB().GetBody()];
+ for(let i = 0; i < bodies.length; i++){
+ let body = bodies[i];
+ if(body.GetUserData().object_type == CustomUserDataObjectTypes.BODY_SENSOR
+ && body.GetUserData().check_contact){
+ let other_body = bodies[(i + 1) % 2];
+ if(other_body.GetUserData().object_type == CustomUserDataObjectTypes.GRIP_TERRAIN
+ || other_body.GetUserData().object_type == CustomUserDataObjectTypes.SENSOR_GRIP_TERRAIN){
+ body.GetUserData().has_contact = true;
+ let idx = this.contact_dictionaries.sensors.indexOf(body);
+ if(idx != -1){
+ this.contact_dictionaries.bodies[idx].push(other_body);
+ }
+ else{
+ this.contact_dictionaries.sensors.push(body);
+ this.contact_dictionaries.bodies.push([other_body]);
+ }
+ }
+ else{
+ return;
+ }
+ }
+ }
+};
+
+ClimbingContactDetector.prototype.EndContact = function (contact){
+ let bodies = [contact.GetFixtureA().GetBody(), contact.GetFixtureB().GetBody()];
+ for(let i = 0; i < bodies.length; i++) {
+ let body = bodies[i];
+ let other_body = bodies[(i + 1) % 2];
+ if(body.GetUserData().object_type == CustomUserDataObjectTypes.BODY_SENSOR &&
+ body.GetUserData().check_contact && body.GetUserData().has_contact){
+ let body_idx = this.contact_dictionaries.sensors.indexOf(body);
+ if (body_idx != -1) {
+ let other_idx = this.contact_dictionaries.bodies[body_idx].indexOf(other_body);
+ if(other_idx != -1){
+ this.contact_dictionaries.bodies[body_idx].splice(other_idx, 1);
+ }
+
+ if(this.contact_dictionaries.bodies[body_idx].length == 0){
+ body.GetUserData().has_contact = false;
+ }
+ }
+
+ }
+ }
+};
+
+ClimbingContactDetector.prototype.Reset = function(){
+ this.contact_dictionaries = {
+ body: []
+ };
+};
\ No newline at end of file
diff --git a/js/Box2D_dynamics/contact_detector.js b/js/Box2D_dynamics/contact_detector.js
new file mode 100644
index 0000000000000000000000000000000000000000..17fc2b982fbe3e8381fcb2019d0521d861c29c41
--- /dev/null
+++ b/js/Box2D_dynamics/contact_detector.js
@@ -0,0 +1,88 @@
+/**
+ * @classdesc Class that derives from b2.ContactListener and that handles both water and climbing collisions.
+ * @param env {Object}
+ * @constructor
+ */
+function ContactDetector (env){
+ b2.ContactListener.call(this);
+ this.water_contact_detector = new WaterContactDetector();
+ this.climbing_contact_detector = new ClimbingContactDetector();
+ this.env = env;
+}
+
+ContactDetector.prototype = Object.create(b2.ContactListener.prototype);
+ContactDetector.prototype.constructor = ContactDetector;
+ContactDetector.prototype.BeginContact = function (contact){
+ let bodies = [contact.GetFixtureA().GetBody(), contact.GetFixtureB().GetBody()];
+ const anyWater = (body) => body.GetUserData().object_type == CustomUserDataObjectTypes.WATER;
+ const anySensor = (body) => body.GetUserData().object_type == CustomUserDataObjectTypes.BODY_SENSOR;
+ if(bodies.some(anyWater)){
+ this.water_contact_detector.BeginContact(contact);
+ }
+ else if(bodies.some(anySensor)){
+ this.climbing_contact_detector.BeginContact(contact);
+ }
+ else{
+ if(contact.GetFixtureA().IsSensor() || contact.GetFixtureB().IsSensor()){
+ return;
+ }
+
+ for(let i = 0; i < bodies.length; i++){
+ let body = bodies[i];
+ if(body.GetUserData().object_type == CustomUserDataObjectTypes.BODY_OBJECT && body.GetUserData().check_contact){
+ body.GetUserData().has_contact = true;
+ }
+ }
+ }
+};
+
+ContactDetector.prototype.EndContact = function (contact){
+ let bodies = [contact.GetFixtureA().GetBody(), contact.GetFixtureB().GetBody()];
+ const anyWater = (body) => body.GetUserData().object_type == CustomUserDataObjectTypes.WATER;
+ const anySensor = (body) => body.GetUserData().object_type == CustomUserDataObjectTypes.BODY_SENSOR;
+ if(bodies.some(anyWater)){
+ this.water_contact_detector.EndContact(contact);
+ }
+ else if(bodies.some(anySensor)){
+ this.climbing_contact_detector.EndContact(contact);
+ }
+ else {
+ for(let body of bodies){
+ if(body.GetUserData().object_type == CustomUserDataObjectTypes.BODY_OBJECT && body.GetUserData().check_contact){
+ body.GetUserData().has_contact = false;
+ }
+ }
+ }
+};
+
+ContactDetector.prototype.Reset = function (){
+ this.water_contact_detector.Reset();
+ this.climbing_contact_detector.Reset();
+}
+
+/**
+ * @classdesc Class that derives from b2.RayCastCallback and that handles lidars raycasts.
+ * @param agent_mask_filter
+ * @constructor
+ */
+function LidarCallback(agent_mask_filter){
+ b2.RayCastCallback.call(this);
+ this.agent_mask_filter = agent_mask_filter;
+ this.fixture = null;
+ this.is_water_detected = false;
+ this.is_creeper_detected = false;
+};
+
+LidarCallback.prototype = Object.create(b2.RayCastCallback.prototype);
+LidarCallback.prototype.constructor = LidarCallback;
+LidarCallback.prototype.ReportFixture = function (fixture, point, normal, fraction){
+ if((fixture.GetFilterData().categoryBits & this.agent_mask_filter) == 0){
+ return -1;
+ }
+
+ this.p2 = point;
+ this.fraction = fraction;
+ this.is_water_detected = fixture.GetBody().GetUserData().object_type == CustomUserDataObjectTypes.WATER;
+ this.is_creeper_detected = fixture.GetBody().GetUserData().object_type == CustomUserDataObjectTypes.SENSOR_GRIP_TERRAIN;
+ return fraction;
+}
diff --git a/js/Box2D_dynamics/water_dynamics.js b/js/Box2D_dynamics/water_dynamics.js
new file mode 100644
index 0000000000000000000000000000000000000000..09662a9f3cd939093bc115908f982db40fd65005
--- /dev/null
+++ b/js/Box2D_dynamics/water_dynamics.js
@@ -0,0 +1,244 @@
+/**
+ * @classdesc Class that handles the water dynamics.
+ */
+class WaterDynamics {
+ constructor(gravity, drag_mod=0.25, lift_mod=0.25, push_mod=0.05,
+ max_drag=2000, max_lift=500, max_push=13){
+ this.gravity = gravity;
+ this.drag_mod = drag_mod;
+ this.lift_mod = lift_mod;
+ this.max_drag = max_drag;
+ this.max_lift = max_lift;
+ this.push_mod = push_mod;
+ this.max_push = max_push;
+ }
+
+ compute_centroids(vectors){
+ let count = vectors.length;
+ console.assert(count >= 3);
+
+ let c = new b2.Vec2(0, 0);
+ let area = 0;
+ let ref_point = new b2.Vec2(0, 0);
+ let inv3 = 1/3;
+
+ for(let i = 0; i < count; i++){
+ // Triangle vertices
+ let p1 = ref_point;
+ let p2 = vectors[i];
+ let p3 = i + 1 < count ? vectors[i + 1] : vectors[0];
+
+ let e1 = b2.Vec2.Subtract(p2, p1);
+ let e2 = b2.Vec2.Subtract(p3, p1);
+ let d = b2.Cross_v2_v2(e1, e2);
+ let triangle_area = 0.5 * d;
+ area += triangle_area;
+
+ // Area weighted centroid
+ c.Add(b2.Vec2.Multiply(triangle_area * inv3, b2.Vec2.Add(p1, b2.Vec2.Add(p2, p3))));
+ }
+
+ if(area > b2.epsilon){
+ c.Multiply(1/area);
+ }
+ else{
+ area = 0;
+ }
+
+ return [c, area];
+ }
+
+ inside(cp1, cp2, p){
+ return (cp2.x - cp1.x) * (p.y - cp1.y) > (cp2.y - cp1.y) * (p.x - cp1.x);
+ }
+
+ intersection(cp1, cp2, s, e){
+ let dc = new b2.Vec2(cp1.x - cp2.x, cp1.y - cp2.y);
+ let dp = new b2.Vec2(s.x - e.x, s.y - e.y);
+ let n1 = cp1.x * cp2.y - cp1.y * cp2.x;
+ let n2 = s.x * e.y - s.y * e.x;
+ let n3 = 1.0 / (dc.x * dp.y - dc.y * dp.x);
+ return new b2.Vec2((n1 * dp.x - n2 * dc.x) * n3, (n1 * dp.y - n2 * dc.y) * n3);
+ }
+
+ find_intersection(fixture_A, fixture_B){
+ // TODO : assert polygons
+ let output_vertices = [];
+ let polygon_A = fixture_A.GetShape();
+ let polygon_B = fixture_B.GetShape();
+
+ // fill 'subject polygon' from fixture_A polygon
+ for(let i = 0; i < polygon_A.m_count; i++){
+ output_vertices.push(fixture_A.GetBody().GetWorldPoint(polygon_A.m_vertices[i]));
+ }
+
+ // fill 'clip polygon' from fixture_B polygon
+ let clip_polygon = [];
+ for(let i = 0; i < polygon_B.m_count; i++){
+ clip_polygon.push(fixture_B.GetBody().GetWorldPoint(polygon_B.m_vertices[i]));
+ }
+
+ let cp1 = clip_polygon[clip_polygon.length - 1];
+ for(let j = 0; j < clip_polygon.length; j++){
+ let cp2 = clip_polygon[j];
+ if(output_vertices.length == 0){
+ break;
+ }
+ let input_list = output_vertices.slice();
+ output_vertices = [];
+
+ let s = input_list[input_list.length - 1];
+ for(let i = 0; i < input_list.length; i++){
+ let e = input_list[i];
+ if(this.inside(cp1, cp2, e)){
+ if(!this.inside(cp1, cp2, s)){
+ output_vertices.push(this.intersection(cp1, cp2, s, e));
+ }
+ output_vertices.push(e);
+ }
+ else if(this.inside(cp1, cp2, s)){
+ output_vertices.push(this.intersection(cp1, cp2, s, e));
+ }
+ s = e;
+ }
+ cp1 = cp2
+ }
+ return [(output_vertices.length != 0), output_vertices];
+ }
+
+ calculate_forces(fixture_pairs){
+ for(let pair of fixture_pairs){
+ let density = pair[0].GetDensity();
+ let [has_intersection, intersection_points] = this.find_intersection(pair[0], pair[1]);
+ if(has_intersection){
+ let [centroid, area] = this.compute_centroids(intersection_points);
+
+ // apply buoyancy force
+ let displaced_mass = pair[0].GetDensity() * area;
+ let buoyancy_force = b2.Vec2.Multiply(displaced_mass, b2.Vec2.Negate(this.gravity));
+ pair[1].GetBody().ApplyForce(buoyancy_force, centroid, true);
+
+ // apply complex drag
+ for(let i = 0; i < intersection_points.length; i++) {
+ let v0 = intersection_points[i];
+ let v1 = intersection_points[(i + 1) % intersection_points.length];
+ let mid_point = b2.Vec2.Multiply(0.5, b2.Vec2.Add(v0, v1));
+
+ // DRAG
+ // find relative velocity between object and fluid at edge midpoint
+ let vel_dir = b2.Vec2.Subtract(pair[1].GetBody().GetLinearVelocityFromWorldPoint(mid_point),
+ pair[0].GetBody().GetLinearVelocityFromWorldPoint(mid_point));
+ let vel = vel_dir.Normalize();
+
+ let edge = b2.Vec2.Subtract(v1, v0);
+ let edge_length = edge.Normalize();
+ let normal = b2.Cross_f_v2(-1, edge);
+ let drag_dot = b2.Dot_v2_v2(normal, vel_dir);
+ if(drag_dot >= 0){ // normal points backwards - this is not a leading edge
+ // apply drag
+ let drag_mag = drag_dot * this.drag_mod * edge_length * density * vel * vel;
+ drag_mag = Math.min(drag_mag, this.max_drag);
+ let drag_force = b2.Vec2.Multiply(drag_mag, b2.Vec2.Negate(vel_dir));
+ pair[1].GetBody().ApplyForce(drag_force, mid_point, true);
+
+ // apply lift
+ let lift_dot = b2.Dot_v2_v2(edge, vel_dir);
+ let lift_mag = drag_dot * lift_dot * this.lift_mod * edge_length * density * vel * vel;
+ lift_mag = Math.min(lift_mag, this.max_lift);
+ let lift_dir = b2.Cross_f_v2(1, vel_dir);
+ let lift_force = b2.Vec2.Multiply(lift_mag, lift_dir);
+ pair[1].GetBody().ApplyForce(lift_force, mid_point, true);
+ }
+
+ // PUSH
+ let body_to_check = pair[1].GetBody();
+ // Simplification /!\
+ let joints_to_check = [];
+ let joint_edge = body_to_check.GetJointList();
+ while(joint_edge != null){
+ if(joint_edge.joint.GetBodyB() == body_to_check){
+ joints_to_check.push(joint_edge.joint);
+ }
+ joint_edge = joint_edge.next;
+ }
+
+ for(let joint of joints_to_check){
+ if(joint.GetLowerLimit() < joint.GetJointAngle() && joint.GetJointAngle() < joint.GetUpperLimit()){
+ let torque = joint.GetMotorTorque(60);
+
+ // Calculate angular inertia of the object
+ let moment_of_inertia = body_to_check.GetInertia();
+ let angular_velocity = body_to_check.GetAngularVelocity();
+ let angular_inertia = moment_of_inertia * angular_velocity;
+
+ // Calculate the force applied to the object
+ let world_center = body_to_check.GetWorldCenter();
+ let anchor = joint.GetAnchorB();
+ let lever_vector = b2.Vec2.Subtract(world_center, anchor); // vector from pivot to point of application of the force
+ let force_applied_at_center = b2.Cross_v2_f(lever_vector, -torque);
+
+ let push_dot = b2.Dot_v2_v2(normal, force_applied_at_center);
+ if(push_dot > 0){
+ vel = torque + angular_inertia;
+ // Wrong approximation /!\
+ let push_mag = push_dot * this.push_mod * edge_length * density * vel * vel;
+ let force = b2.Vec2.Multiply(push_mag, b2.Vec2.Negate(force_applied_at_center));
+ let clip_force_x = Math.max(-this.max_push, Math.min(force.x, this.max_push));
+ let clip_force_y = Math.max(-this.max_push, Math.min(force.y, this.max_push))
+ let push_force = new b2.Vec2(clip_force_x, clip_force_y);
+ body_to_check.ApplyForce(push_force, joint.GetAnchorB(), true);
+ }
+ }
+ }
+
+ }
+
+ }
+
+ }
+ }
+}
+
+/**
+ * @classdesc Stores fixtures of objects in contact with water.
+ * @constructor
+ */
+function WaterContactDetector() {
+ b2.ContactListener.call(this);
+ this.fixture_pairs = [];
+}
+
+WaterContactDetector.prototype = Object.create(b2.ContactListener.prototype);
+WaterContactDetector.prototype.constructor = WaterContactDetector;
+WaterContactDetector.prototype.BeginContact = function (contact){
+ if(contact.GetFixtureA().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.WATER
+ && contact.GetFixtureB().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.BODY_OBJECT){
+ this.fixture_pairs.push([contact.GetFixtureA(), contact.GetFixtureB()]);
+ }
+ else if(contact.GetFixtureB().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.WATER
+ && contact.GetFixtureA().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.BODY_OBJECT){
+ this.fixture_pairs.push([contact.GetFixtureB(), contact.GetFixtureA()]);
+ }
+};
+
+WaterContactDetector.prototype.EndContact = function (contact) {
+ if(contact.GetFixtureA().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.WATER
+ && contact.GetFixtureB().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.BODY_OBJECT){
+ let index = this.fixture_pairs.indexOf([contact.GetFixtureA(), contact.GetFixtureB()]);
+ if (index !== -1) {
+ this.fixture_pairs.splice(index, 1);
+ }
+ }
+ else if(contact.GetFixtureB().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.WATER
+ && contact.GetFixtureA().GetBody().GetUserData().object_type == CustomUserDataObjectTypes.BODY_OBJECT){
+ let index = this.fixture_pairs.indexOf([contact.GetFixtureB(), contact.GetFixtureA()]);
+ if (index !== -1) {
+ this.fixture_pairs.splice(index, 1);
+ }
+ }
+};
+
+WaterContactDetector.prototype.Reset = function (){
+ this.fixture_pairs = [];
+};
+
diff --git a/js/CPPN/cppn.js b/js/CPPN/cppn.js
new file mode 100644
index 0000000000000000000000000000000000000000..0d9f996704fe15b2a7c7a33b1f204b98a4434a41
--- /dev/null
+++ b/js/CPPN/cppn.js
@@ -0,0 +1,33 @@
+/**
+ * @classdesc CPPN class.
+ */
+class CPPN {
+
+ /**
+ * @constructor
+ * @param x_dim {number} - Number of terrain steps to generate
+ * @param input_dim {number} - Dimension of the input encoding vector
+ */
+ constructor(x_dim, input_dim){
+ this.x_dim = x_dim;
+ this.input_dim = input_dim;
+ this.cppn_model = window.cppn_model;
+ }
+
+ /**
+ * Generates the terrain shapes with the CPPN model.
+ * @param input_vector {Array} - 3-dimensional array that encodes the CPPN
+ * @returns {Array} - Array of y-coordinates of the terrain shapes : [[y_ground, y_ceiling]
+ */
+ generate(input_vector){
+ let x = [...Array(this.x_dim).keys()];
+ let scaled_x = x.map(e => e / (this.x_dim - 1));
+ let x_vec = scaled_x.map(e => [e]);
+ let final_input = [];
+ for(let i = 0; i < this.x_dim; i++){
+ final_input.push([x_vec[i].concat(input_vector)]);
+ }
+ final_input = tf.tensor(final_input);
+ return this.cppn_model.predict(final_input.reshape([this.x_dim, this.input_dim + 1]));
+ }
+}
\ No newline at end of file
diff --git a/js/CPPN/weights/ground_cppn/.data-00000-of-00001 b/js/CPPN/weights/ground_cppn/.data-00000-of-00001
new file mode 100644
index 0000000000000000000000000000000000000000..61827bf386d84753cd5f8e0cf5f6ac675ee1c5d1
Binary files /dev/null and b/js/CPPN/weights/ground_cppn/.data-00000-of-00001 differ
diff --git a/js/CPPN/weights/ground_cppn/.index b/js/CPPN/weights/ground_cppn/.index
new file mode 100644
index 0000000000000000000000000000000000000000..6a1664474b938afc6d107ef2a3a30d9b2d50a6f6
Binary files /dev/null and b/js/CPPN/weights/ground_cppn/.index differ
diff --git a/js/CPPN/weights/ground_cppn/.meta b/js/CPPN/weights/ground_cppn/.meta
new file mode 100644
index 0000000000000000000000000000000000000000..69f1769e790514da360328938579cea61aba0a4b
Binary files /dev/null and b/js/CPPN/weights/ground_cppn/.meta differ
diff --git a/js/CPPN/weights/ground_cppn/checkpoint b/js/CPPN/weights/ground_cppn/checkpoint
new file mode 100644
index 0000000000000000000000000000000000000000..2619d8b007c68157f1253fc4100c4a3437d60d44
--- /dev/null
+++ b/js/CPPN/weights/ground_cppn/checkpoint
@@ -0,0 +1,3 @@
+model_checkpoint_path: "."
+all_model_checkpoint_paths: "."
+all_model_checkpoint_paths: "."
diff --git a/js/CPPN/weights/same_ground_ceiling_cppn/.data-00000-of-00001 b/js/CPPN/weights/same_ground_ceiling_cppn/.data-00000-of-00001
new file mode 100644
index 0000000000000000000000000000000000000000..f9f444da5052fca5d0235338353c8b086251384b
Binary files /dev/null and b/js/CPPN/weights/same_ground_ceiling_cppn/.data-00000-of-00001 differ
diff --git a/js/CPPN/weights/same_ground_ceiling_cppn/.index b/js/CPPN/weights/same_ground_ceiling_cppn/.index
new file mode 100644
index 0000000000000000000000000000000000000000..fd5b58b392ff2d36a5fed03f6cc1db3c79f9c1d9
Binary files /dev/null and b/js/CPPN/weights/same_ground_ceiling_cppn/.index differ
diff --git a/js/CPPN/weights/same_ground_ceiling_cppn/.meta b/js/CPPN/weights/same_ground_ceiling_cppn/.meta
new file mode 100644
index 0000000000000000000000000000000000000000..fa68b13bbf2ca7e817359af87d631ee20f46f13d
Binary files /dev/null and b/js/CPPN/weights/same_ground_ceiling_cppn/.meta differ
diff --git a/js/CPPN/weights/same_ground_ceiling_cppn/checkpoint b/js/CPPN/weights/same_ground_ceiling_cppn/checkpoint
new file mode 100644
index 0000000000000000000000000000000000000000..2619d8b007c68157f1253fc4100c4a3437d60d44
--- /dev/null
+++ b/js/CPPN/weights/same_ground_ceiling_cppn/checkpoint
@@ -0,0 +1,3 @@
+model_checkpoint_path: "."
+all_model_checkpoint_paths: "."
+all_model_checkpoint_paths: "."
diff --git a/js/CPPN/weights/same_ground_ceiling_cppn/frozen_model/saved_model.pb b/js/CPPN/weights/same_ground_ceiling_cppn/frozen_model/saved_model.pb
new file mode 100644
index 0000000000000000000000000000000000000000..e53fc8df2e73c432608d3af90180e517d9e98507
Binary files /dev/null and b/js/CPPN/weights/same_ground_ceiling_cppn/frozen_model/saved_model.pb differ
diff --git a/js/CPPN/weights/same_ground_ceiling_cppn/tfjs_model/group1-shard1of1.bin b/js/CPPN/weights/same_ground_ceiling_cppn/tfjs_model/group1-shard1of1.bin
new file mode 100644
index 0000000000000000000000000000000000000000..05516f42de8fb42b9b8e4d184bf499eede39a149
--- /dev/null
+++ b/js/CPPN/weights/same_ground_ceiling_cppn/tfjs_model/group1-shard1of1.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:26132af7a3f2d2209504d78a3079b88fbf4d971d1de5485d41bc1cd7e272890c
+size 50688
diff --git a/js/CPPN/weights/same_ground_ceiling_cppn/tfjs_model/model.json b/js/CPPN/weights/same_ground_ceiling_cppn/tfjs_model/model.json
new file mode 100644
index 0000000000000000000000000000000000000000..d0458340fc92882b9cce74884f06f50caa73a712
--- /dev/null
+++ b/js/CPPN/weights/same_ground_ceiling_cppn/tfjs_model/model.json
@@ -0,0 +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"}]}]}
\ No newline at end of file
diff --git a/js/bodies/abstract_body.js b/js/bodies/abstract_body.js
new file mode 100644
index 0000000000000000000000000000000000000000..9809566e3ff2c4086678001641ccca022a957411
--- /dev/null
+++ b/js/bodies/abstract_body.js
@@ -0,0 +1,161 @@
+/**
+ * @classdesc Abstract class for agent's morphologies
+ */
+class AbstractBody {
+
+ /**
+ * @constructor
+ * @param scale {number} - Scale of the environment
+ * @param motors_torque {number}
+ */
+ constructor(scale, motors_torque){
+ this.SCALE = scale;
+ this.MOTORS_TORQUE = motors_torque;
+ this.body_parts = [];
+ this.motors = [];
+ this.is_selected = false;
+ }
+
+ /**
+ * Gets the size of the motors state.
+ * @return {number}
+ */
+ get_state_size(){
+ return this.get_motors_state().length;
+ }
+
+ /**
+ * Gets the motors state.
+ * @return {Array}
+ */
+ get_motors_state(){
+ let state = [];
+ for(let motor of this.motors){
+ let motor_info = motor.GetUserData();
+ if(motor_info.check_contact){
+ let s = [
+ motor.GetJointAngle() + motor_info.angle_correction,
+ motor.GetJointSpeed() / motor_info.speed_control,
+ 0.0
+ ]
+ if(motor_info.contact_body.GetUserData().has_contact){
+ s[2] = 1.0;
+ }
+ state = state.concat(s);
+ }
+ else{
+ state = state.concat([
+ motor.GetJointAngle() + motor_info.angle_correction,
+ motor.GetJointSpeed() / motor_info.speed_control
+ ])
+ }
+ }
+ return state;
+ }
+
+ /**
+ * Gets the size of the action space.
+ * @return {number}
+ */
+ get_action_size(){
+ return this.motors.length;
+ }
+
+ /**
+ * Activates the motors according to the given actions by setting the motors speed and torque.
+ * @param actions {Array}
+ */
+ activate_motors(actions){
+ for(let i = 0; i < this.motors.length; i++){
+ this.motors[i].SetMotorSpeed(this.motors[i].GetUserData().speed_control * Math.sign(actions[i]));
+ let clamp01 = Math.max(0, Math.min(Math.abs(actions[i]), 1));
+ this.motors[i].SetMaxMotorTorque(this.MOTORS_TORQUE * clamp01);
+ }
+ }
+
+ /**
+ * Creates the Box2D body parts, joints and sensors of the agent.
+ * @param world {Object} - Box2D world
+ * @param init_x {number}
+ * @param init_y {number}
+ * @param force_to_center {number}
+ */
+ draw(world, init_x, init_y, force_to_center){}
+
+ /**
+ * Gets all the body parts
+ * @return {Array}
+ */
+ get_elements_to_render(){
+ return this.body_parts;
+ }
+
+ /**
+ * Checks if the given position is inside the agent's morphology
+ * @param pos {{x: number, y: number}}
+ * @return {boolean}
+ */
+ isPosInside(pos){
+ for(let body of this.body_parts){
+ let shape = body.GetFixtureList().GetShape();
+ let vertices = [];
+ for(let i = 0; i < shape.m_count; i++){
+ let world_pos = body.GetWorldPoint(shape.m_vertices[i]);
+ vertices.push({x: world_pos.x, y: world_pos.y});
+ }
+
+ // 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
+ let nb_intersections = 0;
+ for(let i = 0; i < vertices.length; i++){
+ let v1 = vertices[i];
+ let v2;
+ if(i == vertices.length - 1){
+ v2 = vertices[0];
+ }
+ else {
+ v2 = vertices[i+1];
+ }
+
+ // Checks if the edge between v1 and v2 cross the mouse y-coordinate
+ if(pos.y >= Math.min(v1.y, v2.y) && pos.y <= Math.max(v1.y, v2.y)){
+ let intersection_x;
+
+ // Computes the equation of the line between v1 and v2
+ let a = (v2.y - v1.y) / (v2.x - v1.x);
+ let b = v1.y - a * v1.x;
+
+ // Computes the x-coordinate of the intersection point
+ if(Math.abs(a) == Infinity){
+ intersection_x = v1.x;
+ }
+ else{
+ intersection_x = (pos.y - b) / a;
+ }
+
+ // Increases the number of intersection only if the intersection point is to the right of the mouse x-coordinate
+ if(intersection_x >= pos.x) {
+ nb_intersections += 1;
+ }
+ }
+ }
+
+ // The pos is inside the agent's body if there is an odd number of intersections, else it is outside
+ if(nb_intersections % 2 != 0){
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Destroys all the body parts of the agents.
+ * @param world {Object} - Box2D world
+ */
+ destroy(world){
+ for(let body of this.body_parts){
+ world.DestroyBody(body);
+ }
+ this.body_parts = [];
+ this.motors = [];
+ }
+}
\ No newline at end of file
diff --git a/js/bodies/bodies_enum.js b/js/bodies/bodies_enum.js
new file mode 100644
index 0000000000000000000000000000000000000000..fa09136f02c609c6ae58bf9b07521a8689f2c8ac
--- /dev/null
+++ b/js/bodies/bodies_enum.js
@@ -0,0 +1,7 @@
+let BodyTypesEnum = {
+ WALKER: 0,
+ SWIMMER: 1,
+ CLIMBER: 2,
+ AMPHIBIAN: 3,
+};
+
diff --git a/js/bodies/climbers/climber_abstract_body.js b/js/bodies/climbers/climber_abstract_body.js
new file mode 100644
index 0000000000000000000000000000000000000000..05fe0f907109ee352a084c08252150789c9f52f3
--- /dev/null
+++ b/js/bodies/climbers/climber_abstract_body.js
@@ -0,0 +1,60 @@
+/**
+ * @classdesc Abstract class for climber morphologies.
+ */
+class ClimberAbstractBody extends WalkerAbstractBody {
+ /**
+ * @constructor
+ * @param scale {number} - Scale of the environment
+ * @param motors_torque {number}
+ * @param nb_steps_under_water {number}
+ */
+ constructor(scale, motors_torque, nb_steps_under_water){
+ super(scale, motors_torque, nb_steps_under_water);
+
+ this.body_type = BodyTypesEnum.CLIMBER;
+ this.sensors = [];
+ this.SENSOR_FD = new b2.FixtureDef();
+ this.SENSOR_FD.shape = new b2.CircleShape();
+ this.SENSOR_FD.shape.m_radius = 0.05;
+ this.SENSOR_FD.density = 1.0;
+ this.SENSOR_FD.restitution = 0.0;
+ this.SENSOR_FD.filter.categoryBits = 0x20;
+ this.SENSOR_FD.filter.maskBits = 0x1;
+ this.SENSOR_FD.isSensor = true;
+ }
+
+ // States
+ get_state_size() {
+ return super.get_state_size() + this.get_sensors_state().length;
+ }
+
+ get_sensors_state(){
+ let state = [];
+ for(let sensor of this.sensors){
+ state = state.concat([
+ sensor.GetUserData().has_contact ? 1 : 0,
+ sensor.GetUserData().has_joint ? 1 : 0
+ ]);
+ }
+ return state;
+ }
+
+ // Actions
+ get_action_size() {
+ return super.get_action_size() + this.sensors.length;
+ }
+
+ // Draw
+ get_elements_to_render() {
+ return super.get_elements_to_render().concat(this.sensors);
+ }
+
+ // Destroy
+ destroy(world) {
+ super.destroy(world);
+ for(let sensor of this.sensors){
+ world.DestroyBody(sensor);
+ }
+ this.sensors = [];
+ }
+}
\ No newline at end of file
diff --git a/js/bodies/climbers/climbing_profile_chimpanzee.js b/js/bodies/climbers/climbing_profile_chimpanzee.js
new file mode 100644
index 0000000000000000000000000000000000000000..a2eaf66234b09a741b442dba94997cf9238f0d1d
--- /dev/null
+++ b/js/bodies/climbers/climbing_profile_chimpanzee.js
@@ -0,0 +1,293 @@
+const CHIMP_SPEED_HIP = 4;
+const CHIMP_SPEED_KNEE = 6;
+const CHIMP_SPEED_HAND = 8;
+
+/**
+ * @classdesc Chimpanzee morphology.
+ */
+class ClimbingProfileChimpanzee extends ClimberAbstractBody {
+ /**
+ * @constructor
+ * @param scale {number} - Scale of the environment
+ * @param motors_torque {number}
+ * @param nb_steps_under_water {number}
+ */
+ constructor(scale, motors_torque=100, nb_steps_under_water=600){
+ super(scale, motors_torque, nb_steps_under_water);
+
+ this.LEG_DOWN = 12 / this.SCALE;
+ this.ARM_UP = 22 / this.SCALE;
+ this.LIMB_W = 8 / this.SCALE;
+ this.LIMB_H = 28 / this.SCALE;
+ this.HAND_PART_W = 4 / this.SCALE;
+ this.HAND_PART_H = 8 / this.SCALE;
+ this.LEG_H = this.LIMB_H;
+ this.TORQUE_PENALTY = 0.00035 / 5; // Legs + arms + hands
+ this.BODY_HEIGHT = 45;
+ this.HEAD_HEIGHT = 20;
+ this.HEAD_UP = 2.5 / this.SCALE; // formerly 0.2
+
+ this.AGENT_WIDTH = 24 / this.SCALE;
+ this.AGENT_HEIGHT = this.BODY_HEIGHT / this.SCALE
+ + this.HEAD_HEIGHT / this.SCALE
+ + this.HEAD_UP + this.LEG_H * 2
+ - this.LEG_DOWN;
+ this.AGENT_CENTER_HEIGHT = this.LEG_H * 2 + this.LEG_DOWN;
+
+ this.remove_reward_on_head_angle = true;
+ }
+
+ draw(world, init_x, init_y, force_to_center){
+
+ // Head
+ let head_fd = new b2.FixtureDef();
+ head_fd.shape = new b2.PolygonShape();
+ head_fd.shape.SetAsBox(5 / this.SCALE, 10 / this.SCALE);
+ /*head_fd.shape.Set([
+ new b2.Vec2(-5 / this.SCALE, 10 / this.SCALE),
+ new b2.Vec2(5 / this.SCALE, 10 / this.SCALE),
+ new b2.Vec2(5 / this.SCALE, -10 / this.SCALE),
+ new b2.Vec2(-5 / this.SCALE, -10 / this.SCALE),
+ ], 4);*/
+ head_fd.density = 5.0;
+ head_fd.friction = 0.1;
+ head_fd.filter.categoryBits = 0x20;
+ head_fd.filter.maskBits = 0x1;
+
+ let head_bd = new b2.BodyDef();
+ head_bd.type = b2.Body.b2_dynamicBody;
+ head_bd.position.Set(init_x,
+ init_y + this.BODY_HEIGHT / this.SCALE / 2 + this.HEAD_HEIGHT / this.SCALE / 2 + this.HEAD_UP);
+ let head = world.CreateBody(head_bd);
+ head.CreateFixture(head_fd);
+ head.color1 = "#806682"; // [0.5, 0.4, 0.9]
+ head.color2 = "#4D4D80";
+ head.ApplyForceToCenter(new b2.Vec2(force_to_center, 0), true);
+ head.SetUserData(new CustomBodyUserData(true, true, "head"));
+ this.body_parts.push(head);
+ this.reference_head_object = head;
+
+ // Body
+ let body_fd = new b2.FixtureDef();
+ body_fd.shape = new b2.PolygonShape();
+ body_fd.shape.Set([
+ new b2.Vec2(-12 / this.SCALE, 25 / this.SCALE),
+ new b2.Vec2(12 / this.SCALE, 25 / this.SCALE),
+ new b2.Vec2(8 / this.SCALE, -20 / this.SCALE),
+ new b2.Vec2(-8 / this.SCALE, -20 / this.SCALE),
+ ], 4);
+ body_fd.density = 5.0;
+ body_fd.friction = 0.1;
+ body_fd.filter.categoryBits = 0x20;
+ body_fd.filter.maskBits = 0x1; // collide only with ground
+
+ let body_bd = new b2.BodyDef();
+ body_bd.type = b2.Body.b2_dynamicBody;
+ body_bd.position.Set(init_x, init_y);
+ let body = world.CreateBody(body_bd);
+ body.CreateFixture(body_fd);
+ body.color1 = "#806682"; // [0.5, 0.4, 0.9]
+ body.color2 = "#4D4D80"; // [0.3, 0.3, 0.5]
+ body.SetUserData(new CustomBodyUserData(true, true, "body"));
+ this.body_parts.push(body);
+
+ // Revolute joint between head and body
+ let rjd = new b2.RevoluteJointDef();
+ rjd.Initialize(head, body, new b2.Vec2(init_x, init_y + this.BODY_HEIGHT / this.SCALE / 2));
+ rjd.enableMotor = false;
+ rjd.enableLimit = true;
+ rjd.lowerAngle = -0.1 * Math.PI;
+ rjd.upperAngle = 0.1 * Math.PI;
+ let joint_motor = world.CreateJoint(rjd);
+ joint_motor.SetUserData(new CustomMotorUserData("neck", 0, false));
+ this.neck_joint = joint_motor;
+
+ // Limbs FixtureDef
+ let UPPER_LIMB_FD = new b2.FixtureDef();
+ UPPER_LIMB_FD.shape = new b2.PolygonShape();
+ UPPER_LIMB_FD.shape.SetAsBox(this.LIMB_W / 2, this.LIMB_H / 2);
+ UPPER_LIMB_FD.density = 1.0;
+ UPPER_LIMB_FD.restitution = 0;
+ UPPER_LIMB_FD.filter.categoryBits = 0x20;
+ UPPER_LIMB_FD.filter.maskBits = 0x1;
+
+ let LOWER_LIMB_FD = new b2.FixtureDef();
+ LOWER_LIMB_FD.shape = new b2.PolygonShape();
+ LOWER_LIMB_FD.shape.SetAsBox(0.8 * this.LIMB_W / 2, this.LIMB_H / 2);
+ LOWER_LIMB_FD.density = 1.0;
+ LOWER_LIMB_FD.restitution = 0;
+ LOWER_LIMB_FD.filter.categoryBits = 0x20;
+ LOWER_LIMB_FD.filter.maskBits = 0x1;
+
+ let HAND_PART_FD = new b2.FixtureDef();
+ HAND_PART_FD.shape = new b2.PolygonShape();
+ HAND_PART_FD.shape.SetAsBox(this.HAND_PART_W / 2, this.HAND_PART_H / 2);
+ HAND_PART_FD.density = 1.0;
+ HAND_PART_FD.restitution = 0;
+ HAND_PART_FD.filter.categoryBits = 0x20;
+ HAND_PART_FD.filter.maskBits = 0x1;
+
+ // Legs
+ for(let i = 0; i < 2; i++){
+
+ // Upper leg
+ let upper_bd = new b2.BodyDef();
+ upper_bd.type = b2.Body.b2_dynamicBody;
+ upper_bd.position.Set(init_x, init_y - this.LIMB_H / 2 - this.LEG_DOWN);
+ let upper = world.CreateBody(upper_bd);
+ upper.CreateFixture(UPPER_LIMB_FD);
+ upper.color1 = "#964A7D"; // [0.59, 0.29, 0.49]
+ upper.color2 = "#63304A"; // [0.39, 0.19, 0.29]
+ upper.SetUserData(new CustomBodyUserData(false, false, "upper_leg"));
+ this.body_parts.push(upper);
+
+ // Revolute joint between body and upper leg
+ rjd = new b2.RevoluteJointDef();
+ rjd.Initialize(body, upper, new b2.Vec2(init_x, init_y - this.LEG_DOWN));
+ rjd.enableMotor = true;
+ rjd.enableLimit = true;
+ rjd.maxMotorTorque = this.MOTORS_TORQUE;
+ rjd.motorSpeed = 1;
+ rjd.lowerAngle = -0.3 * Math.PI;
+ rjd.upperAngle = 0.6 * Math.PI;
+ joint_motor = world.CreateJoint(rjd);
+ joint_motor.SetUserData(new CustomMotorUserData("hip", CHIMP_SPEED_HIP, false));
+ this.motors.push(joint_motor);
+
+ // Lower leg
+ let lower_bd = new b2.BodyDef();
+ lower_bd.type = b2.Body.b2_dynamicBody;
+ lower_bd.position.Set(init_x, init_y - this.LIMB_H * 3/2 - this.LEG_DOWN);
+ let lower = world.CreateBody(lower_bd);
+ lower.CreateFixture(LOWER_LIMB_FD);
+ lower.color1 = "#964A7D"; // [0.59, 0.29, 0.49]
+ lower.color2 = "#63304A"; // [0.39, 0.19, 0.29]
+ lower.SetUserData(new CustomBodyUserData(true, true, "lower_leg"));
+ this.body_parts.push(lower);
+
+ // Revolute joint between upper and lower leg
+ rjd = new b2.RevoluteJointDef();
+ rjd.Initialize(upper, lower, new b2.Vec2(init_x, init_y - this.LEG_H - this.LEG_DOWN));
+ rjd.enableMotor = true;
+ rjd.enableLimit = true;
+ rjd.maxMotorTorque = this.MOTORS_TORQUE;
+ rjd.motorSpeed = 1;
+ rjd.lowerAngle = -0.75 * Math.PI;
+ rjd.upperAngle = -0.1;
+ joint_motor = world.CreateJoint(rjd);
+ joint_motor.SetUserData(new CustomMotorUserData("knee",
+ CHIMP_SPEED_KNEE,
+ true,
+ 1,
+ lower));
+ this.motors.push(joint_motor);
+ }
+
+ // Arms
+ for(let i = 0; i < 2; i++){
+ // Upper arm
+ let upper_bd = new b2.BodyDef();
+ upper_bd.type = b2.Body.b2_dynamicBody;
+ upper_bd.position.Set(init_x, init_y + this.LIMB_H / 2 + this.ARM_UP);
+ let upper = world.CreateBody(upper_bd);
+ upper.CreateFixture(UPPER_LIMB_FD);
+ upper.color1 = "#9C4F82"; // [0.61, 0.31, 0.51]
+ upper.color2 = "#69364F"; // [0.41, 0.21, 0.31]
+ upper.SetUserData(new CustomBodyUserData(false, false, "upper_arm"));
+ this.body_parts.push(upper);
+
+ // Revolute joint between body and upper arm
+ rjd = new b2.RevoluteJointDef();
+ rjd.Initialize(body, upper, new b2.Vec2(init_x, init_y + this.ARM_UP));
+ rjd.enableMotor = true;
+ rjd.enableLimit = true;
+ rjd.maxMotorTorque = this.MOTORS_TORQUE;
+ rjd.motorSpeed = 1;
+ rjd.lowerAngle = -0.75 * 2 * Math.PI;
+ rjd.upperAngle = 0;
+ joint_motor = world.CreateJoint(rjd);
+ joint_motor.SetUserData(new CustomMotorUserData("shoulder", CHIMP_SPEED_HIP, false));
+ this.motors.push(joint_motor);
+
+ // Lower arm
+ let lower_bd = new b2.BodyDef();
+ lower_bd.type = b2.Body.b2_dynamicBody;
+ lower_bd.position.Set(init_x, init_y + this.LIMB_H * 3/2 + this.ARM_UP);
+ let lower = world.CreateBody(lower_bd);
+ lower.CreateFixture(LOWER_LIMB_FD);
+ lower.color1 = "#9C4F82"; // [0.61, 0.31, 0.51]
+ lower.color2 = "#69364F"; // [0.41, 0.21, 0.31]
+ lower.SetUserData(new CustomBodyUserData(false, false, "lower_arm"));
+ this.body_parts.push(lower);
+
+ // Revolute joint between upper arm and lower arm
+ rjd = new b2.RevoluteJointDef();
+ rjd.Initialize(upper, lower, new b2.Vec2(init_x, init_y + this.LIMB_H + this.ARM_UP));
+ rjd.enableMotor = true;
+ rjd.enableLimit = true;
+ rjd.maxMotorTorque = this.MOTORS_TORQUE;
+ rjd.motorSpeed = 1;
+ rjd.lowerAngle = 0;
+ rjd.upperAngle = 0.75 * Math.PI;
+ joint_motor = world.CreateJoint(rjd);
+ joint_motor.SetUserData(new CustomMotorUserData("elbow",
+ CHIMP_SPEED_HIP,
+ false));
+ this.motors.push(joint_motor);
+
+ // Hand
+ let prev_part = lower;
+ let initial_y = init_y + this.LIMB_H * 2 + this.ARM_UP;
+ let angle_boundaries = [[-0.5, 0.5]];
+ let nb_hand_parts = 1;
+ for(let u = 0; u < nb_hand_parts; u++){
+ let hand_part_bd = new b2.BodyDef();
+ hand_part_bd.type = b2.Body.b2_dynamicBody;
+ hand_part_bd.position.Set(init_x, initial_y + this.HAND_PART_H / 2 + this.HAND_PART_H * u);
+ let hand_part = world.CreateBody(hand_part_bd);
+ hand_part.CreateFixture(HAND_PART_FD);
+ hand_part.color1 = "#9C4F82"; // [0.61, 0.31, 0.51]
+ hand_part.color2 = "#69364F"; // [0.41, 0.21, 0.31]
+ hand_part.SetUserData(new CustomBodyUserData(true, false, "hand"));
+ this.body_parts.push(hand_part);
+
+ // Revolute joint between prev_part and hand_part
+ rjd = new b2.RevoluteJointDef();
+ rjd.Initialize(prev_part, hand_part, new b2.Vec2(init_x, initial_y + this.HAND_PART_H * u));
+ rjd.enableMotor = true;
+ rjd.enableLimit = true;
+ rjd.maxMotorTorque = this.MOTORS_TORQUE;
+ rjd.motorSpeed = 1;
+ rjd.lowerAngle = angle_boundaries[u][0] * Math.PI;
+ rjd.upperAngle = angle_boundaries[u][1] * Math.PI;
+ joint_motor = world.CreateJoint(rjd);
+ joint_motor.SetUserData(new CustomMotorUserData("hand",
+ CHIMP_SPEED_HAND,
+ true,
+ 0,
+ hand_part));
+ this.motors.push(joint_motor);
+
+ prev_part = hand_part;
+ }
+
+ // Sensor
+ let hand_sensor_position = new b2.Vec2(init_x, initial_y + this.HAND_PART_H * nb_hand_parts);
+ let hand_sensor_part_bd = new b2.BodyDef();
+ hand_sensor_part_bd.type = b2.Body.b2_dynamicBody;
+ hand_sensor_part_bd.position.Assign(hand_sensor_position);
+ let hand_sensor_part = world.CreateBody(hand_sensor_part_bd);
+ hand_sensor_part.CreateFixture(this.SENSOR_FD);
+ hand_sensor_part.color1 = "#FF0000"; // [1, 0, 0]
+ hand_sensor_part.color2 = "#FF0000"; // [1, 0, 0]
+ hand_sensor_part.SetUserData(new CustomBodySensorUserData(true, false, "hand_sensor"));
+ this.sensors.push(hand_sensor_part);
+
+ let wjd = new b2.WeldJointDef();
+ wjd.Initialize(prev_part, hand_sensor_part, hand_sensor_position);
+ world.CreateJoint(wjd);
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/js/bodies/swimmers/fish_body.js b/js/bodies/swimmers/fish_body.js
new file mode 100644
index 0000000000000000000000000000000000000000..6d5b5563ff5141a614e5ed3e8c8d241ba1770465
--- /dev/null
+++ b/js/bodies/swimmers/fish_body.js
@@ -0,0 +1,248 @@
+// Head
+HULL_POLYGON = [
+ [-20, +12], [+6, +12],
+ [+15, +4], [+15, -4],
+ [+6, -12], [-20, -12]
+];
+
+BODY_P1 = [
+ [-8, +9], [+8, +12],
+ [+8, -12], [-8, -9]
+];
+
+BODY_P2 = [
+ [-8, +4], [+8, +9],
+ [+8, -9], [-8, -4]
+];
+
+// Tail
+BODY_P3 = [
+ [-4, +2], [+4, +4],
+ [+4, -4], [-4, -2]
+];
+
+FIN = [
+ [-1, -10], [-1, +10],
+ [+1, +10], [+1, -10]
+];
+
+HULL_BOTTOM_WIDTH = 35;
+const SPEED = 6;
+
+/**
+ * @classdesc Fish morphology.
+ */
+class FishBody extends SwimmerAbstractBody {
+ /**
+ * @constructor
+ * @param scale {number} - Scale of the environment
+ * @param motors_torque {number}
+ * @param density {number} - Density of the agent's body.
+ * @param nb_steps_outside_water {number}
+ */
+ constructor(scale, motors_torque=80, density, nb_steps_outside_water=600) {
+ super(scale, motors_torque, density, nb_steps_outside_water);
+ this.TORQUE_PENALTY = 0.00035;
+
+ this.AGENT_WIDTH = HULL_BOTTOM_WIDTH / this.SCALE;
+ this.AGENT_HEIGHT = 18 / this.SCALE;
+ this.AGENT_CENTER_HEIGHT = 9 / this.SCALE;
+
+ this.remove_reward_on_head_angle = true;
+
+ this.fins = [];
+ this.tail = null;
+ }
+
+ draw(world, init_x, init_y){
+
+ let vertices;
+ let rjd;
+ let joint_motor;
+
+ // HULL
+ let hull_fd = new b2.FixtureDef();
+ hull_fd.shape = new b2.PolygonShape();
+ vertices = [];
+ for(let vertex of HULL_POLYGON){
+ vertices.push(new b2.Vec2(vertex[0] / this.SCALE, vertex[1] / this.SCALE));
+ }
+ hull_fd.shape.Set(vertices, HULL_POLYGON.length);
+ hull_fd.density = this.DENSITY;
+ hull_fd.friction = 0.1;
+ hull_fd.filter.categoryBits = 0x20;
+ hull_fd.filter.maskBits = 0x000F; // 0.99 bouncy
+
+ let hull_bd = new b2.BodyDef();
+ hull_bd.type = b2.Body.b2_dynamicBody;
+ hull_bd.position.Set(init_x, init_y);
+ let hull = world.CreateBody(hull_bd);
+ hull.CreateFixture(hull_fd);
+ hull.color1 = "#806682"; // [0.5, 0.4, 0.9]
+ hull.color2 = "#4D4D80";
+ hull.SetUserData(new CustomBodyUserData(true, false, "head"));
+ this.body_parts.push(hull);
+ this.reference_head_object = hull;
+
+ // BODY_P1
+ let body_p1_x = init_x - 35 / 2 / this.SCALE - 16 / 2 / this.SCALE;
+ let body_p1_fd = new b2.FixtureDef();
+ body_p1_fd.shape = new b2.PolygonShape();
+ vertices = [];
+ for(let vertex of BODY_P1){
+ vertices.push(new b2.Vec2(vertex[0] / this.SCALE, vertex[1] / this.SCALE));
+ }
+ body_p1_fd.shape.Set(vertices, BODY_P1.length);
+ body_p1_fd.density = this.DENSITY;
+ body_p1_fd.restitution = 0.0;
+ body_p1_fd.filter.categoryBits = 0x20;
+ body_p1_fd.filter.maskBits = 0x000F; // 0.99 bouncy
+
+ let body_p1_bd = new b2.BodyDef();
+ body_p1_bd.type = b2.Body.b2_dynamicBody;
+ body_p1_bd.position.Set(body_p1_x, init_y);
+ let body_p1 = world.CreateBody(body_p1_bd);
+ body_p1.CreateFixture(body_p1_fd);
+ body_p1.color1 = "#806682"; // [0.5, 0.4, 0.9]
+ body_p1.color2 = "#4D4D80";
+ body_p1.SetUserData(new CustomBodyUserData(true, false, "body"));
+ this.body_parts.push(body_p1);
+
+ // Revolute joint between HULL and BODY_P1
+ rjd = new b2.RevoluteJointDef();
+ rjd.Initialize(hull, body_p1, new b2.Vec2(init_x - 35 / 2 / this.SCALE, init_y));
+ rjd.enableMotor = true;
+ rjd.enableLimit = true;
+ rjd.maxMotorTorque = this.MOTORS_TORQUE;
+ rjd.motorSpeed = 1;
+ rjd.lowerAngle = -0.1 * Math.PI;
+ rjd.upperAngle = 0.2 * Math.PI;
+ joint_motor = world.CreateJoint(rjd);
+ joint_motor.SetUserData(new CustomMotorUserData("neck", SPEED, true, 0.0, body_p1));
+ this.motors.push(joint_motor);
+
+ // BODY_P2
+ let body_p2_x = body_p1_x - 16 / 2 / this.SCALE - 16 / 2 / this.SCALE;
+ let body_p2_fd = new b2.FixtureDef();
+ body_p2_fd.shape = new b2.PolygonShape();
+ vertices = [];
+ for(let vertex of BODY_P2){
+ vertices.push(new b2.Vec2(vertex[0] / this.SCALE, vertex[1] / this.SCALE));
+ }
+ body_p2_fd.shape.Set(vertices, BODY_P2.length);
+ body_p2_fd.density = this.DENSITY;
+ body_p2_fd.restitution = 0.0;
+ body_p2_fd.filter.categoryBits = 0x20;
+ body_p2_fd.filter.maskBits = 0x000F;
+
+ let body_p2_bd = new b2.BodyDef();
+ body_p2_bd.type = b2.Body.b2_dynamicBody;
+ body_p2_bd.position.Set(body_p2_x, init_y);
+ let body_p2 = world.CreateBody(body_p2_bd);
+ body_p2.CreateFixture(body_p2_fd);
+ body_p2.color1 = "#806682"; // [0.5, 0.4, 0.9]
+ body_p2.color2 = "#4D4D80";
+ body_p2.SetUserData(new CustomBodyUserData(true, false, "body"));
+ this.body_parts.push(body_p2);
+
+ // Revolute joint between BODY_P1 and BODY_P2
+ rjd = new b2.RevoluteJointDef();
+ rjd.Initialize(body_p1, body_p2, new b2.Vec2(body_p1_x - 16 / 2 / this.SCALE, init_y));
+ rjd.enableMotor = true;
+ rjd.enableLimit = true;
+ rjd.maxMotorTorque = this.MOTORS_TORQUE;
+ rjd.motorSpeed = 1;
+ rjd.lowerAngle = -0.15 * Math.PI;
+ rjd.upperAngle = 0.15 * Math.PI;
+ joint_motor = world.CreateJoint(rjd);
+ joint_motor.SetUserData(new CustomMotorUserData("hip", SPEED, true, 0.0, body_p2));
+ this.motors.push(joint_motor);
+
+ // BODY_P3 - TAIL
+ let body_p3_x = body_p2_x - 16 / 2 / this.SCALE - 8 / 2 / this.SCALE;
+ let body_p3_fd = new b2.FixtureDef();
+ body_p3_fd.shape = new b2.PolygonShape();
+ vertices = [];
+ for(let vertex of BODY_P3){
+ vertices.push(new b2.Vec2(vertex[0] / this.SCALE, vertex[1] / this.SCALE));
+ }
+ body_p3_fd.shape.Set(vertices, BODY_P3.length);
+ body_p3_fd.density = this.DENSITY;
+ body_p3_fd.restitution = 0.0;
+ body_p3_fd.filter.categoryBits = 0x20;
+ body_p3_fd.filter.maskBits = 0x000F;
+
+ let body_p3_bd = new b2.BodyDef();
+ body_p3_bd.type = b2.Body.b2_dynamicBody;
+ body_p3_bd.position.Set(body_p3_x, init_y);
+ let body_p3 = world.CreateBody(body_p3_bd);
+ body_p3.CreateFixture(body_p3_fd);
+ body_p3.color1 = "#806682"; // [0.5, 0.4, 0.9]
+ body_p3.color2 = "#4D4D80";
+ body_p3.SetUserData(new CustomBodyUserData(true, false, "body"));
+ this.body_parts.push(body_p3);
+ this.tail = body_p3;
+
+ // Revolute joint between BODY_P2 and BODY_P3
+ rjd = new b2.RevoluteJointDef();
+ rjd.Initialize(body_p2, body_p3, new b2.Vec2(body_p2_x - 16 / 2 / this.SCALE, init_y));
+ rjd.enableMotor = true;
+ rjd.enableLimit = true;
+ rjd.maxMotorTorque = this.MOTORS_TORQUE;
+ rjd.motorSpeed = 1;
+ rjd.lowerAngle = -0.3 * Math.PI;
+ rjd.upperAngle = 0.3 * Math.PI;
+ joint_motor = world.CreateJoint(rjd);
+ joint_motor.SetUserData(new CustomMotorUserData("knee", SPEED, true, 0.0, body_p3));
+ this.motors.push(joint_motor);
+
+ // FINS
+ let fin_fd = new b2.FixtureDef();
+ fin_fd.shape = new b2.PolygonShape();
+ vertices = [];
+ for(let vertex of FIN){
+ vertices.push(new b2.Vec2(vertex[0] / this.SCALE, vertex[1] / this.SCALE));
+ }
+ fin_fd.shape.Set(vertices, FIN.length);
+ fin_fd.density = this.DENSITY;
+ fin_fd.restitution = 0.0;
+ fin_fd.filter.categoryBits = 0x20;
+ fin_fd.filter.maskBits = 0x000F;
+
+ let fin_positions = [
+ [init_x, init_y - 22 / 2 / this.SCALE + 0.2],
+ ];
+ let fin_angle = -0.2 * Math.PI;
+ let middle_fin_x_distance = Math.sin(fin_angle) * 20 / 2 / this.SCALE;
+ let middle_fin_y_distance = Math.cos(fin_angle) * 20 / 2 / this.SCALE;
+
+ for(let fin_pos of fin_positions){
+ let current_fin_x = fin_pos[0] + middle_fin_x_distance;
+ let current_fin_y = fin_pos[1] - middle_fin_y_distance;
+
+ let fin_bd = new b2.BodyDef();
+ fin_bd.type = b2.Body.b2_dynamicBody;
+ fin_bd.position.Set(current_fin_x, current_fin_y);
+ let fin = world.CreateBody(fin_bd);
+ fin.CreateFixture(fin_fd);
+ fin.color1 = "#806682"; // [0.5, 0.4, 0.9]
+ fin.color2 = "#4D4D80";
+ fin.SetUserData(new CustomBodyUserData(true, false, "fin"));
+ this.body_parts.push(fin);
+ this.fins.push(fin);
+
+ // Revolute joint between HULL and FIN
+ rjd = new b2.RevoluteJointDef();
+ rjd.Initialize(hull, fin, new b2.Vec2(fin_pos[0], fin_pos[1]));
+ rjd.enableMotor = true;
+ rjd.enableLimit = true;
+ rjd.maxMotorTorque = this.MOTORS_TORQUE;
+ rjd.motorSpeed = 1;
+ rjd.lowerAngle = -0.3 * Math.PI;
+ rjd.upperAngle = 0.2 * Math.PI;
+ joint_motor = world.CreateJoint(rjd);
+ joint_motor.SetUserData(new CustomMotorUserData("shoulder", SPEED, true, 0.0, fin));
+ this.motors.push(joint_motor);
+ }
+ }
+}
\ No newline at end of file
diff --git a/js/bodies/swimmers/swimmer_abstract_body.js b/js/bodies/swimmers/swimmer_abstract_body.js
new file mode 100644
index 0000000000000000000000000000000000000000..44c6b7d312da552647da9f4a98e340600bdca8e0
--- /dev/null
+++ b/js/bodies/swimmers/swimmer_abstract_body.js
@@ -0,0 +1,26 @@
+/**
+ * @classdesc Abstract class for swimmer morphologies.
+ */
+class SwimmerAbstractBody extends AbstractBody{
+ /**
+ * @constructor
+ * @param scale {number} - Scale of the environment
+ * @param motors_torque {number}
+ * @param density {number} - Density of the agent's body.
+ * @param nb_steps_outside_water {number}
+ */
+ constructor(scale, motors_torque, density, nb_steps_outside_water) {
+ super(scale, motors_torque);
+ this.body_type = BodyTypesEnum.SWIMMER;
+ this.nb_steps_can_survive_outside_water = nb_steps_outside_water;
+
+ // set the embodiment's density to the same value as water so that it will be in a zero-gravity setup
+ this.DENSITY = density - 0.01; // Make it a little lighter such that it slowly goes up when no action is done
+ }
+
+ destroy(world) {
+ super.destroy(world);
+ this.fins = [];
+ this.tail = null;
+ }
+}
\ No newline at end of file
diff --git a/js/bodies/walkers/classic_bipedal_body.js b/js/bodies/walkers/classic_bipedal_body.js
new file mode 100644
index 0000000000000000000000000000000000000000..12b574a722fb6510c78789fbdd6202a426c7ee4b
--- /dev/null
+++ b/js/bodies/walkers/classic_bipedal_body.js
@@ -0,0 +1,140 @@
+HULL_POLYGONS = [
+ [[-30, +9], [+6, +9], [+34, +1], [+34, -8], [-30, -8]]
+];
+HULL_BOTTOM_WIDTH = 64;
+const SPEED_HIP = 4;
+const SPEED_KNEE = 6;
+
+/**
+ * @classdesc Bipedal Walker morphology.
+ */
+class ClassicBipedalBody extends WalkerAbstractBody {
+ /**
+ * @constructor
+ * @param scale {number} - Scale of the environment
+ * @param motors_torque {number}
+ * @param nb_steps_under_water {number}
+ * @param reset_on_hull_critical_contact {boolean}
+ */
+ constructor(scale, motors_torque=80, nb_steps_under_water=600, reset_on_hull_critical_contact=false) {
+ super(scale, motors_torque, nb_steps_under_water);
+
+ this.LEG_DOWN = 3 / this.SCALE; // 0 = center of hull
+ this.LEG_W = 8 / this.SCALE;
+ this.LEG_H = 34 / this.SCALE;
+ this.TORQUE_PENALTY = 0.00035;
+ this.reset_on_hull_critical_contact = reset_on_hull_critical_contact;
+
+ this.AGENT_WIDTH = HULL_BOTTOM_WIDTH / this.SCALE;
+ this.AGENT_HEIGHT = 17 / this.SCALE + this.LEG_H * 2 - this.LEG_DOWN;
+ this.AGENT_CENTER_HEIGHT = this.LEG_H * 2 + this.LEG_DOWN;
+
+ this.old_morphology = false;
+ }
+
+ draw(world, init_x, init_y, force_to_center){
+ let HULL_FIXTURES = [];
+ let fd_polygon;
+ let vertices;
+ let y_offset = 0;//10/this.SCALE;
+
+ for(let polygon of HULL_POLYGONS){
+ fd_polygon = new b2.FixtureDef();
+ fd_polygon.shape = new b2.PolygonShape();
+ vertices = [];
+ for(let vertex of polygon){
+ vertices.push(new b2.Vec2(vertex[0] / this.SCALE, vertex[1] / this.SCALE));
+ }
+ fd_polygon.shape.Set(vertices, polygon.length);
+ fd_polygon.density = 5.0;
+ fd_polygon.friction = 0.1;
+ fd_polygon.filter.categoryBits = 0x20;
+ fd_polygon.filter.maskBits = 0x000F; // 0.99 bouncy
+ HULL_FIXTURES.push(fd_polygon);
+ }
+
+ let LEG_FD = new b2.FixtureDef();
+ LEG_FD.shape = new b2.PolygonShape();
+ LEG_FD.shape.SetAsBox(this.LEG_W / 2, this.LEG_H / 2);
+ LEG_FD.density = 1.0;
+ LEG_FD.restitution = 0.0;
+ LEG_FD.filter.categoryBits = 0x20;
+ LEG_FD.filter.maskBits = 0x000F;
+
+ let LOWER_FD = new b2.FixtureDef();
+ LOWER_FD.shape = new b2.PolygonShape();
+ LOWER_FD.shape.SetAsBox(0.8 * this.LEG_W / 2, this.LEG_H / 2);
+ LOWER_FD.density = 1.0;
+ LOWER_FD.restitution = 0.0;
+ LOWER_FD.filter.categoryBits = 0x20;
+ LOWER_FD.filter.maskBits = 0x000F;
+
+ let hull_bd = new b2.BodyDef();
+ hull_bd.type = b2.Body.b2_dynamicBody;
+ hull_bd.position.Set(init_x, init_y + y_offset);
+ let hull = world.CreateBody(hull_bd);
+ for(let fd of HULL_FIXTURES){
+ hull.CreateFixture(fd);
+ }
+ hull.color1 = "#806682"; // [0.5, 0.4, 0.9]
+ hull.color2 = "#4D4D80"; // [0.3, 0.3, 0.5]
+ hull.ApplyForceToCenter(new b2.Vec2(force_to_center, 0), true);
+ hull.SetUserData(new CustomBodyUserData(true, this.reset_on_hull_critical_contact, "hull"));
+ this.body_parts.push(hull);
+ this.reference_head_object = hull;
+
+ // Leg and lower bodies and joints
+ for(let i of [-1, +1]){
+
+ // Leg body
+ let leg_bd = new b2.BodyDef();
+ leg_bd.type = b2.Body.b2_dynamicBody;
+ leg_bd.position.Set(init_x, init_y - this.LEG_H / 2 - this.LEG_DOWN + y_offset);
+ //leg_bd.angle = i * 0.05; // 2°
+ let leg = world.CreateBody(leg_bd);
+ leg.CreateFixture(LEG_FD);
+ leg.color1 = i == -1 ? "#9C4F82" : "#964A7D"; // [0.61, 0.31, 0.51] : [0.59, 0.29, 0.49]
+ leg.color2 = i == -1 ? "#69364F" : "#63304A"; // [0.41, 0.21, 0.31] : [0.39, 0.19, 0.29]
+ leg.SetUserData(new CustomBodyUserData(false, false,"leg"));
+ this.body_parts.push(leg);
+
+ // Leg joint motor
+ let leg_rjd = new b2.RevoluteJointDef();
+ leg_rjd.Initialize(hull, leg, new b2.Vec2(init_x, init_y - this.LEG_DOWN + y_offset));
+ leg_rjd.enableMotor = true;
+ leg_rjd.enableLimit = true;
+ leg_rjd.maxMotorTorque = this.MOTORS_TORQUE;
+ leg_rjd.motorSpeed = i;
+ leg_rjd.lowerAngle = - 0.8;
+ leg_rjd.upperAngle = 1.1;
+ let joint_motor = world.CreateJoint(leg_rjd);
+ joint_motor.SetUserData(new CustomMotorUserData("hip", SPEED_HIP, false));
+ this.motors.push(joint_motor);
+
+ // lower body
+ let lower_bd = new b2.BodyDef();
+ lower_bd.type = b2.Body.b2_dynamicBody;
+ lower_bd.position.Set(init_x, init_y - this.LEG_H * 3 / 2 - this.LEG_DOWN + y_offset);
+ //lower_bd.angle = i * 0.05; // 2°
+ let lower = world.CreateBody(lower_bd);
+ lower.CreateFixture(LOWER_FD);
+ lower.color1 = i == -1 ? "#9C4F82" : "#964A7D"; // [0.61, 0.31, 0.51] : [0.59, 0.29, 0.49]
+ lower.color2 = i == -1 ? "#69364F" : "#63304A"; // [0.41, 0.21, 0.31] : [0.39, 0.19, 0.29]
+ lower.SetUserData(new CustomBodyUserData(true, false,"lower"));
+ this.body_parts.push(lower);
+
+ // lower joint motor
+ let lower_rjd = new b2.RevoluteJointDef();
+ lower_rjd.Initialize(leg, lower, new b2.Vec2(init_x, init_y - this.LEG_DOWN - this.LEG_H + y_offset));
+ lower_rjd.enableMotor = true;
+ lower_rjd.enableLimit = true;
+ lower_rjd.maxMotorTorque = this.MOTORS_TORQUE;
+ lower_rjd.motorSpeed = 1;
+ lower_rjd.lowerAngle = - 1.6;
+ lower_rjd.upperAngle = -0.1;
+ joint_motor = world.CreateJoint(lower_rjd);
+ joint_motor.SetUserData(new CustomMotorUserData("knee", SPEED_KNEE, true, 1.0, lower));
+ this.motors.push(joint_motor);
+ }
+ }
+}
\ No newline at end of file
diff --git a/js/bodies/walkers/old_classic_bipedal_body.js b/js/bodies/walkers/old_classic_bipedal_body.js
new file mode 100644
index 0000000000000000000000000000000000000000..0abb3b49a56e4b2efa3b9d34162ecad46c8321da
--- /dev/null
+++ b/js/bodies/walkers/old_classic_bipedal_body.js
@@ -0,0 +1,140 @@
+HULL_POLYGONS = [
+ [[-30, +9], [+6, +9], [+34, +1], [+34, -8], [-30, -8]]
+];
+HULL_BOTTOM_WIDTH = 64;
+SPEED_HIP = 4;
+SPEED_KNEE = 6;
+
+class OldClassicBipedalBody extends WalkerAbstractBody{
+ constructor(scale, nb_steps_under_water=600, reset_on_hull_critical_contact=true){
+ super(scale, 80, nb_steps_under_water);
+
+ this.LEG_DOWN = -8 / this.SCALE; // 0 = center of hull
+ this.LEG_W = 8 / this.SCALE;
+ this.LEG_H = 34 / this.SCALE;
+ this.TORQUE_PENALTY = 0.00035;
+ this.reset_on_hull_critical_contact = reset_on_hull_critical_contact;
+
+ // Approximative...
+ this.AGENT_WIDTH = HULL_BOTTOM_WIDTH / this.SCALE;
+ this.AGENT_HEIGHT = 17 / this.SCALE + this.LEG_H * 2 - this.LEG_DOWN + 0.5;
+ this.AGENT_CENTER_HEIGHT = this.LEG_H * 2 + this.LEG_DOWN + 0.5;
+
+ this.old_morphology = true;
+
+ this.body_parts = [];
+ this.nb_motors = 4;
+ this.motors = [];
+ this.state_size = this.nb_motors * 2 + 2;
+ }
+
+ draw(world, init_x, init_y, force_to_center){
+ let HULL_FIXTURES = [];
+ let fd_polygon;
+ let vertices;
+ let y_offset = 0//10/this.SCALE;
+
+ for(let polygon of HULL_POLYGONS){
+ fd_polygon = new b2.FixtureDef();
+ fd_polygon.shape = new b2.PolygonShape();
+ vertices = [];
+ for(let vertex of polygon){
+ vertices.push(new b2.Vec2(vertex[0] / this.SCALE, vertex[1] / this.SCALE));
+ }
+ fd_polygon.shape.Set(vertices, polygon.length);
+ fd_polygon.density = 5.0;
+ fd_polygon.friction = 0.1;
+ fd_polygon.filter.categoryBits = 0x20;
+ fd_polygon.filter.maskBits = 0x000F; // 0.99 bouncy
+ HULL_FIXTURES.push(fd_polygon);
+ }
+
+ let LEG_FD = new b2.FixtureDef();
+ LEG_FD.shape = new b2.PolygonShape();
+ LEG_FD.shape.SetAsBox(this.LEG_W / 2, this.LEG_H / 2);
+ LEG_FD.density = 1.0;
+ LEG_FD.restitution = 0.0;
+ LEG_FD.filter.categoryBits = 0x20;
+ LEG_FD.filter.maskBits = 0x000F;
+
+ let LOWER_FD = new b2.FixtureDef();
+ LOWER_FD.shape = new b2.PolygonShape();
+ LOWER_FD.shape.SetAsBox(0.8 * this.LEG_W / 2, this.LEG_H / 2);
+ LOWER_FD.density = 1.0;
+ LOWER_FD.restitution = 0.0;
+ LOWER_FD.filter.categoryBits = 0x20;
+ LOWER_FD.filter.maskBits = 0x000F;
+
+ let hull_bd = new b2.BodyDef();
+ hull_bd.type = b2.Body.b2_dynamicBody;
+ hull_bd.position.Set(init_x, init_y + y_offset);
+ let hull = world.CreateBody(hull_bd);
+ for(let fd of HULL_FIXTURES){
+ hull.CreateFixture(fd);
+ }
+ hull.color1 = "#806682"; // [0.5, 0.4, 0.9]
+ hull.color2 = "#4D4D80"; // [0.3, 0.3, 0.5]
+ //hull.ApplyForceToCenter(new b2.Vec2(force_to_center, 0), true);
+ hull.SetUserData(new CustomBodyUserData(true, this.reset_on_hull_critical_contact, "hull"));
+ this.body_parts.push(hull);
+ this.reference_head_object = hull;
+
+ // Leg and lower bodies and joints
+ for(let i of [-1, +1]){
+
+ // Leg body
+ let leg_bd = new b2.BodyDef();
+ leg_bd.type = b2.Body.b2_dynamicBody;
+ leg_bd.position.Set(init_x, init_y - this.LEG_H / 2 - this.LEG_DOWN + y_offset);
+ leg_bd.angle = i * 0.05; // 2°
+ let leg = world.CreateBody(leg_bd);
+ leg.CreateFixture(LEG_FD);
+ leg.color1 = i == -1 ? "#9C4F82" : "#964A7D"; // [0.61, 0.31, 0.51] : [0.59, 0.29, 0.49]
+ leg.color2 = i == -1 ? "#69364F" : "#63304A"; // [0.41, 0.21, 0.31] : [0.39, 0.19, 0.29]
+ leg.SetUserData(new CustomBodyUserData(false, false,"leg"));
+ this.body_parts.push(leg);
+
+ // Leg joint motor
+ let leg_rjd = new b2.RevoluteJointDef();
+ leg_rjd.Initialize(hull, leg, new b2.Vec2(init_x, init_y - this.LEG_DOWN + y_offset));
+ leg_rjd.localAnchorA = new b2.Vec2(0, this.LEG_DOWN);
+ leg_rjd.localAnchorB = new b2.Vec2(0, this.LEG_H / 2);
+ leg_rjd.enableMotor = true;
+ leg_rjd.enableLimit = true;
+ leg_rjd.maxMotorTorque = this.MOTORS_TORQUE;
+ leg_rjd.motorSpeed = i;
+ leg_rjd.lowerAngle = - 0.8;
+ leg_rjd.upperAngle = 1.1;
+ let joint_motor = world.CreateJoint(leg_rjd);
+ joint_motor.SetUserData(new CustomMotorUserData("hip", SPEED_HIP, false));
+ this.motors.push(joint_motor);
+
+ // lower body
+ let lower_bd = new b2.BodyDef();
+ lower_bd.type = b2.Body.b2_dynamicBody;
+ lower_bd.position.Set(init_x, init_y - this.LEG_H * 3 / 2 - this.LEG_DOWN + y_offset);
+ lower_bd.angle = i * 0.05; // 2°
+ let lower = world.CreateBody(lower_bd);
+ lower.CreateFixture(LOWER_FD);
+ lower.color1 = i == -1 ? "#9C4F82" : "#964A7D"; // [0.61, 0.31, 0.51] : [0.59, 0.29, 0.49]
+ lower.color2 = i == -1 ? "#69364F" : "#63304A"; // [0.41, 0.21, 0.31] : [0.39, 0.19, 0.29]
+ lower.SetUserData(new CustomBodyUserData(true, false,"lower"));
+ this.body_parts.push(lower);
+
+ // lower joint motor
+ let lower_rjd = new b2.RevoluteJointDef();
+ lower_rjd.Initialize(leg, lower, new b2.Vec2(init_x, init_y - this.LEG_DOWN - this.LEG_H + y_offset));
+ lower_rjd.localAnchorA = new b2.Vec2(0, - this.LEG_H / 2);
+ lower_rjd.localAnchorB = new b2.Vec2(0, this.LEG_H / 2);
+ lower_rjd.enableMotor = true;
+ lower_rjd.enableLimit = true;
+ lower_rjd.maxMotorTorque = this.MOTORS_TORQUE;
+ lower_rjd.motorSpeed = 1;
+ lower_rjd.lowerAngle = - 1.6;
+ lower_rjd.upperAngle = -0.1;
+ joint_motor = world.CreateJoint(lower_rjd);
+ joint_motor.SetUserData(new CustomMotorUserData("knee", SPEED_KNEE, true, 1.0, lower));
+ this.motors.push(joint_motor);
+ }
+ }
+}
\ No newline at end of file
diff --git a/js/bodies/walkers/spider_body.js b/js/bodies/walkers/spider_body.js
new file mode 100644
index 0000000000000000000000000000000000000000..83121b88c8956b392bb59922ee2c9a94e38db44f
--- /dev/null
+++ b/js/bodies/walkers/spider_body.js
@@ -0,0 +1,189 @@
+const MAIN_BODY_POLYGONS = [
+ [[-10, +10], [+10, +10], [+10, -10], [-10, -10]]
+]
+const MAIN_BODY_BOTTOM_WIDTH = 20
+const SPIDER_SPEED_HIP = 4
+const SPIDER_SPEED_KNEE = 6
+
+/**
+ * @classdesc Spider morphology.
+ */
+class SpiderBody extends WalkerAbstractBody {
+
+ constructor(scale, motors_torque=100, nb_pairs_of_legs=2,
+ nb_steps_under_water=600, reset_on_hull_critical_contact=false){
+ super(scale, motors_torque, nb_steps_under_water);
+
+ this.LEG_DOWN = 4 / this.SCALE;
+ this.LEG_W = 6 / this.SCALE;
+ this.LEG_H = 20 / this.SCALE;
+ this.reset_on_critical_contact = reset_on_hull_critical_contact;
+
+ this.nb_pairs_of_legs = nb_pairs_of_legs;
+
+ this.TORQUE_PENALTY = 0.00035 / this.nb_pairs_of_legs;
+
+ // not exacts but works
+ this.AGENT_WIDTH = MAIN_BODY_BOTTOM_WIDTH / this.SCALE + this.LEG_H * 4;
+ this.AGENT_HEIGHT = 20 / this.SCALE + this.LEG_H * 2;
+ this.AGENT_CENTER_HEIGHT = this.LEG_H + this.LEG_DOWN;
+ }
+
+ draw(world, init_x, init_y, force_to_center){
+
+ let fd_polygon;
+ let vertices;
+
+ /* Creates the different fixtures */
+
+ let MAIN_BODY_FIXTURES = [];
+ for(let polygon of MAIN_BODY_POLYGONS){
+ fd_polygon = new b2.FixtureDef();
+ fd_polygon.shape = new b2.PolygonShape();
+ vertices = [];
+ for(let vertex of polygon){
+ vertices.push(new b2.Vec2(vertex[0] / this.SCALE, vertex[1] / this.SCALE));
+ }
+ fd_polygon.shape.Set(vertices, polygon.length);
+ fd_polygon.density = 5.0;
+ fd_polygon.friction = 0.1;
+ fd_polygon.filter.categoryBits = 0x20;
+ fd_polygon.filter.maskBits = 0x000F;
+ MAIN_BODY_FIXTURES.push(fd_polygon);
+ }
+
+ let LEG_FD = new b2.FixtureDef();
+ LEG_FD.shape = new b2.PolygonShape();
+ LEG_FD.shape.SetAsBox(this.LEG_W / 2, this.LEG_H / 2);
+ LEG_FD.density = 1.0;
+ LEG_FD.restitution = 0.0;
+ LEG_FD.filter.categoryBits = 0x20;
+ LEG_FD.filter.maskBits = 0x000F;
+
+ let LOWER_FD = new b2.FixtureDef();
+ LOWER_FD.shape = new b2.PolygonShape();
+ LOWER_FD.shape.SetAsBox(0.8 * this.LEG_W / 2, this.LEG_H / 2);
+ LOWER_FD.density = 1.0;
+ LOWER_FD.restitution = 0.0;
+ LOWER_FD.filter.categoryBits = 0x20;
+ LOWER_FD.filter.maskBits = 0x000F;
+
+ /* Creates the different bodies */
+
+ // Main body
+ let main_body_bd = new b2.BodyDef();
+ main_body_bd.type = b2.Body.b2_dynamicBody;
+ main_body_bd.position.Set(init_x, init_y);
+ let main_body = world.CreateBody(main_body_bd);
+ for(let fd of MAIN_BODY_FIXTURES){
+ main_body.CreateFixture(fd);
+ }
+ main_body.color1 = "#803366"; // [0.5, 0.2, 0.4]
+ main_body.color2 = "#4D1A33"; // [0.3, 0.1, 0.2]
+ main_body.ApplyForceToCenter(new b2.Vec2(force_to_center, 0), true);
+ main_body.SetUserData(new CustomBodyUserData(true, this.reset_on_hull_critical_contact, "main_body"));
+ this.body_parts.push(main_body);
+ this.reference_head_object = main_body;
+
+ // Legs bodies and joints
+ let legs_coef = [];
+ for(let i = 0; i < this.nb_pairs_of_legs; i++){
+ legs_coef.push(+1, -1);
+ }
+ for(let i of legs_coef){
+
+ // First part of the leg
+ let upper_leg_angle = 0.25 * Math.PI * i;
+ let upper_leg_x_distance = Math.sin(upper_leg_angle) * this.LEG_H / 2;
+ let upper_leg_y_distance = Math.cos(upper_leg_angle) * this.LEG_H / 2;
+ let upper_leg_x = init_x - i * MAIN_BODY_BOTTOM_WIDTH / this.SCALE / 2 - upper_leg_x_distance;
+ let upper_leg_y = init_y + upper_leg_y_distance - this.LEG_DOWN;
+
+ let upper_leg_bd = new b2.BodyDef();
+ upper_leg_bd.type = b2.Body.b2_dynamicBody;
+ upper_leg_bd.position.Set(upper_leg_x, upper_leg_y);
+ upper_leg_bd.angle = upper_leg_angle;
+ let upper_leg = world.CreateBody(upper_leg_bd);
+ upper_leg.CreateFixture(LEG_FD);
+ upper_leg.color1 = "#B36699"; // [0.7, 0.4, 0.6]
+ upper_leg.color2 = "#804D66"; // [0.5, 0.3, 0.4]
+ upper_leg.SetUserData(new CustomBodyUserData(false, false,"upper_leg"));
+ this.body_parts.push(upper_leg);
+
+ // Upper leg joint motor
+ let upper_leg_rjd = new b2.RevoluteJointDef();
+ 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));
+ upper_leg_rjd.enableMotor = true;
+ upper_leg_rjd.enableLimit = true;
+ upper_leg_rjd.maxMotorTorque = this.MOTORS_TORQUE;
+ upper_leg_rjd.motorSpeed = 1;
+ upper_leg_rjd.lowerAngle = - 0.1 * Math.PI;
+ upper_leg_rjd.upperAngle = 0.1 * Math.PI;
+ let joint_motor = world.CreateJoint(upper_leg_rjd);
+ joint_motor.SetUserData(new CustomMotorUserData("hip", SPIDER_SPEED_HIP, false));
+ this.motors.push(joint_motor);
+
+ // Second part of the leg
+ let middle_leg_angle = 0.7 * Math.PI * i;
+ let middle_leg_x_distance = Math.sin(middle_leg_angle) * this.LEG_H / 2;
+ let middle_leg_y_distance = - Math.cos(middle_leg_angle) * this.LEG_H / 2;
+ let middle_leg_x = upper_leg_x - upper_leg_x_distance - middle_leg_x_distance;
+ let middle_leg_y = upper_leg_y + upper_leg_y_distance - middle_leg_y_distance;
+
+ let middle_leg_bd = new b2.BodyDef();
+ middle_leg_bd.type = b2.Body.b2_dynamicBody;
+ middle_leg_bd.position.Set(middle_leg_x, middle_leg_y);
+ middle_leg_bd.angle = middle_leg_angle;
+ let middle_leg = world.CreateBody(middle_leg_bd);
+ middle_leg.CreateFixture(LEG_FD);
+ middle_leg.color1 = "#B36699"; // [0.7, 0.4, 0.6]
+ middle_leg.color2 = "#804D66"; // [0.5, 0.3, 0.4]
+ middle_leg.SetUserData(new CustomBodyUserData(false, false,"middle_leg"));
+ this.body_parts.push(middle_leg);
+
+ // middle_leg joint motor
+ let middle_leg_rjd = new b2.RevoluteJointDef();
+ 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));
+ middle_leg_rjd.enableMotor = true;
+ middle_leg_rjd.enableLimit = true;
+ middle_leg_rjd.maxMotorTorque = this.MOTORS_TORQUE;
+ middle_leg_rjd.motorSpeed = 1;
+ middle_leg_rjd.lowerAngle = - 0.15 * Math.PI;
+ middle_leg_rjd.upperAngle = 0.15 * Math.PI;
+ joint_motor = world.CreateJoint(middle_leg_rjd);
+ joint_motor.SetUserData(new CustomMotorUserData("hip", SPIDER_SPEED_HIP,false));
+ this.motors.push(joint_motor);
+
+ // Third part of the leg
+ let lower_leg_angle = 0.9 * Math.PI * i;
+ let lower_leg_x_distance = Math.sin(lower_leg_angle) * this.LEG_H / 2;
+ let lower_leg_y_distance = - Math.cos(lower_leg_angle) * this.LEG_H / 2;
+ let lower_leg_x = middle_leg_x - middle_leg_x_distance - lower_leg_x_distance;
+ let lower_leg_y = middle_leg_y - middle_leg_y_distance - lower_leg_y_distance;
+
+ let lower_leg_bd = new b2.BodyDef();
+ lower_leg_bd.type = b2.Body.b2_dynamicBody;
+ lower_leg_bd.position.Set(lower_leg_x, lower_leg_y);
+ lower_leg_bd.angle = lower_leg_angle;
+ let lower_leg = world.CreateBody(lower_leg_bd);
+ lower_leg.CreateFixture(LOWER_FD);
+ lower_leg.color1 = "#B36699"; // [0.7, 0.4, 0.6]
+ lower_leg.color2 = "#804D66"; // [0.5, 0.3, 0.4]
+ lower_leg.SetUserData(new CustomBodyUserData(true, false,"lower_leg"));
+ this.body_parts.push(lower_leg);
+
+ // lower_leg joint motor
+ let lower_leg_rjd = new b2.RevoluteJointDef();
+ 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));
+ lower_leg_rjd.enableMotor = true;
+ lower_leg_rjd.enableLimit = true;
+ lower_leg_rjd.maxMotorTorque = this.MOTORS_TORQUE;
+ lower_leg_rjd.motorSpeed = 1;
+ lower_leg_rjd.lowerAngle = - 0.2 * Math.PI;
+ lower_leg_rjd.upperAngle = 0.2 * Math.PI;
+ joint_motor = world.CreateJoint(lower_leg_rjd);
+ joint_motor.SetUserData(new CustomMotorUserData("knee", SPIDER_SPEED_KNEE,true, 0.0, lower_leg));
+ this.motors.push(joint_motor);
+ }
+ }
+};
\ No newline at end of file
diff --git a/js/bodies/walkers/walker_abstract_body.js b/js/bodies/walkers/walker_abstract_body.js
new file mode 100644
index 0000000000000000000000000000000000000000..6ae12640f34b30156ceac8c06c22d351d55820f1
--- /dev/null
+++ b/js/bodies/walkers/walker_abstract_body.js
@@ -0,0 +1,16 @@
+/**
+ * @classdesc Abstract class for walker morphologies.
+ */
+class WalkerAbstractBody extends AbstractBody{
+ /**
+ * @constructor
+ * @param scale {number} - Scale of the environment
+ * @param motors_torque {number}
+ * @param nb_steps_under_water {number}
+ */
+ constructor(scale, motors_torque, nb_steps_under_water) {
+ super(scale, motors_torque);
+ this.body_type = BodyTypesEnum.WALKER;
+ this.nb_steps_can_survive_under_water = nb_steps_under_water;
+ }
+}
\ No newline at end of file
diff --git a/js/box2d.js b/js/box2d.js
new file mode 100644
index 0000000000000000000000000000000000000000..b4a2f283f700fb7c91d828806a0dcfdef87ba43c
--- /dev/null
+++ b/js/box2d.js
@@ -0,0 +1,11581 @@
+/** https://github.com/kripken/box2d.js v2.3.1 */
+
+var performance = {
+ now: function () {
+ return +new Date()
+ }
+}
+Function.prototype._extend = function (parent) {
+ this.prototype.parent = parent;
+ for (var x in parent.prototype) {
+ if (!this.prototype[x]) {
+ this.prototype[x] = parent.prototype[x]
+ }
+ }
+};
+Function.prototype._implement = function (parent) {
+ return this._extend(parent)
+};
+var b2Profiler = (function () {
+
+ function profileStruct(name, parent) {
+ this.name = name;
+ this.parent = parent;
+ this.children = {};
+ this.startTime = 0;
+ this.elapsedTime = 0;
+ this.totalTime = 0;
+ this.running = false;
+ this.childrenCount = 0
+ }
+ profileStruct.prototype = {
+ start: function () {
+ this.startTime = performance.now();
+ this.running = true
+ },
+ stop: function (reset) {
+ if (!this.running) {
+ return
+ }
+ this.running = false;
+ this.elapsedTime += performance.now() - this.startTime;
+ if (reset) {
+ this.start()
+ }
+ for (var x in this.children) {
+ this.children[x].stop()
+ }
+ },
+ reset: function (dontRun) {
+ if (!dontRun) {
+ this.running = true;
+ this.totalTime += this.elapsedTime;
+ this.start()
+ }
+ this.elapsedTime = 0;
+ for (var x in this.children) {
+ this.children[x].reset(true)
+ }
+ }
+ };
+ var profiles = [];
+ var root = new profileStruct("root");
+
+ function create(name, parent) {
+ if (!profiles) {
+ throw new Error("late profile creation not allowed")
+ }
+ var s = new profileStruct(name, parent || "root");
+ profiles.push(s);
+ return s
+ }
+
+ function destroy(profile) {
+ profile.childrenCount--;
+ delete profile.children[profile.name]
+ }
+
+ function recursiveParentCheck(node, profile) {
+ if (node.name === profile.parent) {
+ return node
+ }
+ for (var x in node.children) {
+ var n;
+ if (n = recursiveParentCheck(node.children[x], profile)) {
+ return n
+ }
+ }
+ return null
+ }
+
+ function init() {
+ while (profiles.length) {
+ var p = profiles.pop();
+ if (!(p.parentNode = recursiveParentCheck(root, p))) {
+ profiles.unshift(p)
+ } else {
+ p.parentNode.children[p.name] = p;
+ p.parentNode.childrenCount++
+ }
+ }
+ profiles = null
+ }
+
+ function resetAll() {
+ root.reset(true)
+ }
+ return {
+ create: create,
+ destroy: destroy,
+ init: init,
+ reset: resetAll,
+ profileRoot: root
+ }
+}());
+"use strict";
+var b2_maxFloat = Number.MAX_VALUE;
+var b2_epsilon = 2.220446049250313e-16;
+var b2_pi = Math.PI;
+var b2_maxManifoldPoints = 2;
+var b2_maxPolygonVertices = 8;
+var b2_aabbExtension = 0.1;
+var b2_aabbMultiplier = 2;
+var b2_linearSlop = 0.005;
+var b2_angularSlop = (2 / 180 * b2_pi);
+var b2_polygonRadius = (2 * b2_linearSlop);
+var b2_maxSubSteps = 8;
+var b2_maxTOIContacts = 32;
+var b2_velocityThreshold = 1;
+var b2_maxLinearCorrection = 0.2;
+var b2_maxAngularCorrection = (8 / 180 * b2_pi);
+var b2_maxTranslation = 2;
+var b2_maxTranslationSquared = (b2_maxTranslation * b2_maxTranslation);
+var b2_maxRotation = (0.5 * b2_pi);
+var b2_maxRotationSquared = (b2_maxRotation * b2_maxRotation);
+var b2_baumgarte = 0.2;
+var b2_toiBaugarte = 0.75;
+var b2_timeToSleep = 0.5;
+var b2_linearSleepTolerance = 0.01;
+var b2_angularSleepTolerance = (2 / 180 * b2_pi);
+
+function b2Version(ma, mi, re) {
+ this.major = ma;
+ this.minor = mi;
+ this.revision = re
+}
+b2Version.prototype = {
+ toString: function () {
+ return this.major + "." + this.minor + "." + this.revision
+ }
+};
+var b2_version = new b2Version(2, 3, 1);
+"use strict";
+
+function b2IsValid(x) {
+ return isFinite(x) && !isNaN(x)
+}
+var sqrtf = Math.sqrt;
+var atan2f = Math.atan2;
+var sinf = Math.sin;
+var cosf = Math.cos;
+var floorf = Math.floor;
+var ceilf = Math.ceil;
+var b2Sqrt = sqrtf;
+var b2Atan2 = atan2f;
+
+function b2InvSqrt(x) {
+ return 1 / sqrtf(x)
+}
+
+function b2Vec2(x, y) {
+ if (typeof (x) !== "undefined") {
+ this.x = x;
+ this.y = y
+ } else {
+ this.x = this.y = 0
+ }
+}
+b2Vec2.prototype = {
+ Clone: function () {
+ return new b2Vec2(this.x, this.y)
+ },
+ SetZero: function () {
+ this.x = 0;
+ this.y = 0;
+ return this
+ },
+ Set: function (x_, y_) {
+ this.x = x_;
+ this.y = y_;
+ return this
+ },
+ Assign: function (l) {
+ this.x = l.x;
+ this.y = l.y;
+ return this
+ },
+ Negate: function () {
+ var v = new b2Vec2();
+ v.Set(-this.x, -this.y);
+ return v
+ },
+ get_i: function (i) {
+ switch (i) {
+ case 0:
+ return this.x;
+ case 1:
+ return this.y
+ }
+ },
+ set_i: function (i, v) {
+ switch (i) {
+ case 0:
+ return this.x = v;
+ case 1:
+ return this.y = v
+ }
+ },
+ Add: function (v) {
+ this.x += v.x;
+ this.y += v.y;
+ return this
+ },
+ Subtract: function (v) {
+ this.x -= v.x;
+ this.y -= v.y;
+ return this
+ },
+ Multiply: function (a) {
+ this.x *= a;
+ this.y *= a;
+ return this
+ },
+ Length: function () {
+ return b2Sqrt(this.x * this.x + this.y * this.y)
+ },
+ LengthSquared: function () {
+ return this.x * this.x + this.y * this.y
+ },
+ Normalize: function () {
+ var length = this.Length();
+ if (length < b2_epsilon) {
+ return 0
+ }
+ var invLength = 1 / length;
+ this.x *= invLength;
+ this.y *= invLength;
+ return length
+ },
+ IsValid: function () {
+ return b2IsValid(this.x) && b2IsValid(this.y)
+ },
+ Skew: function () {
+ return new b2Vec2(-this.y, this.x)
+ },
+ _serialize: function (out) {
+ var obj = out || [];
+ obj[0] = this.x;
+ obj[1] = this.y;
+ return obj
+ },
+ _deserialize: function (data) {
+ this.x = data[0];
+ this.y = data[1]
+ }
+};
+b2Vec2.Add = function (a, b) {
+ return new b2Vec2(a.x + b.x, a.y + b.y)
+};
+b2Vec2.Subtract = function (a, b) {
+ return new b2Vec2(a.x - b.x, a.y - b.y)
+};
+b2Vec2.Equals = function (a, b) {
+ return a.x == b.x && a.y == b.y
+};
+b2Vec2.Multiply = function (s, a) {
+ return new b2Vec2(s * a.x, s * a.y)
+};
+b2Vec2.Negate = function (a) {
+ return new b2Vec2(-a.x, -a.y)
+};
+
+function b2Vec3(x, y, z) {
+ if (typeof (x) !== "undefined") {
+ this.x = x;
+ this.y = y;
+ this.z = z
+ }
+}
+b2Vec3.prototype = {
+ Clone: function () {
+ return new b2Vec3(this.x, this.y, this.z)
+ },
+ SetZero: function () {
+ this.x = 0;
+ this.y = 0;
+ this.z = 0
+ },
+ Set: function (x_, y_, z_) {
+ this.x = x_;
+ this.y = y_;
+ this.z = z_
+ },
+ Negate: function () {
+ var v = new b2Vec3();
+ v.Set(-this.x, -this.y, -this.z);
+ return v
+ },
+ Add: function (v) {
+ this.x += v.x;
+ this.y += v.y;
+ this.z += v.z
+ },
+ Subtract: function (v) {
+ this.x -= v.x;
+ this.y -= v.y;
+ this.z -= v.z
+ },
+ Multiply: function (s) {
+ this.x *= s;
+ this.y *= s;
+ this.z *= s
+ },
+ x: 0,
+ y: 0,
+ z: 0
+};
+b2Vec3.Multiply = function (s, a) {
+ return new b2Vec3(s * a.x, s * a.y, s * a.z)
+};
+b2Vec3.Add = function (a, b) {
+ return new b2Vec3(a.x + b.x, a.y + b.y, a.z + b.z)
+};
+b2Vec3.Subtract = function (a, b) {
+ return new b2Vec3(a.x - b.x, a.y - b.y, a.z - b.z)
+};
+
+function b2Mat22(c1, c2) {
+ this.ex = c1 ? c1.Clone() : new b2Vec2();
+ this.ey = c2 ? c2.Clone() : new b2Vec2()
+}
+b2Mat22.prototype = {
+ Set: function (c1, c2) {
+ this.ex.Assign(c1);
+ this.ey.Assign(c2)
+ },
+ Assign: function (mat) {
+ this.ex.Assign(mat.ex);
+ this.ey.Assign(mat.ey)
+ },
+ SetIdentity: function () {
+ this.ex.x = 1;
+ this.ey.x = 0;
+ this.ex.y = 0;
+ this.ey.y = 1
+ },
+ SetZero: function () {
+ this.ex.x = 0;
+ this.ey.x = 0;
+ this.ex.y = 0;
+ this.ey.y = 0
+ },
+ GetInverse: function () {
+ var a = this.ex.x,
+ b = this.ey.x,
+ c = this.ex.y,
+ d = this.ey.y;
+ var B = new b2Mat22();
+ var det = a * d - b * c;
+ if (det != 0) {
+ det = 1 / det
+ }
+ B.ex.x = det * d;
+ B.ey.x = -det * b;
+ B.ex.y = -det * c;
+ B.ey.y = det * a;
+ return B
+ },
+ Solve: function (b) {
+ var a11 = this.ex.x,
+ a12 = this.ey.x,
+ a21 = this.ex.y,
+ a22 = this.ey.y;
+ var det = a11 * a22 - a12 * a21;
+ if (det != 0) {
+ det = 1 / det
+ }
+ var x = new b2Vec2();
+ x.x = det * (a22 * b.x - a12 * b.y);
+ x.y = det * (a11 * b.y - a21 * b.x);
+ return x
+ }
+};
+b2Mat22.Add = function (A, B) {
+ return new b2Mat22(b2Vec2.Add(A.ex, B.ex), b2Vec2.Add(A.ey, B.ey))
+};
+
+function b2Mat33(c1, c2, c3) {
+ this.ex = c1 ? c1.Clone() : new b2Vec3();
+ this.ey = c2 ? c2.Clone() : new b2Vec3();
+ this.ez = c3 ? c3.Clone() : new b2Vec3()
+}
+b2Mat33.prototype = {
+ SetZero: function () {
+ this.ex.SetZero();
+ this.ey.SetZero();
+ this.ez.SetZero()
+ },
+ Solve33: function (b) {
+ var det = b2Dot_v3_v3(this.ex, b2Cross_v3_v3(this.ey, this.ez));
+ if (det != 0) {
+ det = 1 / det
+ }
+ var x = new b2Vec3();
+ x.x = det * b2Dot_v3_v3(b, b2Cross_v3_v3(this.ey, this.ez));
+ x.y = det * b2Dot_v3_v3(this.ex, b2Cross_v3_v3(b, this.ez));
+ x.z = det * b2Dot_v3_v3(this.ex, b2Cross_v3_v3(this.ey, b));
+ return x
+ },
+ Solve22: function (b) {
+ var a11 = this.ex.x,
+ a12 = this.ey.x,
+ a21 = this.ex.y,
+ a22 = this.ey.y;
+ var det = a11 * a22 - a12 * a21;
+ if (det != 0) {
+ det = 1 / det
+ }
+ var x = new b2Vec2();
+ x.x = det * (a22 * b.x - a12 * b.y);
+ x.y = det * (a11 * b.y - a21 * b.x);
+ return x
+ },
+ GetInverse22: function (M) {
+ var a = this.ex.x,
+ b = this.ey.x,
+ c = this.ex.y,
+ d = this.ey.y;
+ var det = a * d - b * c;
+ if (det != 0) {
+ det = 1 / det
+ }
+ M.ex.x = det * d;
+ M.ey.x = -det * b;
+ M.ex.z = 0;
+ M.ex.y = -det * c;
+ M.ey.y = det * a;
+ M.ey.z = 0;
+ M.ez.x = 0;
+ M.ez.y = 0;
+ M.ez.z = 0
+ },
+ GetSymInverse33: function (M) {
+ var det = b2Dot_v3_v3(this.ex, b2Cross_v3_v3(this.ey, this.ez));
+ if (det != 0) {
+ det = 1 / det
+ }
+ var a11 = this.ex.x,
+ a12 = this.ey.x,
+ a13 = this.ez.x;
+ var a22 = this.ey.y,
+ a23 = this.ez.y;
+ var a33 = this.ez.z;
+ M.ex.x = det * (a22 * a33 - a23 * a23);
+ M.ex.y = det * (a13 * a23 - a12 * a33);
+ M.ex.z = det * (a12 * a23 - a13 * a22);
+ M.ey.x = M.ex.y;
+ M.ey.y = det * (a11 * a33 - a13 * a13);
+ M.ey.z = det * (a13 * a12 - a11 * a23);
+ M.ez.x = M.ex.z;
+ M.ez.y = M.ey.z;
+ M.ez.z = det * (a11 * a22 - a12 * a12)
+ }
+};
+
+function b2Rot(angle, c) {
+ if (typeof (c) !== "undefined") {
+ this.s = angle;
+ this.c = c
+ } else {
+ if (typeof (angle) !== "undefined") {
+ this.Set(angle)
+ }
+ }
+}
+b2Rot.prototype = {
+ Clone: function () {
+ return new b2Rot(this.s, this.c)
+ },
+ Assign: function (l) {
+ this.s = l.s;
+ this.c = l.c
+ },
+ Set: function (x) {
+ this.s = sinf(x);
+ this.c = cosf(x)
+ },
+ SetIdentity: function () {
+ this.s = 0;
+ this.c = 1
+ },
+ GetAngle: function () {
+ return b2Atan2(this.s, this.c)
+ },
+ GetXAxis: function () {
+ return new b2Vec2(this.c, this.s)
+ },
+ GetYAxis: function () {
+ return new b2Vec2(-this.s, this.c)
+ },
+ s: 0,
+ c: 1
+};
+
+function b2Transform(position, rotation) {
+ this.p = new b2Vec2();
+ this.q = new b2Rot();
+ if (position) {
+ this.p.Assign(position);
+ this.q.Assign(rotation)
+ }
+}
+b2Transform.prototype = {
+ Clone: function () {
+ var xf = new b2Transform(this.p, this.q);
+ return xf
+ },
+ Assign: function (xf) {
+ this.p.Assign(xf.p);
+ this.q.Assign(xf.q)
+ },
+ SetIdentity: function () {
+ this.p.SetZero();
+ this.q.SetIdentity()
+ },
+ Set: function (position, angle) {
+ this.p.Assign(position);
+ this.q.Set(angle)
+ }
+};
+
+function b2Sweep() {
+ this.localCenter = new b2Vec2();
+ this.c0 = new b2Vec2();
+ this.c = new b2Vec2()
+}
+b2Sweep.prototype = {
+ Assign: function (sweep) {
+ this.localCenter.Assign(sweep.localCenter);
+ this.c0.Assign(sweep.c0);
+ this.c.Assign(sweep.c);
+ this.a = sweep.a;
+ this.a0 = sweep.a0;
+ this.alpha0 = sweep.alpha0
+ },
+ Clone: function () {
+ var sweep = new b2Sweep();
+ sweep.localCenter.Assign(this.localCenter);
+ sweep.c0.Assign(this.c0);
+ sweep.c.Assign(this.c);
+ sweep.a = this.a;
+ sweep.a0 = this.a0;
+ sweep.alpha0 = this.alpha0;
+ return sweep
+ },
+ GetTransform: function (xf, beta) {
+ xf.p.x = ((1 - beta) * this.c0.x) + (beta * this.c.x);
+ xf.p.y = ((1 - beta) * this.c0.y) + (beta * this.c.y);
+ var angle = (1 - beta) * this.a0 + beta * this.a;
+ xf.q.Set(angle);
+ xf.p.x -= xf.q.c * this.localCenter.x - xf.q.s * this.localCenter.y;
+ xf.p.y -= xf.q.s * this.localCenter.x + xf.q.c * this.localCenter.y
+ },
+ Advance: function (alpha) {
+ var beta = (alpha - this.alpha0) / (1 - this.alpha0);
+ this.c0.Add(b2Vec2.Multiply(beta, b2Vec2.Subtract(this.c, this.c0)));
+ this.a0 += beta * (this.a - this.a0);
+ this.alpha0 = alpha
+ },
+ Normalize: function () {
+ var twoPi = 2 * b2_pi;
+ var d = twoPi * floorf(this.a0 / twoPi);
+ this.a0 -= d;
+ this.a -= d
+ },
+ a0: 0,
+ a: 0,
+ alpha0: 0
+};
+
+function b2Dot_v2_v2(a, b) {
+ return a.x * b.x + a.y * b.y
+}
+
+function b2Cross_v2_v2(a, b) {
+ return a.x * b.y - a.y * b.x
+}
+
+function b2Cross_v2_f(a, s) {
+ return new b2Vec2(s * a.y, -s * a.x)
+}
+
+function b2Cross_f_v2(s, a) {
+ return new b2Vec2(-s * a.y, s * a.x)
+}
+
+function b2Mul_m22_v2(A, v) {
+ return new b2Vec2(A.ex.x * v.x + A.ey.x * v.y, A.ex.y * v.x + A.ey.y * v.y)
+}
+
+function b2MulT_m22_v2(A, v) {
+ return new b2Vec2(b2Dot_v2_v2(v, A.ex), b2Dot_v2_v2(v, A.ey))
+}
+
+function b2Distance(a, b) {
+ var c = b2Vec2.Subtract(a, b);
+ return c.Length()
+}
+
+function b2DistanceSquared(a, b) {
+ var c = b2Vec2.Subtract(a, b);
+ return b2Dot_v2_v2(c, c)
+}
+
+function b2Dot_v3_v3(a, b) {
+ return a.x * b.x + a.y * b.y + a.z * b.z
+}
+
+function b2Cross_v3_v3(a, b) {
+ return new b2Vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x)
+}
+
+function b2Mul_m22_m22(A, B) {
+ return new b2Mat22(b2Mul_m22_v2(A, B.ex), b2Mul_m22_v2(A, B.ey))
+}
+
+function b2MulT_m22_m22(A, B) {
+ var c1 = new b2Vec2(b2Dot_v2_v2(A.ex, B.ex), b2Dot_v2_v2(A.ey, B.ex));
+ var c2 = new b2Vec2(b2Dot_v2_v2(A.ex, B.ey), b2Dot_v2_v2(A.ey, B.ey));
+ return new b2Mat22(c1, c2)
+}
+
+function b2Mul_m33_v3(A, v) {
+ return b2Vec3.Add(b2Vec3.Add(b2Vec3.Multiply(v.x, A.ex), b2Vec3.Multiply(v.y, A.ey)), b2Vec3.Multiply(v.z, A.ez))
+}
+
+function b2Mul22_m33_v2(A, v) {
+ return new b2Vec2(A.ex.x * v.x + A.ey.x * v.y, A.ex.y * v.x + A.ey.y * v.y)
+}
+
+function b2Mul_r_r(q, r) {
+ var qr = new b2Rot();
+ qr.s = q.s * r.c + q.c * r.s;
+ qr.c = q.c * r.c - q.s * r.s;
+ return qr
+}
+
+function b2MulT_r_r(q, r) {
+ var qr = new b2Rot();
+ qr.s = q.c * r.s - q.s * r.c;
+ qr.c = q.c * r.c + q.s * r.s;
+ return qr
+}
+
+function b2Mul_r_v2(q, v) {
+ return new b2Vec2(q.c * v.x - q.s * v.y, q.s * v.x + q.c * v.y)
+}
+
+function b2MulT_r_v2(q, v) {
+ return new b2Vec2(q.c * v.x + q.s * v.y, -q.s * v.x + q.c * v.y)
+}
+
+function b2Mul_t_v2(T, v) {
+ return new b2Vec2((T.q.c * v.x - T.q.s * v.y) + T.p.x, (T.q.s * v.x + T.q.c * v.y) + T.p.y)
+}
+
+function b2MulT_t_v2(T, v) {
+ var px = v.x - T.p.x;
+ var py = v.y - T.p.y;
+ var x = (T.q.c * px + T.q.s * py);
+ var y = (-T.q.s * px + T.q.c * py);
+ return new b2Vec2(x, y)
+}
+
+function b2Mul_t_t(A, B) {
+ var C = new b2Transform();
+ C.q = b2Mul_r_r(A.q, B.q);
+ C.p = b2Vec2.Add(b2Mul_r_v2(A.q, B.p), A.p);
+ return C
+}
+
+function b2MulT_t_t(A, B) {
+ var C = new b2Transform();
+ C.q = b2MulT_r_r(A.q, B.q);
+ var tvx = B.p.x - A.p.x;
+ var tvy = B.p.y - A.p.y;
+ C.p.x = A.q.c * tvx + A.q.s * tvy;
+ C.p.y = -A.q.s * tvx + A.q.c * tvy;
+ return C
+}
+var b2Abs = Math.abs;
+
+function b2Abs_v2(a) {
+ return new b2Vec2(b2Abs(a.x), b2Abs(a.y))
+}
+
+function b2Abs_m22(A) {
+ return new b2Mat22(b2Abs_v2(A.ex), b2Abs_v2(A.ey))
+}
+var b2Min = Math.min;
+
+function b2Min_v2(a, b) {
+ return new b2Vec2(b2Min(a.x, b.x), b2Min(a.y, b.y))
+}
+var b2Max = Math.max;
+
+function b2Max_v2(a, b) {
+ return new b2Vec2(b2Max(a.x, b.x), b2Max(a.y, b.y))
+}
+
+function b2Clamp(a, low, high) {
+ return b2Max(low, b2Min(a, high))
+}
+
+function b2Clamp_v2(a, low, high) {
+ return b2Max_v2(low, b2Min_v2(a, high))
+}
+
+function b2NextPowerOfTwo(x) {
+ x |= (x >> 1);
+ x |= (x >> 2);
+ x |= (x >> 4);
+ x |= (x >> 8);
+ x |= (x >> 16);
+ return x + 1
+}
+
+function b2IsPowerOfTwo(x) {
+ var result = x > 0 && (x & (x - 1)) == 0;
+ return result
+}
+var RAND_LIMIT = 32767;
+
+function b2RandomFloat(lo, hi) {
+ var r = Math.random();
+ if (typeof (lo) !== "undefined") {
+ r = (hi - lo) * r + lo
+ } else {
+ r = 2 * r - 1
+ }
+ return r
+}
+"use strict";
+
+function b2Color(r, g, b) {
+ this.r = r || 0;
+ this.g = g || 0;
+ this.b = b || 0
+}
+b2Color.prototype = {
+ Set: function (r, g, b) {
+ this.r = r;
+ this.g = g;
+ this.b = b
+ }
+};
+
+function b2Draw() {}
+b2Draw.prototype = {
+ SetFlags: function (flags) {
+ this.m_drawFlags = flags
+ },
+ GetFlags: function () {
+ return this.m_drawFlags
+ },
+ AppendFlags: function (flags) {
+ this.m_drawFlags |= flags
+ },
+ ClearFlags: function (flags) {
+ this.m_drawFlags &= ~flags
+ },
+ ToggleFlags: function (flags) {
+ this.m_drawFlags ^= flags
+ },
+ DrawPolygon: function (vertices, vertexCount, color) {},
+ DrawSolidPolygon: function (vertices, vertexCount, color) {},
+ DrawCircle: function (center, radius, color) {},
+ DrawSolidCircle: function (center, radius, axis, color) {},
+ DrawSegment: function (p1, p2, color) {},
+ DrawTransform: function (xf) {},
+ m_drawFlags: 0
+};
+b2Draw.e_shapeBit = 1;
+b2Draw.e_jointBit = 2;
+b2Draw.e_aabbBit = 4;
+b2Draw.e_centerOfMassBit = 8;
+b2Draw.e_contactPoints = 16;
+b2Draw.e_contactNormals = 32;
+b2Draw.e_contactImpulses = 64;
+b2Draw.e_frictionImpulses = 128;
+b2Draw.e_statistics = 256;
+b2Draw.e_profile = 512;
+b2Draw.e_pairBit = 1024;
+
+"use strict";
+function b2Timer() {
+ this.Reset()
+}
+b2Timer.prototype = {
+ Reset: function () {
+ this.m_start = performance.now()
+ },
+ GetMilliseconds: function () {
+ return performance.now() - this.m_start
+ }
+};
+"use strict";
+
+function b2MassData() {
+ this.mass = 0;
+ this.center = new b2Vec2();
+ this.I = 0
+}
+
+function b2Shape() {
+ this.m_type = 0;
+ this.m_radius = 0
+}
+b2Shape.prototype = {
+ Clone: function () {},
+ GetType: function () {
+ return this.m_type
+ },
+ GetChildCount: function () {},
+ TestPoint: function (xf, p) {},
+ RayCast: function (output, input, transform, childIndex) {},
+ ComputeAABB: function (aabb, xf, childIndex) {},
+ ComputeMass: function (massData, density) {},
+ _serialize: function (out) {
+ var obj = out || {};
+ obj.m_type = this.m_type;
+ obj.m_radius = this.m_radius;
+ return obj
+ },
+ _deserialize: function (data) {
+ this.m_radius = data.m_radius
+ }
+};
+b2Shape.e_circle = 0;
+b2Shape.e_edge = 1;
+b2Shape.e_polygon = 2;
+b2Shape.e_chain = 3;
+b2Shape.e_typeCount = 4;
+"use strict";
+
+function b2CircleShape() {
+ this.parent.call(this);
+ this.m_type = b2Shape.e_circle;
+ this.m_radius = 0;
+ this.m_p = new b2Vec2();
+ Object.seal(this)
+}
+b2CircleShape.prototype = {
+ Clone: function () {
+ var shape = new b2CircleShape();
+ shape.m_radius = this.m_radius;
+ shape.m_p = this.m_p.Clone();
+ return shape
+ },
+ GetChildCount: function () {
+ return 1
+ },
+ TestPoint: function (transform, p) {
+ var center = b2Vec2.Add(transform.p, b2Mul_r_v2(transform.q, this.m_p));
+ var d = b2Vec2.Subtract(p, center);
+ return b2Dot_v2_v2(d, d) <= this.m_radius * this.m_radius
+ },
+ RayCast: function (output, input, transform, childIndex) {
+ var position = b2Vec2.Add(transform.p, b2Mul_r_v2(transform.q, this.m_p));
+ var s = b2Vec2.Subtract(input.p1, position);
+ var b = b2Dot_v2_v2(s, s) - this.m_radius * this.m_radius;
+ var r = b2Vec2.Subtract(input.p2, input.p1);
+ var c = b2Dot_v2_v2(s, r);
+ var rr = b2Dot_v2_v2(r, r);
+ var sigma = c * c - rr * b;
+ if (sigma < 0 || rr < b2_epsilon) {
+ return false
+ }
+ var a = -(c + b2Sqrt(sigma));
+ if (0 <= a && a <= input.maxFraction * rr) {
+ a /= rr;
+ output.fraction = a;
+ output.normal = b2Vec2.Add(s, b2Vec2.Multiply(a, r));
+ output.normal.Normalize();
+ return true
+ }
+ return false
+ },
+ ComputeAABB: function (aabb, transform, childIndex) {
+ var px = transform.p.x + (transform.q.c * this.m_p.x - transform.q.s * this.m_p.y);
+ var py = transform.p.y + (transform.q.s * this.m_p.x + transform.q.c * this.m_p.y);
+ aabb.lowerBound.x = px - this.m_radius;
+ aabb.lowerBound.y = py - this.m_radius;
+ aabb.upperBound.x = px + this.m_radius;
+ aabb.upperBound.y = py + this.m_radius
+ },
+ ComputeMass: function (massData, density) {
+ massData.mass = density * b2_pi * this.m_radius * this.m_radius;
+ massData.center = this.m_p;
+ massData.I = massData.mass * (0.5 * this.m_radius * this.m_radius + b2Dot_v2_v2(this.m_p, this.m_p))
+ },
+ GetSupport: function (d) {
+ return 0
+ },
+ GetSupportVertex: function (d) {
+ return this.m_p
+ },
+ GetVertexCount: function () {
+ return 1
+ },
+ GetVertex: function (index) {
+ return this.m_p
+ },
+ _serialize: function (out) {
+ var obj = out || {};
+ this.parent.prototype._serialize.call(this, obj);
+ obj.m_p = this.m_p._serialize();
+ return obj
+ },
+ _deserialize: function (data) {
+ this.parent.prototype._deserialize.call(this, data);
+ this.m_p._deserialize(data.m_p)
+ }
+};
+b2CircleShape._extend(b2Shape);
+"use strict";
+
+function b2EdgeShape() {
+ this.parent.call(this);
+ this.m_type = b2Shape.e_edge;
+ this.m_radius = b2_polygonRadius;
+ this.m_vertex0 = new b2Vec2();
+ this.m_vertex1 = new b2Vec2();
+ this.m_vertex2 = new b2Vec2();
+ this.m_vertex3 = new b2Vec2();
+ this.m_hasVertex0 = false;
+ this.m_hasVertex3 = false;
+ Object.seal(this)
+}
+b2EdgeShape.prototype = {
+ Set: function (v1, v2) {
+ this.m_vertex1.Assign(v1);
+ this.m_vertex2.Assign(v2);
+ this.m_hasVertex0 = false;
+ this.m_hasVertex3 = false
+ },
+ Clone: function () {
+ var shape = new b2EdgeShape();
+ shape.m_vertex0 = this.m_vertex0.Clone();
+ shape.m_vertex1 = this.m_vertex1.Clone();
+ shape.m_vertex2 = this.m_vertex2.Clone();
+ shape.m_vertex3 = this.m_vertex3.Clone();
+ shape.m_hasVertex0 = this.m_hasVertex0;
+ shape.m_hasVertex3 = this.m_hasVertex3;
+ return shape
+ },
+ GetChildCount: function () {
+ return 1
+ },
+ TestPoint: function (transform, p) {
+ return false
+ },
+ RayCast: function (output, input, xf, childIndex) {
+ var p1 = b2MulT_r_v2(xf.q, b2Vec2.Subtract(input.p1, xf.p));
+ var p2 = b2MulT_r_v2(xf.q, b2Vec2.Subtract(input.p2, xf.p));
+ var d = b2Vec2.Subtract(p2, p1);
+ var v1 = this.m_vertex1;
+ var v2 = this.m_vertex2;
+ var e = b2Vec2.Subtract(v2, v1);
+ var normal = new b2Vec2(e.y, -e.x);
+ normal.Normalize();
+ var numerator = b2Dot_v2_v2(normal, b2Vec2.Subtract(v1, p1));
+ var denominator = b2Dot_v2_v2(normal, d);
+ if (denominator == 0) {
+ return false
+ }
+ var t = numerator / denominator;
+ if (t < 0 || input.maxFraction < t) {
+ return false
+ }
+ var q = b2Vec2.Add(p1, b2Vec2.Multiply(t, d));
+ var r = b2Vec2.Subtract(v2, v1);
+ var rr = b2Dot_v2_v2(r, r);
+ if (rr == 0) {
+ return false
+ }
+ var s = b2Dot_v2_v2(b2Vec2.Subtract(q, v1), r) / rr;
+ if (s < 0 || 1 < s) {
+ return false
+ }
+ output.fraction = t;
+ if (numerator > 0) {
+ output.normal = b2Mul_r_v2(xf.q, normal).Negate()
+ } else {
+ output.normal = b2Mul_r_v2(xf.q, normal)
+ }
+ return true
+ },
+ ComputeAABB: function (aabb, xf, childIndex) {
+ var v1x = (xf.q.c * this.m_vertex1.x - xf.q.s * this.m_vertex1.y) + xf.p.x;
+ var v1y = (xf.q.s * this.m_vertex1.x + xf.q.c * this.m_vertex1.y) + xf.p.y;
+ var v2x = (xf.q.c * this.m_vertex2.x - xf.q.s * this.m_vertex2.y) + xf.p.x;
+ var v2y = (xf.q.s * this.m_vertex2.x + xf.q.c * this.m_vertex2.y) + xf.p.y;
+ var lowerx = b2Min(v1x, v2x);
+ var lowery = b2Min(v1y, v2y);
+ var upperx = b2Max(v1x, v2x);
+ var uppery = b2Max(v1y, v2y);
+ aabb.lowerBound.x = lowerx - this.m_radius;
+ aabb.lowerBound.y = lowery - this.m_radius;
+ aabb.upperBound.x = upperx + this.m_radius;
+ aabb.upperBound.y = uppery + this.m_radius
+ },
+ ComputeMass: function (massData, density) {
+ massData.mass = 0;
+ massData.center = b2Vec2.Multiply(0.5, b2Vec2.Add(this.m_vertex1, this.m_vertex2));
+ massData.I = 0
+ },
+ _serialize: function (out) {
+ var obj = out || {};
+ this.parent.prototype._serialize.call(this, obj);
+ obj.m_vertex1 = this.m_vertex1._serialize();
+ obj.m_vertex2 = this.m_vertex2._serialize();
+ obj.m_hasVertex0 = this.m_hasVertex0;
+ if (this.m_hasVertex0) {
+ obj.m_vertex0 = this.m_vertex0._serialize()
+ }
+ obj.m_hasVertex3 = this.m_hasVertex3;
+ if (this.m_hasVertex3) {
+ obj.m_vertex3 = this.m_vertex3._serialize()
+ }
+ return obj
+ },
+ _deserialize: function (data) {
+ this.parent.prototype._deserialize.call(this, data);
+ this.m_vertex1._deserialize(data.m_vertex1);
+ this.m_vertex2._deserialize(data.m_vertex2);
+ this.m_hasVertex0 = data.m_hasVertex0;
+ if (this.m_hasVertex0) {
+ this.m_vertex0._deserialize(data.m_vertex0)
+ }
+ this.m_hasVertex3 = data.m_hasVertex3;
+ if (this.m_hasVertex3) {
+ this.m_vertex3._deserialize(data.m_vertex3)
+ }
+ }
+};
+b2EdgeShape._extend(b2Shape);
+"use strict";
+
+function b2ChainShape() {
+ this.parent.call(this);
+ this.m_type = b2Shape.e_chain;
+ this.m_radius = b2_polygonRadius;
+ this.m_vertices = null;
+ this.m_count = 0;
+ this.m_prevVertex = new b2Vec2();
+ this.m_nextVertex = new b2Vec2();
+ this.m_hasPrevVertex = false;
+ this.m_hasNextVertex = false;
+ Object.seal(this)
+}
+b2ChainShape.prototype = {
+ CreateLoop: function (vertices, count) {
+ for (var i = 1; i < count; ++i) {
+ var v1 = vertices[i - 1];
+ var v2 = vertices[i]
+ }
+ this.m_count = count + 1;
+ this.m_vertices = new Array(this.m_count);
+ for (var i = 0; i < count; ++i) {
+ this.m_vertices[i] = vertices[i].Clone()
+ }
+ this.m_vertices[count] = this.m_vertices[0].Clone();
+ this.m_prevVertex.Assign(this.m_vertices[this.m_count - 2]);
+ this.m_nextVertex.Assign(this.m_vertices[1]);
+ this.m_hasPrevVertex = true;
+ this.m_hasNextVertex = true
+ },
+ CreateChain: function (vertices, count) {
+ for (var i = 1; i < count; ++i) {
+ var v1 = vertices[i - 1];
+ var v2 = vertices[i]
+ }
+ this.m_count = count;
+ this.m_vertices = new Array(count);
+ for (var i = 0; i < count; ++i) {
+ this.m_vertices[i] = vertices[i].Clone()
+ }
+ this.m_hasPrevVertex = false;
+ this.m_hasNextVertex = false;
+ this.m_prevVertex.SetZero();
+ this.m_nextVertex.SetZero()
+ },
+ SetPrevVertex: function (prevVertex) {
+ this.m_prevVertex.Assign(prevVertex);
+ this.m_hasPrevVertex = true
+ },
+ SetNextVertex: function (nextVertex) {
+ this.m_nextVertex.Assign(nextVertex);
+ this.m_hasNextVertex = true
+ },
+ Clone: function () {
+ var shape = new b2ChainShape();
+ shape.m_count = this.m_count;
+ shape.m_vertices = new Array(this.m_count);
+ for (var i = 0; i < this.m_count; ++i) {
+ shape.m_vertices[i] = this.m_vertices[i].Clone()
+ }
+ shape.m_prevVertex = this.m_prevVertex.Clone();
+ shape.m_nextVertex = this.m_nextVertex.Clone();
+ shape.m_hasPrevVertex = this.m_hasPrevVertex;
+ shape.m_hasNextVertex = this.m_hasNextVertex;
+ return shape
+ },
+ GetChildCount: function () {
+ return this.m_count - 1
+ },
+ GetChildEdge: function (edge, index) {
+ edge.m_type = b2Shape.e_edge;
+ edge.m_radius = this.m_radius;
+ edge.m_vertex1 = this.m_vertices[index + 0];
+ edge.m_vertex2 = this.m_vertices[index + 1];
+ if (index > 0) {
+ edge.m_vertex0 = this.m_vertices[index - 1];
+ edge.m_hasVertex0 = true
+ } else {
+ edge.m_vertex0 = this.m_prevVertex;
+ edge.m_hasVertex0 = this.m_hasPrevVertex
+ }
+ if (index < this.m_count - 2) {
+ edge.m_vertex3 = this.m_vertices[index + 2];
+ edge.m_hasVertex3 = true
+ } else {
+ edge.m_vertex3 = this.m_nextVertex;
+ edge.m_hasVertex3 = this.m_hasNextVertex
+ }
+ },
+ TestPoint: function (transform, p) {
+ return false
+ },
+ RayCast: function (output, input, xf, childIndex) {
+ var edgeShape = new b2EdgeShape();
+ var i1 = childIndex;
+ var i2 = childIndex + 1;
+ if (i2 == this.m_count) {
+ i2 = 0
+ }
+ edgeShape.m_vertex1 = this.m_vertices[i1].Clone();
+ edgeShape.m_vertex2 = this.m_vertices[i2].Clone();
+ return edgeShape.RayCast(output, input, xf, 0)
+ },
+ ComputeAABB: function (aabb, xf, childIndex) {
+ var i1 = childIndex;
+ var i2 = childIndex + 1;
+ if (i2 == this.m_count) {
+ i2 = 0
+ }
+ var v1x = (xf.q.c * this.m_vertices[i1].x - xf.q.s * this.m_vertices[i1].y) + xf.p.x;
+ var v1y = (xf.q.s * this.m_vertices[i1].x + xf.q.c * this.m_vertices[i1].y) + xf.p.y;
+ var v2x = (xf.q.c * this.m_vertices[i2].x - xf.q.s * this.m_vertices[i2].y) + xf.p.x;
+ var v2y = (xf.q.s * this.m_vertices[i2].x + xf.q.c * this.m_vertices[i2].y) + xf.p.y;
+ aabb.lowerBound.x = b2Min(v1x, v2x);
+ aabb.lowerBound.y = b2Min(v1y, v2y);
+ aabb.upperBound.x = b2Max(v1x, v2x);
+ aabb.upperBound.y = b2Max(v1y, v2y)
+ },
+ ComputeMass: function (massData, density) {
+ massData.mass = 0;
+ massData.center.SetZero();
+ massData.I = 0
+ },
+ _serialize: function (out) {
+ var obj = out || {};
+ this.parent.prototype._serialize.call(this, obj);
+ obj.m_count = this.m_count;
+ obj.m_vertices = [];
+ for (var i = 0; i < this.m_count; ++i) {
+ obj.m_vertices.push(this.m_vertices[i]._serialize())
+ }
+ obj.m_hasPrevVertex = this.m_hasPrevVertex;
+ if (this.m_hasPrevVertex) {
+ obj.m_prevVertex = this.m_prevVertex._serialize()
+ }
+ obj.m_hasNextVertex = this.m_hasNextVertex;
+ if (this.m_hasNextVertex) {
+ obj.m_nextVertex = this.m_nextVertex._serialize()
+ }
+ return obj
+ },
+ _deserialize: function (data) {
+ this.parent.prototype._deserialize.call(this, data);
+ this.m_count = data.m_count;
+ this.m_vertices = [];
+ for (var i = 0; i < this.m_count; ++i) {
+ this.m_vertices[i] = new b2Vec2();
+ this.m_vertices[i]._deserialize(data.m_vertices[i])
+ }
+ this.m_hasPrevVertex = data.m_hasPrevVertex;
+ if (this.m_hasPrevVertex) {
+ this.m_prevVertex._deserialize(data.m_prevVertex)
+ }
+ this.m_hasNextVertex = data.m_hasNextVertex;
+ if (this.m_hasNextVertex) {
+ this.m_nextVertex._deserialize(data.m_nextVertex)
+ }
+ }
+};
+b2ChainShape._extend(b2Shape);
+"use strict";
+
+function b2PolygonShape() {
+ this.parent.call(this);
+ this.m_type = b2Shape.e_polygon;
+ this.m_radius = b2_polygonRadius;
+ this.m_count = 0;
+ this.m_centroid = new b2Vec2();
+ this.m_vertices = new Array(b2_maxPolygonVertices);
+ this.m_normals = new Array(b2_maxPolygonVertices);
+ Object.seal(this)
+}
+b2PolygonShape.prototype = {
+ Clone: function () {
+ var shape = new b2PolygonShape();
+ shape.m_count = this.m_count;
+ shape.m_centroid = this.m_centroid.Clone();
+ for (var i = 0; i < this.m_count; ++i) {
+ shape.m_vertices[i] = this.m_vertices[i].Clone();
+ shape.m_normals[i] = this.m_normals[i].Clone()
+ }
+ return shape
+ },
+ GetChildCount: function () {
+ return 1
+ },
+ Set: function (vertices, count) {
+ if (count < 3) {
+ this.SetAsBox(1, 1);
+ return
+ }
+ var n = b2Min(count, b2_maxPolygonVertices);
+ var ps = new Array(b2_maxPolygonVertices);
+ var tempCount = 0;
+ for (var i = 0; i < n; ++i) {
+ var v = vertices[i];
+ var unique = true;
+ for (var j = 0; j < tempCount; ++j) {
+ if (b2DistanceSquared(v, ps[j]) < 0.5 * b2_linearSlop) {
+ unique = false;
+ break
+ }
+ }
+ if (unique) {
+ ps[tempCount++] = v.Clone()
+ }
+ }
+ n = tempCount;
+ if (n < 3) {
+ this.SetAsBox(1, 1);
+ return
+ }
+ var i0 = 0;
+ var x0 = ps[0].x;
+ for (i = 1; i < n; ++i) {
+ var x = ps[i].x;
+ if (x > x0 || (x == x0 && ps[i].y < ps[i0].y)) {
+ i0 = i;
+ x0 = x
+ }
+ }
+ var hull = new Array(b2_maxPolygonVertices);
+ var m = 0;
+ var ih = i0;
+ for (;;) {
+ hull[m] = ih;
+ var ie = 0;
+ for (j = 1; j < n; ++j) {
+ if (ie == ih) {
+ ie = j;
+ continue
+ }
+ var r = b2Vec2.Subtract(ps[ie], ps[hull[m]]);
+ var v = b2Vec2.Subtract(ps[j], ps[hull[m]]);
+ var c = b2Cross_v2_v2(r, v);
+ if (c < 0) {
+ ie = j
+ }
+ if (c == 0 && v.LengthSquared() > r.LengthSquared()) {
+ ie = j
+ }
+ }++m;
+ ih = ie;
+ if (ie == i0) {
+ break
+ }
+ }
+ this.m_count = m;
+ for (i = 0; i < m; ++i) {
+ this.m_vertices[i] = ps[hull[i]].Clone()
+ }
+ for (i = 0; i < m; ++i) {
+ var i1 = i;
+ var i2 = i + 1 < m ? i + 1 : 0;
+ var edge = b2Vec2.Subtract(this.m_vertices[i2], this.m_vertices[i1]);
+ this.m_normals[i] = b2Cross_v2_f(edge, 1).Clone();
+ this.m_normals[i].Normalize()
+ }
+ this.m_centroid = b2PolygonShape.ComputeCentroid(this.m_vertices, m)
+ },
+ SetAsBox: function (hx, hy, center, angle) {
+ this.m_count = 4;
+ this.m_vertices[0] = new b2Vec2(-hx, -hy);
+ this.m_vertices[1] = new b2Vec2(hx, -hy);
+ this.m_vertices[2] = new b2Vec2(hx, hy);
+ this.m_vertices[3] = new b2Vec2(-hx, hy);
+ this.m_normals[0] = new b2Vec2(0, -1);
+ this.m_normals[1] = new b2Vec2(1, 0);
+ this.m_normals[2] = new b2Vec2(0, 1);
+ this.m_normals[3] = new b2Vec2(-1, 0);
+ if (!center) {
+ return
+ }
+ this.m_centroid.Assign(center);
+ var xf = new b2Transform();
+ xf.p = center;
+ xf.q.Set(angle);
+ for (var i = 0; i < this.m_count; ++i) {
+ this.m_vertices[i].Assign(b2Mul_t_v2(xf, this.m_vertices[i]));
+ this.m_normals[i].Assign(b2Mul_r_v2(xf.q, this.m_normals[i]))
+ }
+ },
+ TestPoint: function (xf, p) {
+ var pLocal = b2MulT_r_v2(xf.q, b2Vec2.Subtract(p, xf.p));
+ for (var i = 0; i < this.m_count; ++i) {
+ var dot = b2Dot_v2_v2(this.m_normals[i], b2Vec2.Subtract(pLocal, this.m_vertices[i]));
+ if (dot > 0) {
+ return false
+ }
+ }
+ return true
+ },
+ RayCast: function (output, input, xf, childIndex) {
+ var p1 = b2MulT_r_v2(xf.q, b2Vec2.Subtract(input.p1, xf.p));
+ var p2 = b2MulT_r_v2(xf.q, b2Vec2.Subtract(input.p2, xf.p));
+ var d = b2Vec2.Subtract(p2, p1);
+ var lower = 0,
+ upper = input.maxFraction;
+ var index = -1;
+ for (var i = 0; i < this.m_count; ++i) {
+ var numerator = b2Dot_v2_v2(this.m_normals[i], b2Vec2.Subtract(this.m_vertices[i], p1));
+ var denominator = b2Dot_v2_v2(this.m_normals[i], d);
+ if (denominator == 0) {
+ if (numerator < 0) {
+ return false
+ }
+ } else {
+ if (denominator < 0 && numerator < lower * denominator) {
+ lower = numerator / denominator;
+ index = i
+ } else {
+ if (denominator > 0 && numerator < upper * denominator) {
+ upper = numerator / denominator
+ }
+ }
+ }
+ if (upper < lower) {
+ return false
+ }
+ }
+ if (index >= 0) {
+ output.fraction = lower;
+ output.normal = b2Mul_r_v2(xf.q, this.m_normals[index]);
+ return true
+ }
+ return false
+ },
+ ComputeAABB: function (aabb, xf, childIndex) {
+ var lowerx = (xf.q.c * this.m_vertices[0].x - xf.q.s * this.m_vertices[0].y) + xf.p.x;
+ var lowery = (xf.q.s * this.m_vertices[0].x + xf.q.c * this.m_vertices[0].y) + xf.p.y;
+ var upperx = lowerx;
+ var uppery = lowery;
+ for (var i = 1; i < this.m_count; ++i) {
+ var vx = (xf.q.c * this.m_vertices[i].x - xf.q.s * this.m_vertices[i].y) + xf.p.x;
+ var vy = (xf.q.s * this.m_vertices[i].x + xf.q.c * this.m_vertices[i].y) + xf.p.y;
+ lowerx = b2Min(lowerx, vx);
+ lowery = b2Min(lowery, vy);
+ upperx = b2Max(upperx, vx);
+ uppery = b2Max(uppery, vy)
+ }
+ aabb.lowerBound.x = lowerx - this.m_radius;
+ aabb.lowerBound.y = lowery - this.m_radius;
+ aabb.upperBound.x = upperx + this.m_radius;
+ aabb.upperBound.y = uppery + this.m_radius
+ },
+ ComputeMass: function (massData, density) {
+ var center = new b2Vec2(0, 0);
+ var area = 0;
+ var I = 0;
+ var s = new b2Vec2(0, 0);
+ for (var i = 0; i < this.m_count; ++i) {
+ s.Add(this.m_vertices[i])
+ }
+ s.Multiply(1 / this.m_count);
+ var k_inv3 = 1 / 3;
+ for (var i = 0; i < this.m_count; ++i) {
+ var e1 = b2Vec2.Subtract(this.m_vertices[i], s);
+ var e2 = i + 1 < this.m_count ? b2Vec2.Subtract(this.m_vertices[i + 1], s) : b2Vec2.Subtract(this.m_vertices[0], s);
+ var D = b2Cross_v2_v2(e1, e2);
+ var triangleArea = 0.5 * D;
+ area += triangleArea;
+ center.Add(b2Vec2.Multiply(triangleArea * k_inv3, b2Vec2.Add(e1, e2)));
+ var ex1 = e1.x,
+ ey1 = e1.y;
+ var ex2 = e2.x,
+ ey2 = e2.y;
+ var intx2 = ex1 * ex1 + ex2 * ex1 + ex2 * ex2;
+ var inty2 = ey1 * ey1 + ey2 * ey1 + ey2 * ey2;
+ I += (0.25 * k_inv3 * D) * (intx2 + inty2)
+ }
+ massData.mass = density * area;
+ center.Multiply(1 / area);
+ massData.center = b2Vec2.Add(center, s);
+ massData.I = density * I;
+ massData.I += massData.mass * (b2Dot_v2_v2(massData.center, massData.center) - b2Dot_v2_v2(center, center))
+ },
+ GetVertexCount: function () {
+ return this.m_count
+ },
+ GetVertex: function (index) {
+ return this.m_vertices[index]
+ },
+ Validate: function () {
+ for (var i = 0; i < this.m_count; ++i) {
+ var i1 = i;
+ var i2 = i < this.m_count - 1 ? i1 + 1 : 0;
+ var p = this.m_vertices[i1];
+ var e = b2Vec2.Subtract(this.m_vertices[i2], p);
+ for (var j = 0; j < this.m_count; ++j) {
+ if (j == i1 || j == i2) {
+ continue
+ }
+ var v = b2Vec2.Subtract(this.m_vertices[j], p);
+ var c = b2Cross_v2_v2(e, v);
+ if (c < 0) {
+ return false
+ }
+ }
+ }
+ return true
+ },
+ _serialize: function (out) {
+ var obj = out || {};
+ this.parent.prototype._serialize.call(this, obj);
+ obj.m_count = this.m_count;
+ obj.m_centroid = this.m_centroid._serialize();
+ obj.m_vertices = [];
+ obj.m_normals = [];
+ for (var i = 0; i < this.m_count; ++i) {
+ obj.m_vertices.push(this.m_vertices[i]._serialize());
+ obj.m_normals.push(this.m_normals[i]._serialize())
+ }
+ return obj
+ },
+ _deserialize: function (data) {
+ this.parent.prototype._deserialize.call(this, data);
+ this.m_count = data.m_count;
+ this.m_centroid._deserialize(data.m_centroid);
+ this.m_vertices = [];
+ this.m_normals = [];
+ for (var i = 0; i < this.m_count; ++i) {
+ this.m_vertices[i] = new b2Vec2();
+ this.m_vertices[i]._deserialize(data.m_vertices[i]);
+ this.m_normals[i] = new b2Vec2();
+ this.m_normals[i]._deserialize(data.m_normals[i])
+ }
+ }
+};
+b2PolygonShape.ComputeCentroid = function (vs, count) {
+ var c = new b2Vec2();
+ var area = 0;
+ var pRef = new b2Vec2(0, 0);
+ var inv3 = 1 / 3;
+ for (var i = 0; i < count; ++i) {
+ var p1 = pRef;
+ var p2 = vs[i];
+ var p3 = i + 1 < count ? vs[i + 1] : vs[0];
+ var e1 = b2Vec2.Subtract(p2, p1);
+ var e2 = b2Vec2.Subtract(p3, p1);
+ var D = b2Cross_v2_v2(e1, e2);
+ var triangleArea = 0.5 * D;
+ area += triangleArea;
+ c.Add(b2Vec2.Multiply(triangleArea, b2Vec2.Multiply(inv3, b2Vec2.Add(b2Vec2.Add(p1, p2), p3))))
+ }
+ c.Multiply(1 / area);
+ return c
+};
+b2PolygonShape._extend(b2Shape);
+"use strict";
+
+function b2Pair() {
+ this.proxyIdA = 0;
+ this.proxyIdB = 0
+}
+
+function b2PairLessThan(pair1, pair2) {
+ if (pair1.proxyIdA == pair2.proxyIdA) {
+ return pair1.proxyIdB - pair2.proxyIdB
+ }
+ return pair1.proxyIdA - pair2.proxyIdA
+}
+
+function b2BroadPhase() {
+ this.m_tree = new b2DynamicTree();
+ this.m_queryProxyId = 0;
+ this.m_proxyCount = 0;
+ this.m_pairCount = 0;
+ this.m_pairBuffer = [];
+ this.m_moveCount = 0;
+ this.m_moveBuffer = []
+}
+b2BroadPhase.prototype = {
+ CreateProxy: function (aabb, userData) {
+ var proxyId = this.m_tree.CreateProxy(aabb, userData);
+ ++this.m_proxyCount;
+ this.BufferMove(proxyId);
+ return proxyId
+ },
+ DestroyProxy: function (proxyId) {
+ this.UnBufferMove(proxyId);
+ --this.m_proxyCount;
+ this.m_tree.DestroyProxy(proxyId)
+ },
+ MoveProxy: function (proxyId, aabb, displacement) {
+ var buffer = this.m_tree.MoveProxy(proxyId, aabb, displacement);
+ if (buffer) {
+ this.BufferMove(proxyId)
+ }
+ },
+ TouchProxy: function (proxyId) {
+ this.BufferMove(proxyId)
+ },
+ GetFatAABB: function (proxyId) {
+ return this.m_tree.GetFatAABB(proxyId)
+ },
+ GetUserData: function (proxyId) {
+ return this.m_tree.GetUserData(proxyId)
+ },
+ TestOverlap: function (proxyIdA, proxyIdB) {
+ var aabbA = this.m_tree.GetFatAABB(proxyIdA);
+ var aabbB = this.m_tree.GetFatAABB(proxyIdB);
+ return b2TestOverlap(aabbA, aabbB)
+ },
+ GetProxyCount: function () {
+ return this.m_proxyCount
+ },
+ UpdatePairs: function (callback) {
+ this.m_pairCount = 0;
+ this.m_pairBuffer.length = 0;
+ for (var i = 0; i < this.m_moveCount; ++i) {
+ this.m_queryProxyId = this.m_moveBuffer[i];
+ if (this.m_queryProxyId == b2BroadPhase.e_nullProxy) {
+ continue
+ }
+ var fatAABB = this.m_tree.GetFatAABB(this.m_queryProxyId);
+ this.m_tree.Query(this, fatAABB)
+ }
+ this.m_moveCount = 0;
+ this.m_pairBuffer.sort(b2PairLessThan);
+ var i = 0;
+ while (i < this.m_pairCount) {
+ var primaryPair = this.m_pairBuffer[i];
+ var userDataA = this.m_tree.GetUserData(primaryPair.proxyIdA);
+ var userDataB = this.m_tree.GetUserData(primaryPair.proxyIdB);
+ callback.AddPair(userDataA, userDataB);
+ ++i;
+ while (i < this.m_pairCount) {
+ var pair = this.m_pairBuffer[i];
+ if (pair.proxyIdA != primaryPair.proxyIdA || pair.proxyIdB != primaryPair.proxyIdB) {
+ break
+ }++i
+ }
+ }
+ },
+ Query: function (callback, aabb) {
+ this.m_tree.Query(callback, aabb)
+ },
+ RayCast: function (callback, input) {
+ this.m_tree.RayCast(callback, input)
+ },
+ GetTreeHeight: function () {
+ return this.m_tree.GetHeight()
+ },
+ GetTreeBalance: function () {
+ return this.m_tree.GetMaxBalance()
+ },
+ GetTreeQuality: function () {
+ return this.m_tree.GetAreaRatio()
+ },
+ ShiftOrigin: function (newOrigin) {
+ this.m_tree.ShiftOrigin(newOrigin)
+ },
+ BufferMove: function (proxyId) {
+ this.m_moveBuffer[this.m_moveCount] = proxyId;
+ ++this.m_moveCount
+ },
+ UnBufferMove: function (proxyId) {
+ for (var i = 0; i < this.m_moveCount; ++i) {
+ if (this.m_moveBuffer[i] == proxyId) {
+ this.m_moveBuffer[i] = b2BroadPhase.e_nullProxy
+ }
+ }
+ },
+ QueryCallback: function (proxyId) {
+ if (proxyId == this.m_queryProxyId) {
+ return true
+ }
+ this.m_pairBuffer[this.m_pairCount] = new b2Pair();
+ this.m_pairBuffer[this.m_pairCount].proxyIdA = b2Min(proxyId, this.m_queryProxyId);
+ this.m_pairBuffer[this.m_pairCount].proxyIdB = b2Max(proxyId, this.m_queryProxyId);
+ ++this.m_pairCount;
+ return true
+ }
+};
+b2BroadPhase.e_nullProxy = -1;
+"use strict";
+
+function b2DistanceProxy() {
+ this.m_vertices = null;
+ this.m_count = 0;
+ this.m_radius = 0
+}
+b2DistanceProxy.prototype = {
+ Assign: function (l) {
+ this.m_vertices = l.m_vertices;
+ this.m_count = l.m_count;
+ this.m_radius = l.m_radius
+ },
+ Set: function (shape, index) {
+ switch (shape.GetType()) {
+ case b2Shape.e_circle:
+ var circle = shape;
+ this.m_vertices = [circle.m_p];
+ this.m_count = 1;
+ this.m_radius = circle.m_radius;
+ break;
+ case b2Shape.e_polygon:
+ var polygon = shape;
+ this.m_vertices = polygon.m_vertices;
+ this.m_count = polygon.m_count;
+ this.m_radius = polygon.m_radius;
+ break;
+ case b2Shape.e_chain:
+ var chain = shape;
+ this.m_vertices = [chain.m_vertices[index]];
+ if (index + 1 < chain.m_count) {
+ this.m_vertices[1] = chain.m_vertices[index + 1]
+ } else {
+ this.m_vertices[1] = chain.m_vertices[0]
+ }
+ this.m_count = 2;
+ this.m_radius = chain.m_radius;
+ break;
+ case b2Shape.e_edge:
+ var edge = shape;
+ this.m_vertices = [edge.m_vertex1, edge.m_vertex2];
+ this.m_count = 2;
+ this.m_radius = edge.m_radius;
+ break
+ }
+ },
+ GetSupport: function (dx, dy) {
+ var bestIndex = 0;
+ var bestValue = this.m_vertices[0].x * dx + this.m_vertices[0].y * dy;
+ for (var i = 1; i < this.m_count; ++i) {
+ var value = this.m_vertices[i].x * dx + this.m_vertices[i].y * dy;
+ if (value > bestValue) {
+ bestIndex = i;
+ bestValue = value
+ }
+ }
+ return bestIndex
+ },
+ GetSupportVertex: function (dx, dy) {
+ return this.m_vertices[this.GetSupport(dx, dy)]
+ },
+ GetVertexCount: function () {
+ return this.m_count
+ },
+ GetVertex: function (index) {
+ return this.m_vertices[index]
+ }
+};
+
+function b2SimplexCache() {
+ this.metric = 0;
+ this.count = 0;
+ this.indexA = [0, 0, 0];
+ this.indexB = [0, 0, 0]
+}
+
+function b2DistanceInput() {
+ this.proxyA = new b2DistanceProxy();
+ this.proxyB = new b2DistanceProxy();
+ this.transformA = new b2Transform();
+ this.transformB = new b2Transform();
+ this.useRadii = false
+}
+
+function b2DistanceOutput() {
+ this.pointA = new b2Vec2();
+ this.pointB = new b2Vec2();
+ this.distance = 0;
+ this.iterations = 0
+}
+
+function b2SimplexVertex() {
+ this.wA = new b2Vec2();
+ this.wB = new b2Vec2();
+ this.w = new b2Vec2();
+ this.a = 0;
+ this.indexA = 0;
+ this.indexB = 0
+}
+b2SimplexVertex.prototype = {
+ Assign: function (l) {
+ this.wA.x = l.wA.x;
+ this.wA.y = l.wA.y;
+ this.wB.x = l.wB.x;
+ this.wB.y = l.wB.y;
+ this.w.x = l.w.x;
+ this.w.y = l.w.y;
+ this.a = l.a;
+ this.indexA = l.indexA;
+ this.indexB = l.indexB
+ }
+};
+
+function b2Simplex() {
+ this.m_v = [new b2SimplexVertex(), new b2SimplexVertex(), new b2SimplexVertex()];
+ this.m_count = 0
+}
+b2Simplex.prototype = {
+ ReadCache: function (cache, proxyA, transformA, proxyB, transformB) {
+ this.m_count = cache.count;
+ var vertices = this.m_v;
+ for (var i = 0; i < this.m_count; ++i) {
+ var v = vertices[i];
+ v.indexA = cache.indexA[i];
+ v.indexB = cache.indexB[i];
+ var wALocal = proxyA.GetVertex(v.indexA);
+ var wBLocal = proxyB.GetVertex(v.indexB);
+ v.wA.x = (transformA.q.c * wALocal.x - transformA.q.s * wALocal.y) + transformA.p.x;
+ v.wA.y = (transformA.q.s * wALocal.x + transformA.q.c * wALocal.y) + transformA.p.y;
+ v.wB.x = (transformB.q.c * wBLocal.x - transformB.q.s * wBLocal.y) + transformB.p.x;
+ v.wB.y = (transformB.q.s * wBLocal.x + transformB.q.c * wBLocal.y) + transformB.p.y;
+ v.w.x = v.wB.x - v.wA.x;
+ v.w.y = v.wB.y - v.wA.y;
+ v.a = 0
+ }
+ if (this.m_count > 1) {
+ var metric1 = cache.metric;
+ var metric2 = this.GetMetric();
+ if (metric2 < 0.5 * metric1 || 2 * metric1 < metric2 || metric2 < b2_epsilon) {
+ this.m_count = 0
+ }
+ }
+ if (this.m_count == 0) {
+ var v = vertices[0];
+ v.indexA = 0;
+ v.indexB = 0;
+ var wALocal = proxyA.GetVertex(0);
+ var wBLocal = proxyB.GetVertex(0);
+ v.wA.x = (transformA.q.c * wALocal.x - transformA.q.s * wALocal.y) + transformA.p.x;
+ v.wA.y = (transformA.q.s * wALocal.x + transformA.q.c * wALocal.y) + transformA.p.y;
+ v.wB.x = (transformB.q.c * wBLocal.x - transformB.q.s * wBLocal.y) + transformB.p.x;
+ v.wB.y = (transformB.q.s * wBLocal.x + transformB.q.c * wBLocal.y) + transformB.p.y;
+ v.w.x = v.wB.x - v.wA.x;
+ v.w.y = v.wB.y - v.wA.y;
+ v.a = 1;
+ this.m_count = 1
+ }
+ },
+ WriteCache: function (cache) {
+ cache.metric = this.GetMetric();
+ cache.count = this.m_count;
+ var vertices = this.m_v;
+ for (var i = 0; i < this.m_count; ++i) {
+ cache.indexA[i] = vertices[i].indexA;
+ cache.indexB[i] = vertices[i].indexB
+ }
+ },
+ GetSearchDirection: function (p) {
+ switch (this.m_count) {
+ case 1:
+ p.x = -this.m_v[0].w.x;
+ p.y = -this.m_v[0].w.y;
+ break;
+ case 2:
+ var e12x = this.m_v[1].w.x - this.m_v[0].w.x;
+ var e12y = this.m_v[1].w.y - this.m_v[0].w.y;
+ var sgn = e12x * -this.m_v[0].w.y - e12y * -this.m_v[0].w.x;
+ if (sgn > 0) {
+ p.x = -1 * e12y;
+ p.y = 1 * e12x
+ } else {
+ p.x = 1 * e12y;
+ p.y = -1 * e12x
+ }
+ break
+ }
+ },
+ GetClosestPoint: function (p) {
+ switch (this.m_count) {
+ case 1:
+ p.x = this.m_v[0].w.x;
+ p.y = this.m_v[0].w.y;
+ break;
+ case 2:
+ p.x = (this.m_v[0].a * this.m_v[0].w.x) + (this.m_v[1].a * this.m_v[1].w.x);
+ p.y = (this.m_v[0].a * this.m_v[0].w.y) + (this.m_v[1].a * this.m_v[1].w.y);
+ break;
+ case 3:
+ p.x = p.y = 0;
+ break
+ }
+ },
+ GetWitnessPoints: function (pA, pB) {
+ switch (this.m_count) {
+ case 1:
+ pA.x = this.m_v[0].wA.x;
+ pA.y = this.m_v[0].wA.y;
+ pB.x = this.m_v[0].wB.x;
+ pB.y = this.m_v[0].wB.y;
+ break;
+ case 2:
+ pA.x = (this.m_v[0].a * this.m_v[0].wA.x) + (this.m_v[1].a * this.m_v[1].wA.x);
+ pA.y = (this.m_v[0].a * this.m_v[0].wA.y) + (this.m_v[1].a * this.m_v[1].wA.y);
+ pB.x = (this.m_v[0].a * this.m_v[0].wB.x) + (this.m_v[1].a * this.m_v[1].wB.x);
+ pB.y = (this.m_v[0].a * this.m_v[0].wB.y) + (this.m_v[1].a * this.m_v[1].wB.y);
+ break;
+ case 3:
+ pA.x = (this.m_v[0].a, this.m_v[0].wA.x) + (this.m_v[1].a, this.m_v[1].wA.x) + (this.m_v[2].a, this.m_v[2].wA.x);
+ pA.y = (this.m_v[0].a, this.m_v[0].wA.y) + (this.m_v[1].a, this.m_v[1].wA.y) + (this.m_v[2].a, this.m_v[2].wA.y);
+ pB.x = pA.x;
+ pB.y = pA.y;
+ break
+ }
+ },
+ GetMetric: function () {
+ switch (this.m_count) {
+ case 1:
+ return 0;
+ case 2:
+ return b2Distance(this.m_v[0].w, this.m_v[1].w);
+ case 3:
+ return (this.m_v[1].w.x - this.m_v[0].w.x) * (this.m_v[2].w.y - this.m_v[0].w.y) - (this.m_v[1].w.y - this.m_v[0].w.y) * (this.m_v[2].w.x - this.m_v[0].w.x)
+ }
+ },
+ Solve2: function () {
+ var w1 = this.m_v[0].w;
+ var w2 = this.m_v[1].w;
+ var e12x = w2.x - w1.x;
+ var e12y = w2.y - w1.y;
+ var d12_2 = -(w1.x * e12x + w1.y * e12y);
+ if (d12_2 <= 0) {
+ this.m_v[0].a = 1;
+ this.m_count = 1;
+ return
+ }
+ var d12_1 = w2.x * e12x + w2.y * e12y;
+ if (d12_1 <= 0) {
+ this.m_v[1].a = 1;
+ this.m_count = 1;
+ this.m_v[0].Assign(this.m_v[1]);
+ return
+ }
+ var inv_d12 = 1 / (d12_1 + d12_2);
+ this.m_v[0].a = d12_1 * inv_d12;
+ this.m_v[1].a = d12_2 * inv_d12;
+ this.m_count = 2
+ },
+ Solve3: function () {
+ var w1 = this.m_v[0].w;
+ var w2 = this.m_v[1].w;
+ var w3 = this.m_v[2].w;
+ var e12x = w2.x - w1.x;
+ var e12y = w2.y - w1.y;
+ var w1e12 = w1.x * e12x + w1.y * e12y;
+ var w2e12 = w2.x * e12x + w2.y * e12y;
+ var d12_1 = w2e12;
+ var d12_2 = -w1e12;
+ var e13x = w3.x - w1.x;
+ var e13y = w3.y - w1.y;
+ var w1e13 = w1.x * e13x + w1.y * e13y;
+ var w3e13 = w3.x * e13x + w3.y * e13y;
+ var d13_1 = w3e13;
+ var d13_2 = -w1e13;
+ var e23x = w3.x - w2.x;
+ var e23y = w3.y - w2.y;
+ var w2e23 = w2.x * e23x + w2.y * e23y;
+ var w3e23 = w3.x * e23x + w3.y * e23y;
+ var d23_1 = w3e23;
+ var d23_2 = -w2e23;
+ var n123 = e12x * e13y - e12y * e13x;
+ var d123_1 = n123 * (w2.x * w3.y - w2.y * w3.x);
+ var d123_2 = n123 * (w3.x * w1.y - w3.y * w1.x);
+ var d123_3 = n123 * (w1.x * w2.y - w1.y * w2.x);
+ if (d12_2 <= 0 && d13_2 <= 0) {
+ this.m_v[0].a = 1;
+ this.m_count = 1;
+ return
+ }
+ if (d12_1 > 0 && d12_2 > 0 && d123_3 <= 0) {
+ var inv_d12 = 1 / (d12_1 + d12_2);
+ this.m_v[0].a = d12_1 * inv_d12;
+ this.m_v[1].a = d12_2 * inv_d12;
+ this.m_count = 2;
+ return
+ }
+ if (d13_1 > 0 && d13_2 > 0 && d123_2 <= 0) {
+ var inv_d13 = 1 / (d13_1 + d13_2);
+ this.m_v[0].a = d13_1 * inv_d13;
+ this.m_v[2].a = d13_2 * inv_d13;
+ this.m_count = 2;
+ this.m_v[1].Assign(this.m_v[2]);
+ return
+ }
+ if (d12_1 <= 0 && d23_2 <= 0) {
+ this.m_v[1].a = 1;
+ this.m_count = 1;
+ this.m_v[0].Assign(this.m_v[1]);
+ return
+ }
+ if (d13_1 <= 0 && d23_1 <= 0) {
+ this.m_v[2].a = 1;
+ this.m_count = 1;
+ this.m_v[0].Assign(this.m_v[2]);
+ return
+ }
+ if (d23_1 > 0 && d23_2 > 0 && d123_1 <= 0) {
+ var inv_d23 = 1 / (d23_1 + d23_2);
+ this.m_v[1].a = d23_1 * inv_d23;
+ this.m_v[2].a = d23_2 * inv_d23;
+ this.m_count = 2;
+ this.m_v[0].Assign(this.m_v[2]);
+ return
+ }
+ var inv_d123 = 1 / (d123_1 + d123_2 + d123_3);
+ this.m_v[0].a = d123_1 * inv_d123;
+ this.m_v[1].a = d123_2 * inv_d123;
+ this.m_v[2].a = d123_3 * inv_d123;
+ this.m_count = 3
+ }
+};
+var _b2Distance_simplex = new b2Simplex();
+var _b2Distance_normal = new b2Vec2();
+var _b2Distance_p = new b2Vec2();
+
+function b2DistanceFunc(output, cache, input) {
+ ++b2DistanceFunc.b2_gjkCalls;
+ var proxyA = input.proxyA;
+ var proxyB = input.proxyB;
+ var transformA = input.transformA;
+ var transformB = input.transformB;
+ _b2Distance_simplex.ReadCache(cache, proxyA, transformA, proxyB, transformB);
+ var vertices = _b2Distance_simplex.m_v;
+ var k_maxIters = 20;
+ var saveA = [0, 0, 0],
+ saveB = [0, 0, 0];
+ var saveCount = 0;
+ var distanceSqr1 = b2_maxFloat;
+ var distanceSqr2 = distanceSqr1;
+ var iter = 0;
+ while (iter < k_maxIters) {
+ saveCount = _b2Distance_simplex.m_count;
+ for (var i = 0; i < saveCount; ++i) {
+ saveA[i] = vertices[i].indexA;
+ saveB[i] = vertices[i].indexB
+ }
+ switch (_b2Distance_simplex.m_count) {
+ case 1:
+ break;
+ case 2:
+ _b2Distance_simplex.Solve2();
+ break;
+ case 3:
+ _b2Distance_simplex.Solve3();
+ break
+ }
+ if (_b2Distance_simplex.m_count == 3) {
+ break
+ }
+ _b2Distance_simplex.GetClosestPoint(_b2Distance_p);
+ distanceSqr2 = _b2Distance_p.LengthSquared();
+ if (distanceSqr2 >= distanceSqr1) {}
+ distanceSqr1 = distanceSqr2;
+ _b2Distance_simplex.GetSearchDirection(_b2Distance_p);
+ if (_b2Distance_p.LengthSquared() < b2_epsilon * b2_epsilon) {
+ break
+ }
+ var vertex = vertices[_b2Distance_simplex.m_count];
+ vertex.indexA = proxyA.GetSupport(transformA.q.c * -_b2Distance_p.x + transformA.q.s * -_b2Distance_p.y, -transformA.q.s * -_b2Distance_p.x + transformA.q.c * -_b2Distance_p.y);
+ var pva = proxyA.GetVertex(vertex.indexA);
+ vertex.wA.x = (transformA.q.c * pva.x - transformA.q.s * pva.y) + transformA.p.x;
+ vertex.wA.y = (transformA.q.s * pva.x + transformA.q.c * pva.y) + transformA.p.y;
+ vertex.indexB = proxyB.GetSupport(transformB.q.c * _b2Distance_p.x + transformB.q.s * _b2Distance_p.y, -transformB.q.s * _b2Distance_p.x + transformB.q.c * _b2Distance_p.y);
+ var pvb = proxyB.GetVertex(vertex.indexB);
+ vertex.wB.x = (transformB.q.c * pvb.x - transformB.q.s * pvb.y) + transformB.p.x;
+ vertex.wB.y = (transformB.q.s * pvb.x + transformB.q.c * pvb.y) + transformB.p.y;
+ vertex.w.x = vertex.wB.x - vertex.wA.x;
+ vertex.w.y = vertex.wB.y - vertex.wA.y;
+ ++iter;
+ ++b2DistanceFunc.b2_gjkIters;
+ var duplicate = false;
+ for (var i = 0; i < saveCount; ++i) {
+ if (vertex.indexA == saveA[i] && vertex.indexB == saveB[i]) {
+ duplicate = true;
+ break
+ }
+ }
+ if (duplicate) {
+ break
+ }++_b2Distance_simplex.m_count
+ }
+ b2DistanceFunc.b2_gjkMaxIters = b2Max(b2DistanceFunc.b2_gjkMaxIters, iter);
+ _b2Distance_simplex.GetWitnessPoints(output.pointA, output.pointB);
+ output.distance = b2Distance(output.pointA, output.pointB);
+ output.iterations = iter;
+ _b2Distance_simplex.WriteCache(cache);
+ if (input.useRadii) {
+ var rA = proxyA.m_radius;
+ var rB = proxyB.m_radius;
+ if (output.distance > rA + rB && output.distance > b2_epsilon) {
+ output.distance -= rA + rB;
+ _b2Distance_normal.x = output.pointB.x - output.pointA.x;
+ _b2Distance_normal.y = output.pointB.y - output.pointA.y;
+ _b2Distance_normal.Normalize();
+ output.pointA.x += (rA * _b2Distance_normal.x);
+ output.pointA.y += (rA * _b2Distance_normal.y);
+ output.pointB.x -= (rB * _b2Distance_normal.x);
+ output.pointB.y -= (rB * _b2Distance_normal.y)
+ } else {
+ var px = (0.5 * (output.pointA.x + output.pointB.x));
+ var py = (0.5 * (output.pointA.y + output.pointB.y));
+ output.pointA.x = px;
+ output.pointA.y = py;
+ output.pointB.x = px;
+ output.pointB.y = py;
+ output.distance = 0
+ }
+ }
+}
+b2DistanceFunc.b2_gjkCalls = 0;
+b2DistanceFunc.b2_gjkIters = 0;
+b2DistanceFunc.b2_gjkMaxIters = 0;
+"use strict";
+var b2_nullFeature = 255;
+
+function b2ContactID() {}
+b2ContactID.prototype = {
+ indexA: 0,
+ indexB: 0,
+ typeA: 0,
+ typeB: 0,
+ Reset: function () {
+ this.indexA = this.indexB = this.typeA = this.typeB = 0
+ },
+ Get: function () {
+ return this.indexA | (this.indexB << 8) | (this.typeA << 16) | (this.typeB << 24)
+ },
+ Assign: function (k) {
+ this.indexA = k.indexA;
+ this.indexB = k.indexB;
+ this.typeA = k.typeA;
+ this.typeB = k.typeB
+ }
+};
+b2ContactID.e_vertex = 0;
+b2ContactID.e_face = 1;
+
+function b2ManifoldPoint() {
+ this.localPoint = new b2Vec2();
+ this.normalImpulse = 0;
+ this.tangentImpulse = 0;
+ this.id = new b2ContactID()
+}
+b2ManifoldPoint.prototype = {
+ Clone: function () {
+ var point = new b2ManifoldPoint();
+ point.localPoint.x = this.localPoint.x;
+ point.localPoint.y = this.localPoint.y;
+ point.normalImpulse = this.normalImpulse;
+ point.tangentImpulse = this.tangentImpulse;
+ point.id.Assign(this.id);
+ return point
+ }
+};
+
+function b2Manifold() {
+ this.points = new Array(b2_maxManifoldPoints);
+ this.localNormal = new b2Vec2();
+ this.localPoint = new b2Vec2();
+ this.type = 0;
+ this.pointCount = 0
+}
+b2Manifold.prototype = {
+ Clone: function () {
+ var manifold = new b2Manifold();
+ manifold.pointCount = this.pointCount;
+ manifold.type = this.type;
+ manifold.localPoint.x = this.localPoint.x;
+ manifold.localPoint.y = this.localPoint.y;
+ manifold.localNormal.x = this.localNormal.x;
+ manifold.localNormal.y = this.localNormal.y;
+ for (var i = 0; i < this.pointCount; ++i) {
+ manifold.points[i] = this.points[i].Clone()
+ }
+ return manifold
+ },
+ Assign: function (manifold) {
+ this.pointCount = manifold.pointCount;
+ this.type = manifold.type;
+ this.localPoint.x = manifold.localPoint.x;
+ this.localPoint.y = manifold.localPoint.y;
+ this.localNormal.x = manifold.localNormal.x;
+ this.localNormal.y = manifold.localNormal.y;
+ for (var i = 0; i < this.pointCount; ++i) {
+ this.points[i] = manifold.points[i].Clone()
+ }
+ }
+};
+b2Manifold.e_circles = 0;
+b2Manifold.e_faceA = 1;
+b2Manifold.e_faceB = 2;
+b2Manifold.b2_nullState = 0;
+b2Manifold.b2_addState = 1;
+b2Manifold.b2_persistState = 2;
+b2Manifold.b2_removeState = 3;
+
+function b2WorldManifold() {
+ this.normal = new b2Vec2();
+ this.points = new Array(b2_maxManifoldPoints);
+ this.separations = new Array(b2_maxManifoldPoints)
+}
+b2WorldManifold.prototype = {
+ Initialize: function (manifold, xfA, radiusA, xfB, radiusB) {
+ if (manifold.pointCount == 0) {
+ return
+ }
+ switch (manifold.type) {
+ case b2Manifold.e_circles:
+ this.normal.x = 1;
+ this.normal.y = 0;
+ var pointAx = (xfA.q.c * manifold.localPoint.x - xfA.q.s * manifold.localPoint.y) + xfA.p.x;
+ var pointAy = (xfA.q.s * manifold.localPoint.x + xfA.q.c * manifold.localPoint.y) + xfA.p.y;
+ var pointBx = (xfB.q.c * manifold.points[0].localPoint.x - xfB.q.s * manifold.points[0].localPoint.y) + xfB.p.x;
+ var pointBy = (xfB.q.s * manifold.points[0].localPoint.x + xfB.q.c * manifold.points[0].localPoint.y) + xfB.p.y;
+ var cx = pointAx - pointBx;
+ var cy = pointAy - pointBy;
+ if ((cx * cx + cy * cy) > b2_epsilon * b2_epsilon) {
+ this.normal.x = pointBx - pointAx;
+ this.normal.y = pointBy - pointAy;
+ this.normal.Normalize()
+ }
+ var cAx = pointAx + (radiusA * this.normal.x);
+ var cAy = pointAy + (radiusA * this.normal.y);
+ var cBx = pointBx - (radiusB * this.normal.x);
+ var cBy = pointBy - (radiusB * this.normal.y);
+ this.points[0] = new b2Vec2(0.5 * (cAx + cBx), 0.5 * (cAy + cBy));
+ this.separations[0] = (cBx - cAx) * this.normal.x + (cBy - cAy) * this.normal.y;
+ break;
+ case b2Manifold.e_faceA:
+ this.normal.x = xfA.q.c * manifold.localNormal.x - xfA.q.s * manifold.localNormal.y;
+ this.normal.y = xfA.q.s * manifold.localNormal.x + xfA.q.c * manifold.localNormal.y;
+ var planePointx = (xfA.q.c * manifold.localPoint.x - xfA.q.s * manifold.localPoint.y) + xfA.p.x;
+ var planePointy = (xfA.q.s * manifold.localPoint.x + xfA.q.c * manifold.localPoint.y) + xfA.p.y;
+ for (var i = 0; i < manifold.pointCount; ++i) {
+ var clipPointx = (xfB.q.c * manifold.points[i].localPoint.x - xfB.q.s * manifold.points[i].localPoint.y) + xfB.p.x;
+ var clipPointy = (xfB.q.s * manifold.points[i].localPoint.x + xfB.q.c * manifold.points[i].localPoint.y) + xfB.p.y;
+ var d = (clipPointx - planePointx) * this.normal.x + (clipPointy - planePointy) * this.normal.y;
+ var cAx = clipPointx + ((radiusA - d) * this.normal.x);
+ var cAy = clipPointy + ((radiusA - d) * this.normal.y);
+ var cBx = (clipPointx - (radiusB * this.normal.x));
+ var cBy = (clipPointy - (radiusB * this.normal.y));
+ this.points[i] = new b2Vec2(0.5 * (cAx + cBx), 0.5 * (cAy + cBy));
+ this.separations[i] = (cBx - cAx) * this.normal.x + (cBy - cAy) * this.normal.y
+ }
+ break;
+ case b2Manifold.e_faceB:
+ this.normal.x = xfB.q.c * manifold.localNormal.x - xfB.q.s * manifold.localNormal.y;
+ this.normal.y = xfB.q.s * manifold.localNormal.x + xfB.q.c * manifold.localNormal.y;
+ var planePointx = (xfB.q.c * manifold.localPoint.x - xfB.q.s * manifold.localPoint.y) + xfB.p.x;
+ var planePointy = (xfB.q.s * manifold.localPoint.x + xfB.q.c * manifold.localPoint.y) + xfB.p.y;
+ for (var i = 0; i < manifold.pointCount; ++i) {
+ var clipPointx = (xfA.q.c * manifold.points[i].localPoint.x - xfA.q.s * manifold.points[i].localPoint.y) + xfA.p.x;
+ var clipPointy = (xfA.q.s * manifold.points[i].localPoint.x + xfA.q.c * manifold.points[i].localPoint.y) + xfA.p.y;
+ var d = (clipPointx - planePointx) * this.normal.x + (clipPointy - planePointy) * this.normal.y;
+ var cBx = clipPointx + ((radiusB - d) * this.normal.x);
+ var cBy = clipPointy + ((radiusB - d) * this.normal.y);
+ var cAx = (clipPointx - (radiusA * this.normal.x));
+ var cAy = (clipPointy - (radiusA * this.normal.y));
+ this.points[i] = new b2Vec2(0.5 * (cAx + cBx), 0.5 * (cAy + cBy));
+ this.separations[i] = (cAx - cBx) * this.normal.x + (cAy - cBy) * this.normal.y
+ }
+ this.normal.x = -this.normal.x;
+ this.normal.y = -this.normal.y;
+ break
+ }
+ }
+};
+
+function b2GetPointStates(state1, state2, manifold1, manifold2) {
+ for (var i = 0; i < b2_maxManifoldPoints; ++i) {
+ state1[i] = b2Manifold.b2_nullState;
+ state2[i] = b2Manifold.b2_nullState
+ }
+ for (var i = 0; i < manifold1.pointCount; ++i) {
+ var id = manifold1.points[i].id;
+ state1[i] = b2Manifold.b2_removeState;
+ for (var j = 0; j < manifold2.pointCount; ++j) {
+ if (manifold2.points[j].id.Get() == id.Get()) {
+ state1[i] = b2Manifold.b2_persistState;
+ break
+ }
+ }
+ }
+ for (var i = 0; i < manifold2.pointCount; ++i) {
+ var id = manifold2.points[i].id;
+ state2[i] = b2Manifold.b2_addState;
+ for (var j = 0; j < manifold1.pointCount; ++j) {
+ if (manifold1.points[j].id.Get() == id.Get()) {
+ state2[i] = b2Manifold.b2_persistState;
+ break
+ }
+ }
+ }
+}
+
+function b2ClipVertex() {
+ this.v = new b2Vec2();
+ this.id = new b2ContactID()
+}
+
+function b2RayCastInput() {
+ this.p1 = new b2Vec2(), this.p2 = new b2Vec2();
+ this.maxFraction = 0
+}
+
+function b2RayCastOutput() {
+ this.normal = new b2Vec2();
+ this.fraction = 0
+}
+
+function b2AABB() {
+ this.lowerBound = new b2Vec2();
+ this.upperBound = new b2Vec2()
+}
+b2AABB.prototype = {
+ Assign: function (other) {
+ this.lowerBound.x = other.lowerBound.x;
+ this.lowerBound.y = other.lowerBound.y;
+ this.upperBound.x = other.upperBound.x;
+ this.upperBound.y = other.upperBound.y
+ },
+ Clone: function () {
+ var clone = new b2AABB();
+ clone.lowerBound.x = this.lowerBound.x;
+ clone.lowerBound.y = this.lowerBound.y;
+ clone.lowerBound.x = this.lowerBound.x;
+ clone.lowerBound.y = this.lowerBound.y;
+ return clone
+ },
+ IsValid: function () {
+ return (this.upperBound.x - this.lowerBound.x) >= 0 && (this.upperBound.y - this.lowerBound.y) >= 0 && this.lowerBound.IsValid() && this.upperBound.IsValid()
+ },
+ GetCenter: function () {
+ return new b2Vec2(0.5 * (this.lowerBound.x + this.upperBound.x), 0.5 * (this.lowerBound.y + this.upperBound.y))
+ },
+ GetExtents: function () {
+ return new b2Vec2(0.5 * (this.upperBound.x - this.lowerBound.x), 0.5 * (this.upperBound.y - this.lowerBound.y))
+ },
+ GetPerimeter: function () {
+ return 2 * ((this.upperBound.x - this.lowerBound.x) + (this.upperBound.y - this.lowerBound.y))
+ },
+ Combine: function (aabb1, aabb2) {
+ if (aabb2) {
+ this.lowerBound.x = b2Min(aabb1.lowerBound.x, aabb2.lowerBound.x);
+ this.lowerBound.y = b2Min(aabb1.lowerBound.y, aabb2.lowerBound.y);
+ this.upperBound.x = b2Max(aabb1.upperBound.x, aabb2.upperBound.x);
+ this.upperBound.y = b2Max(aabb1.upperBound.y, aabb2.upperBound.y)
+ } else {
+ this.lowerBound.x = b2Min(this.lowerBound.x, aabb1.lowerBound.x);
+ this.lowerBound.y = b2Min(this.lowerBound.y, aabb1.lowerBound.y);
+ this.upperBound.x = b2Max(this.upperBound.x, aabb1.upperBound.x);
+ this.upperBound.y = b2Max(this.upperBound.y, aabb1.upperBound.y)
+ }
+ },
+ Contains: function (aabb) {
+ return this.lowerBound.x <= aabb.lowerBound.x && this.lowerBound.y <= aabb.lowerBound.y && aabb.upperBound.x <= this.upperBound.x && aabb.upperBound.y <= this.upperBound.y
+ },
+ RayCast: function (output, input) {
+ var tmin = -b2_maxFloat;
+ var tmax = b2_maxFloat;
+ var p = input.p1;
+ var d = b2Vec2.Subtract(input.p2, input.p1);
+ var absD = b2Abs_v2(d);
+ var normal = new b2Vec2();
+ for (var i = 0; i < 2; ++i) {
+ if (absD.get_i(i) < b2_epsilon) {
+ if (p.get_i(i) < this.lowerBound.get_i(i) || this.upperBound.get_i(i) < p.get_i(i)) {
+ return false
+ }
+ } else {
+ var inv_d = 1 / d.get_i(i);
+ var t1 = (this.lowerBound.get_i(i) - p.get_i(i)) * inv_d;
+ var t2 = (this.upperBound.get_i(i) - p.get_i(i)) * inv_d;
+ var s = -1;
+ if (t1 > t2) {
+ var temp = t2;
+ t2 = t1;
+ t1 = temp;
+ s = 1
+ }
+ if (t1 > tmin) {
+ normal.x = normal.y = 0;
+ normal.set_i(i, s);
+ tmin = t1
+ }
+ tmax = b2Min(tmax, t2);
+ if (tmin > tmax) {
+ return false
+ }
+ }
+ }
+ if (tmin < 0 || input.maxFraction < tmin) {
+ return false
+ }
+ output.fraction = tmin;
+ output.normal.x = normal.x;
+ output.normal.y = normal.y;
+ return true
+ }
+};
+
+function b2CollideCircles(manifold, circleA, xfA, circleB, xfB) {
+ manifold.pointCount = 0;
+ var pA = b2Mul_t_v2(xfA, circleA.m_p);
+ var pB = b2Mul_t_v2(xfB, circleB.m_p);
+ var dx = pB.x - pA.x;
+ var dy = pB.y - pA.y;
+ var distSqr = dx * dx + dy * dy;
+ var rA = circleA.m_radius,
+ rB = circleB.m_radius;
+ var radius = rA + rB;
+ if (distSqr > radius * radius) {
+ return
+ }
+ manifold.type = b2Manifold.e_circles;
+ manifold.localPoint.x = circleA.m_p.x;
+ manifold.localPoint.y = circleA.m_p.y;
+ manifold.localNormal.x = manifold.localNormal.y = 0;
+ manifold.pointCount = 1;
+ manifold.points[0] = new b2ManifoldPoint();
+ manifold.points[0].localPoint.x = circleB.m_p.x;
+ manifold.points[0].localPoint.y = circleB.m_p.y;
+ manifold.points[0].id.Reset()
+}
+
+function b2CollidePolygonAndCircle(manifold, polygonA, xfA, circleB, xfB) {
+ manifold.pointCount = 0;
+ var c = b2Mul_t_v2(xfB, circleB.m_p);
+ var cLocal = b2MulT_t_v2(xfA, c);
+ var normalIndex = 0;
+ var separation = -b2_maxFloat;
+ var radius = polygonA.m_radius + circleB.m_radius;
+ var vertexCount = polygonA.m_count;
+ var vertices = polygonA.m_vertices;
+ var normals = polygonA.m_normals;
+ for (var i = 0; i < vertexCount; ++i) {
+ var s = normals[i].x * (cLocal.x - vertices[i].x) + normals[i].y * (cLocal.y - vertices[i].y);
+ if (s > radius) {
+ return
+ }
+ if (s > separation) {
+ separation = s;
+ normalIndex = i
+ }
+ }
+ var vertIndex1 = normalIndex;
+ var vertIndex2 = vertIndex1 + 1 < vertexCount ? vertIndex1 + 1 : 0;
+ var v1 = vertices[vertIndex1];
+ var v2 = vertices[vertIndex2];
+ if (separation < b2_epsilon) {
+ manifold.pointCount = 1;
+ manifold.type = b2Manifold.e_faceA;
+ manifold.localNormal.x = normals[normalIndex].x;
+ manifold.localNormal.y = normals[normalIndex].y;
+ manifold.localPoint.x = 0.5 * (v1.x + v2.x);
+ manifold.localPoint.y = 0.5 * (v1.y + v2.y);
+ manifold.points[0] = new b2ManifoldPoint();
+ manifold.points[0].localPoint.x = circleB.m_p.x;
+ manifold.points[0].localPoint.y = circleB.m_p.y;
+ manifold.points[0].id.Reset();
+ return
+ }
+ var u1 = (cLocal.x - v1.x) * (v2.x - v1.x) + (cLocal.y - v1.y) * (v2.y - v1.y);
+ var u2 = (cLocal.x - v2.x) * (v1.x - v2.x) + (cLocal.y - v2.y) * (v1.y - v2.y);
+ if (u1 <= 0) {
+ if (b2DistanceSquared(cLocal, v1) > radius * radius) {
+ return
+ }
+ manifold.pointCount = 1;
+ manifold.type = b2Manifold.e_faceA;
+ manifold.localNormal.x = cLocal.x - v1.x;
+ manifold.localNormal.y = cLocal.y - v1.y;
+ manifold.localNormal.Normalize();
+ manifold.localPoint.x = v1.x;
+ manifold.localPoint.y = v1.y;
+ manifold.points[0] = new b2ManifoldPoint();
+ manifold.points[0].localPoint.x = circleB.m_p.x;
+ manifold.points[0].localPoint.y = circleB.m_p.y;
+ manifold.points[0].id.Reset()
+ } else {
+ if (u2 <= 0) {
+ if (b2DistanceSquared(cLocal, v2) > radius * radius) {
+ return
+ }
+ manifold.pointCount = 1;
+ manifold.type = b2Manifold.e_faceA;
+ manifold.localNormal.x = cLocal.x - v2.x;
+ manifold.localNormal.y = cLocal.y - v2.y;
+ manifold.localNormal.Normalize();
+ manifold.localPoint.x = v2.x;
+ manifold.localPoint.y = v2.y;
+ manifold.points[0] = new b2ManifoldPoint();
+ manifold.points[0].localPoint.x = circleB.m_p.x;
+ manifold.points[0].localPoint.y = circleB.m_p.y;
+ manifold.points[0].id.Reset()
+ } else {
+ var faceCenterx = 0.5 * (v1.x + v2.x);
+ var faceCentery = 0.5 * (v1.y + v2.y);
+ var separation = (cLocal.x - faceCenterx) * normals[vertIndex1].x + (cLocal.y - faceCentery) * normals[vertIndex1].y;
+ if (separation > radius) {
+ return
+ }
+ manifold.pointCount = 1;
+ manifold.type = b2Manifold.e_faceA;
+ manifold.localNormal.x = normals[vertIndex1].x;
+ manifold.localNormal.y = normals[vertIndex1].y;
+ manifold.localPoint.x = faceCenterx;
+ manifold.localPoint.y = faceCentery;
+ manifold.points[0] = new b2ManifoldPoint();
+ manifold.points[0].localPoint.x = circleB.m_p.x;
+ manifold.points[0].localPoint.y = circleB.m_p.y;
+ manifold.points[0].id.Reset()
+ }
+ }
+}
+
+function b2FindMaxSeparation(edgeIndex, poly1, xf1, poly2, xf2) {
+ var count1 = poly1.m_count;
+ var count2 = poly2.m_count;
+ var n1s = poly1.m_normals;
+ var v1s = poly1.m_vertices;
+ var v2s = poly2.m_vertices;
+ var xf = b2MulT_t_t(xf2, xf1);
+ var bestIndex = 0;
+ var maxSeparation = -b2_maxFloat;
+ for (var i = 0; i < count1; ++i) {
+ var nx = xf.q.c * n1s[i].x - xf.q.s * n1s[i].y;
+ var ny = xf.q.s * n1s[i].x + xf.q.c * n1s[i].y;
+ var v1x = (xf.q.c * v1s[i].x - xf.q.s * v1s[i].y) + xf.p.x;
+ var v1y = (xf.q.s * v1s[i].x + xf.q.c * v1s[i].y) + xf.p.y;
+ var si = b2_maxFloat;
+ for (var j = 0; j < count2; ++j) {
+ var sij = nx * (v2s[j].x - v1x) + ny * (v2s[j].y - v1y);
+ if (sij < si) {
+ si = sij
+ }
+ }
+ if (si > maxSeparation) {
+ maxSeparation = si;
+ bestIndex = i
+ }
+ }
+ edgeIndex[0] = bestIndex;
+ return maxSeparation
+}
+
+function b2FindIncidentEdge(c, poly1, xf1, edge1, poly2, xf2) {
+ var normals1 = poly1.m_normals;
+ var count2 = poly2.m_count;
+ var vertices2 = poly2.m_vertices;
+ var normals2 = poly2.m_normals;
+ var t1x = xf1.q.c * normals1[edge1].x - xf1.q.s * normals1[edge1].y;
+ var t1y = xf1.q.s * normals1[edge1].x + xf1.q.c * normals1[edge1].y;
+ var normal1x = xf2.q.c * t1x + xf2.q.s * t1y;
+ var normal1y = -xf2.q.s * t1x + xf2.q.c * t1y;
+ var index = 0;
+ var minDot = b2_maxFloat;
+ for (var i = 0; i < count2; ++i) {
+ var dot = normal1x * normals2[i].x + normal1y * normals2[i].y;
+ if (dot < minDot) {
+ minDot = dot;
+ index = i
+ }
+ }
+ var i1 = index;
+ var i2 = i1 + 1 < count2 ? i1 + 1 : 0;
+ c[0].v.x = (xf2.q.c * vertices2[i1].x - xf2.q.s * vertices2[i1].y) + xf2.p.x;
+ c[0].v.y = (xf2.q.s * vertices2[i1].x + xf2.q.c * vertices2[i1].y) + xf2.p.y;
+ c[0].id.indexA = edge1;
+ c[0].id.indexB = i1;
+ c[0].id.typeA = b2ContactID.e_face;
+ c[0].id.typeB = b2ContactID.e_vertex;
+ c[1].v.x = (xf2.q.c * vertices2[i2].x - xf2.q.s * vertices2[i2].y) + xf2.p.x;
+ c[1].v.y = (xf2.q.s * vertices2[i2].x + xf2.q.c * vertices2[i2].y) + xf2.p.y;
+ c[1].id.indexA = edge1;
+ c[1].id.indexB = i2;
+ c[1].id.typeA = b2ContactID.e_face;
+ c[1].id.typeB = b2ContactID.e_vertex
+}
+
+function b2CollidePolygons(manifold, polyA, xfA, polyB, xfB) {
+ manifold.pointCount = 0;
+ var totalRadius = polyA.m_radius + polyB.m_radius;
+ var edgeA = [0];
+ var separationA = b2FindMaxSeparation(edgeA, polyA, xfA, polyB, xfB);
+ if (separationA > totalRadius) {
+ return
+ }
+ var edgeB = [0];
+ var separationB = b2FindMaxSeparation(edgeB, polyB, xfB, polyA, xfA);
+ if (separationB > totalRadius) {
+ return
+ }
+ var poly1;
+ var poly2;
+ var xf1, xf2;
+ var edge1 = 0;
+ var flip = 0;
+ var k_tol = 0.1 * b2_linearSlop;
+ if (separationB > separationA + k_tol) {
+ poly1 = polyB;
+ poly2 = polyA;
+ xf1 = xfB;
+ xf2 = xfA;
+ edge1 = edgeB[0];
+ manifold.type = b2Manifold.e_faceB;
+ flip = 1
+ } else {
+ poly1 = polyA;
+ poly2 = polyB;
+ xf1 = xfA;
+ xf2 = xfB;
+ edge1 = edgeA[0];
+ manifold.type = b2Manifold.e_faceA;
+ flip = 0
+ }
+ b2FindIncidentEdge(b2CollidePolygons._local_incidentEdges, poly1, xf1, edge1, poly2, xf2);
+ var count1 = poly1.m_count;
+ var vertices1 = poly1.m_vertices;
+ var iv1 = edge1;
+ var iv2 = edge1 + 1 < count1 ? edge1 + 1 : 0;
+ var v11 = vertices1[iv1];
+ var v12 = vertices1[iv2];
+ b2CollidePolygons._localTangent.x = v12.x - v11.x;
+ b2CollidePolygons._localTangent.y = v12.y - v11.y;
+ b2CollidePolygons._localTangent.Normalize();
+ var localNormalx = 1 * b2CollidePolygons._localTangent.y;
+ var localNormaly = -1 * b2CollidePolygons._localTangent.x;
+ var planePointx = 0.5 * (v11.x + v12.x);
+ var planePointy = 0.5 * (v11.y + v12.y);
+ var tangentx = xf1.q.c * b2CollidePolygons._localTangent.x - xf1.q.s * b2CollidePolygons._localTangent.y;
+ var tangenty = xf1.q.s * b2CollidePolygons._localTangent.x + xf1.q.c * b2CollidePolygons._localTangent.y;
+ var normalx = 1 * tangenty;
+ var normaly = -1 * tangentx;
+ v11 = b2Mul_t_v2(xf1, v11);
+ v12 = b2Mul_t_v2(xf1, v12);
+ var frontOffset = normalx * v11.x + normaly * v11.y;
+ var sideOffset1 = -(tangentx * v11.x + tangenty * v11.y) + totalRadius;
+ var sideOffset2 = (tangentx * v12.x + tangenty * v12.y) + totalRadius;
+ var clipPoints1 = new Array(2);
+ var clipPoints2 = new Array(2);
+ var np;
+ np = b2ClipSegmentToLine(clipPoints1, b2CollidePolygons._local_incidentEdges, -tangentx, -tangenty, sideOffset1, iv1);
+ if (np < 2) {
+ return
+ }
+ np = b2ClipSegmentToLine(clipPoints2, clipPoints1, tangentx, tangenty, sideOffset2, iv2);
+ if (np < 2) {
+ return
+ }
+ manifold.localNormal.x = localNormalx;
+ manifold.localNormal.y = localNormaly;
+ manifold.localPoint.x = planePointx;
+ manifold.localPoint.y = planePointy;
+ var pointCount = 0;
+ for (var i = 0; i < b2_maxManifoldPoints; ++i) {
+ var separation = (normalx * clipPoints2[i].v.x + normaly * clipPoints2[i].v.y) - frontOffset;
+ if (separation <= totalRadius) {
+ var cp = manifold.points[pointCount] = new b2ManifoldPoint();
+ cp.localPoint.Assign(b2MulT_t_v2(xf2, clipPoints2[i].v));
+ cp.id.Assign(clipPoints2[i].id);
+ if (flip) {
+ var cf = new b2ContactID();
+ cf.Assign(cp.id);
+ cp.id.indexA = cf.indexB;
+ cp.id.indexB = cf.indexA;
+ cp.id.typeA = cf.typeB;
+ cp.id.typeB = cf.typeA
+ }++pointCount
+ }
+ }
+ manifold.pointCount = pointCount
+}
+b2CollidePolygons._localTangent = new b2Vec2();
+b2CollidePolygons._local_incidentEdges = [new b2ClipVertex(), new b2ClipVertex()];
+
+function b2CollideEdgeAndCircle(manifold, edgeA, xfA, circleB, xfB) {
+ manifold.pointCount = 0;
+ var Q = b2MulT_t_v2(xfA, b2Mul_t_v2(xfB, circleB.m_p));
+ var A = edgeA.m_vertex1,
+ B = edgeA.m_vertex2;
+ var ex = B.x - A.x;
+ var ey = B.y - A.y;
+ var u = ex * (B.x - Q.x) + ey * (B.y - Q.y);
+ var v = ex * (Q.x - A.x) + ey * (Q.y - A.y);
+ var radius = edgeA.m_radius + circleB.m_radius;
+ var cf = new b2ContactID();
+ cf.indexB = 0;
+ cf.typeB = b2ContactID.e_vertex;
+ if (v <= 0) {
+ var P = A;
+ var dx = Q.x - P.x;
+ var dy = Q.y - P.y;
+ var dd = dx * dx + dy * dy;
+ if (dd > radius * radius) {
+ return
+ }
+ if (edgeA.m_hasVertex0) {
+ var A1 = edgeA.m_vertex0;
+ var B1 = A;
+ var e1x = B1.x - A1.x;
+ var e1y = B1.y - A1.y;
+ var u1 = e1x * (B1.x - Q.x) + e1y * (B1.y - Q.y);
+ if (u1 > 0) {
+ return
+ }
+ }
+ cf.indexA = 0;
+ cf.typeA = b2ContactID.e_vertex;
+ manifold.pointCount = 1;
+ manifold.type = b2Manifold.e_circles;
+ manifold.localNormal.x = manifold.localNormal.y = 0;
+ manifold.localPoint.x = P.x;
+ manifold.localPoint.y = P.y;
+ manifold.points[0] = new b2ManifoldPoint();
+ manifold.points[0].id.Assign(cf);
+ manifold.points[0].localPoint.x = circleB.m_p.x;
+ manifold.points[0].localPoint.y = circleB.m_p.y;
+ return
+ }
+ if (u <= 0) {
+ var P = B;
+ var dx = Q.x - P.x;
+ var dy = Q.y - P.y;
+ var dd = dx * dx + dy * dy;
+ if (dd > radius * radius) {
+ return
+ }
+ if (edgeA.m_hasVertex3) {
+ var B2 = edgeA.m_vertex3;
+ var A2 = B;
+ var e2x = B2.x - A2.x;
+ var e2y = B2.y - A2.y;
+ var v2 = e2x * (Q.x - A2.x) + e2y * (Q.y - A2.y);
+ if (v2 > 0) {
+ return
+ }
+ }
+ cf.indexA = 1;
+ cf.typeA = b2ContactID.e_vertex;
+ manifold.pointCount = 1;
+ manifold.type = b2Manifold.e_circles;
+ manifold.localNormal.x = manifold.localNormal.y = 0;
+ manifold.localPoint.x = P.x;
+ manifold.localPoint.y = P.y;
+ manifold.points[0] = new b2ManifoldPoint();
+ manifold.points[0].id.Assign(cf);
+ manifold.points[0].localPoint.x = circleB.m_p.x;
+ manifold.points[0].localPoint.y = circleB.m_p.y;
+ return
+ }
+ var den = ex * ex + ey * ey;
+ var Px = (1 / den) * ((u * A.x) + (v * B.x));
+ var Py = (1 / den) * ((u * A.y) + (v * B.y));
+ var dx = Q.x - Px;
+ var dy = Q.y - Py;
+ var dd = dx * dx + dy * dy;
+ if (dd > radius * radius) {
+ return
+ }
+ var nx = -ey;
+ var ny = ex;
+ if (nx * (Q.x - A.x) + ny * (Q.y - A.y) < 0) {
+ nx = -nx;
+ ny = -ny
+ }
+ cf.indexA = 0;
+ cf.typeA = b2ContactID.e_face;
+ manifold.pointCount = 1;
+ manifold.type = b2Manifold.e_faceA;
+ manifold.localNormal.x = nx;
+ manifold.localNormal.y = ny;
+ manifold.localNormal.Normalize();
+ manifold.localPoint.x = A.x;
+ manifold.localPoint.y = A.y;
+ manifold.points[0] = new b2ManifoldPoint();
+ manifold.points[0].id.Assign(cf);
+ manifold.points[0].localPoint.x = circleB.m_p.x;
+ manifold.points[0].localPoint.y = circleB.m_p.y
+}
+
+function b2EPAxis() {
+ this.type = 0;
+ this.index = 0;
+ this.separation = 0
+}
+b2EPAxis.e_unknown = 0;
+b2EPAxis.e_edgeA = 1;
+b2EPAxis.e_edgeB = 2;
+
+function b2TempPolygon() {
+ this.vertices = new Array(b2_maxPolygonVertices);
+ this.normals = new Array(b2_maxPolygonVertices);
+ this.count = 0
+}
+
+function b2ReferenceFace() {
+ this.i1 = 0, this.i2 = 0;
+ this.v1 = new b2Vec2(), this.v2 = new b2Vec2();
+ this.normal = new b2Vec2();
+ this.sideNormal1 = new b2Vec2();
+ this.sideOffset1 = 0;
+ this.sideNormal2 = new b2Vec2();
+ this.sideOffset2 = 0
+}
+
+function b2EPCollider() {
+ this.m_polygonB = new b2TempPolygon();
+ this.m_xf = new b2Transform();
+ this.m_centroidB = new b2Vec2();
+ this.m_v0 = new b2Vec2(), this.m_v1 = new b2Vec2(), this.m_v2 = new b2Vec2(), this.m_v3 = new b2Vec2();
+ this.m_normal0 = new b2Vec2(), this.m_normal1 = new b2Vec2(), this.m_normal2 = new b2Vec2();
+ this.m_normal = new b2Vec2();
+ this.m_type1 = 0, this.m_type2 = 0;
+ this.m_lowerLimit = new b2Vec2(), this.m_upperLimit = new b2Vec2();
+ this.m_radius = 0;
+ this.m_front = false
+}
+b2EPCollider._temp_edge = new b2Vec2();
+b2EPCollider._temp_edge0 = new b2Vec2();
+b2EPCollider._temp_edge2 = new b2Vec2();
+b2EPCollider.prototype = {
+ Collide: function (manifold, edgeA, xfA, polygonB, xfB) {
+ this.m_xf.Assign(b2MulT_t_t(xfA, xfB));
+ this.m_centroidB.x = (this.m_xf.q.c * polygonB.m_centroid.x - this.m_xf.q.s * polygonB.m_centroid.y) + this.m_xf.p.x;
+ this.m_centroidB.y = (this.m_xf.q.s * polygonB.m_centroid.x + this.m_xf.q.c * polygonB.m_centroid.y) + this.m_xf.p.y;
+ this.m_v0.x = edgeA.m_vertex0.x;
+ this.m_v0.y = edgeA.m_vertex0.y;
+ this.m_v1.x = edgeA.m_vertex1.x;
+ this.m_v1.y = edgeA.m_vertex1.y;
+ this.m_v2.x = edgeA.m_vertex2.x;
+ this.m_v2.y = edgeA.m_vertex2.y;
+ this.m_v3.x = edgeA.m_vertex3.x;
+ this.m_v3.y = edgeA.m_vertex3.y;
+ var hasVertex0 = edgeA.m_hasVertex0;
+ var hasVertex3 = edgeA.m_hasVertex3;
+ b2EPCollider._temp_edge.x = this.m_v2.x - this.m_v1.x;
+ b2EPCollider._temp_edge.y = this.m_v2.y - this.m_v1.y;
+ b2EPCollider._temp_edge.Normalize();
+ this.m_normal1.x = b2EPCollider._temp_edge.y;
+ this.m_normal1.y = -b2EPCollider._temp_edge.x;
+ var offset1 = this.m_normal1.x * (this.m_centroidB.x - this.m_v1.x) + this.m_normal1.y * (this.m_centroidB.y - this.m_v1.y);
+ var offset0 = 0,
+ offset2 = 0;
+ var convex1 = false,
+ convex2 = false;
+ if (hasVertex0) {
+ b2EPCollider._temp_edge0.x = this.m_v1.x - this.m_v0.x;
+ b2EPCollider._temp_edge0.y = this.m_v1.y - this.m_v0.y;
+ b2EPCollider._temp_edge0.Normalize();
+ this.m_normal0.x = b2EPCollider._temp_edge0.y;
+ this.m_normal0.y = -b2EPCollider._temp_edge0.x;
+ convex1 = (b2EPCollider._temp_edge0.x * b2EPCollider._temp_edge.y - b2EPCollider._temp_edge0.y * b2EPCollider._temp_edge.x) >= 0;
+ offset0 = this.m_normal0.x * (this.m_centroidB.x - this.m_v0.x) + this.m_normal0.y * (this.m_centroidB.y - this.m_v0.y)
+ }
+ if (hasVertex3) {
+ b2EPCollider._temp_edge2.x = this.m_v3.x - this.m_v2.x;
+ b2EPCollider._temp_edge2.y = this.m_v3.y - this.m_v2.y;
+ b2EPCollider._temp_edge2.Normalize();
+ this.m_normal2.x = b2EPCollider._temp_edge2.y;
+ this.m_normal2.y = -b2EPCollider._temp_edge2.x;
+ convex2 = (b2EPCollider._temp_edge.x * b2EPCollider._temp_edge2.y - b2EPCollider._temp_edge.y * b2EPCollider._temp_edge2.x) > 0;
+ offset2 = this.m_normal2.x * (this.m_centroidB.x - this.m_v2.x) + this.m_normal2.y * (this.m_centroidB.y - this.m_v2.y)
+ }
+ if (hasVertex0 && hasVertex3) {
+ if (convex1 && convex2) {
+ this.m_front = offset0 >= 0 || offset1 >= 0 || offset2 >= 0;
+ if (this.m_front) {
+ this.m_normal.x = this.m_normal1.x;
+ this.m_normal.y = this.m_normal1.y;
+ this.m_lowerLimit.x = this.m_normal0.x;
+ this.m_lowerLimit.y = this.m_normal0.y;
+ this.m_upperLimit.x = this.m_normal2.x;
+ this.m_upperLimit.y = this.m_normal2.y
+ } else {
+ this.m_normal.x = -this.m_normal1.x;
+ this.m_normal.y = -this.m_normal1.y;
+ this.m_lowerLimit.x = -this.m_normal1.x;
+ this.m_lowerLimit.y = -this.m_normal1.y;
+ this.m_upperLimit.x = -this.m_normal1.x;
+ this.m_upperLimit.y = -this.m_normal1.y
+ }
+ } else {
+ if (convex1) {
+ this.m_front = offset0 >= 0 || (offset1 >= 0 && offset2 >= 0);
+ if (this.m_front) {
+ this.m_normal.x = this.m_normal1.x;
+ this.m_normal.y = this.m_normal1.y;
+ this.m_lowerLimit.x = this.m_normal0.x;
+ this.m_lowerLimit.y = this.m_normal0.y;
+ this.m_upperLimit.x = this.m_normal1.x;
+ this.m_upperLimit.y = this.m_normal1.y
+ } else {
+ this.m_normal.x = -this.m_normal1.x;
+ this.m_normal.y = -this.m_normal1.y;
+ this.m_lowerLimit.x = -this.m_normal2.x;
+ this.m_lowerLimit.y = -this.m_normal2.y;
+ this.m_upperLimit.x = -this.m_normal1.x;
+ this.m_upperLimit.y = -this.m_normal1.y
+ }
+ } else {
+ if (convex2) {
+ this.m_front = offset2 >= 0 || (offset0 >= 0 && offset1 >= 0);
+ if (this.m_front) {
+ this.m_normal.x = this.m_normal1.x;
+ this.m_normal.y = this.m_normal1.y;
+ this.m_lowerLimit.x = this.m_normal1.x;
+ this.m_lowerLimit.y = this.m_normal1.y;
+ this.m_upperLimit.x = this.m_normal2.x;
+ this.m_upperLimit.y = this.m_normal2.y
+ } else {
+ this.m_normal.x = -this.m_normal1.x;
+ this.m_normal.y = -this.m_normal1.y;
+ this.m_lowerLimit.x = -this.m_normal1.x;
+ this.m_lowerLimit.y = -this.m_normal1.y;
+ this.m_upperLimit.x = -this.m_normal0.x;
+ this.m_upperLimit.y = -this.m_normal0.y
+ }
+ } else {
+ this.m_front = offset0 >= 0 && offset1 >= 0 && offset2 >= 0;
+ if (this.m_front) {
+ this.m_normal.x = this.m_normal1.x;
+ this.m_normal.y = this.m_normal1.y;
+ this.m_lowerLimit.x = this.m_normal1.x;
+ this.m_lowerLimit.y = this.m_normal1.y;
+ this.m_upperLimit.x = this.m_normal1.x;
+ this.m_upperLimit.y = this.m_normal1.y
+ } else {
+ this.m_normal.x = -this.m_normal1.x;
+ this.m_normal.y = -this.m_normal1.y;
+ this.m_lowerLimit.x = -this.m_normal2.x;
+ this.m_lowerLimit.y = -this.m_normal2.y;
+ this.m_upperLimit.x = -this.m_normal0.x;
+ this.m_upperLimit.y = -this.m_normal0.y
+ }
+ }
+ }
+ }
+ } else {
+ if (hasVertex0) {
+ if (convex1) {
+ this.m_front = offset0 >= 0 || offset1 >= 0;
+ if (this.m_front) {
+ this.m_normal.x = this.m_normal1.x;
+ this.m_normal.y = this.m_normal1.y;
+ this.m_lowerLimit.x = this.m_normal0.x;
+ this.m_lowerLimit.y = this.m_normal0.y;
+ this.m_upperLimit.x = -this.m_normal1.x;
+ this.m_upperLimit.y = -this.m_normal1.y
+ } else {
+ this.m_normal.x = -this.m_normal1.x;
+ this.m_normal.y = -this.m_normal1.y;
+ this.m_lowerLimit.x = this.m_normal1.x;
+ this.m_lowerLimit.y = this.m_normal1.y;
+ this.m_upperLimit.x = -this.m_normal1.x;
+ this.m_upperLimit.y = -this.m_normal1.y
+ }
+ } else {
+ this.m_front = offset0 >= 0 && offset1 >= 0;
+ if (this.m_front) {
+ this.m_normal.x = this.m_normal1.x;
+ this.m_normal.y = this.m_normal1.y;
+ this.m_lowerLimit.x = this.m_normal1.x;
+ this.m_lowerLimit.y = this.m_normal1.y;
+ this.m_upperLimit.x = -this.m_normal1.x;
+ this.m_upperLimit.y = -this.m_normal1.y
+ } else {
+ this.m_normal.x = -this.m_normal1.x;
+ this.m_normal.y = -this.m_normal1.y;
+ this.m_lowerLimit.x = this.m_normal1.x;
+ this.m_lowerLimit.y = this.m_normal1.y;
+ this.m_upperLimit.x = -this.m_normal0.x;
+ this.m_upperLimit.y = -this.m_normal0.y
+ }
+ }
+ } else {
+ if (hasVertex3) {
+ if (convex2) {
+ this.m_front = offset1 >= 0 || offset2 >= 0;
+ if (this.m_front) {
+ this.m_normal.x = this.m_normal1.x;
+ this.m_normal.y = this.m_normal1.y;
+ this.m_lowerLimit.x = -this.m_normal1.x;
+ this.m_lowerLimit.y = -this.m_normal1.y;
+ this.m_upperLimit.x = this.m_normal2.x;
+ this.m_upperLimit.y = this.m_normal2.y
+ } else {
+ this.m_normal.x = -this.m_normal1.x;
+ this.m_normal.y = -this.m_normal1.y;
+ this.m_lowerLimit.x = -this.m_normal1.x;
+ this.m_lowerLimit.y = -this.m_normal1.y;
+ this.m_upperLimit.x = this.m_normal1.x;
+ this.m_upperLimit.y = this.m_normal1.y
+ }
+ } else {
+ this.m_front = offset1 >= 0 && offset2 >= 0;
+ if (this.m_front) {
+ this.m_normal.x = this.m_normal1.x;
+ this.m_normal.y = this.m_normal1.y;
+ this.m_lowerLimit.x = -this.m_normal1.x;
+ this.m_lowerLimit.y = -this.m_normal1.y;
+ this.m_upperLimit.x = this.m_normal1.x;
+ this.m_upperLimit.y = this.m_normal1.y
+ } else {
+ this.m_normal.x = -this.m_normal1.x;
+ this.m_normal.y = -this.m_normal1.y;
+ this.m_lowerLimit.x = -this.m_normal2.x;
+ this.m_lowerLimit.y = -this.m_normal2.y;
+ this.m_upperLimit.x = this.m_normal1.x;
+ this.m_upperLimit.y = this.m_normal1.y
+ }
+ }
+ } else {
+ this.m_front = offset1 >= 0;
+ if (this.m_front) {
+ this.m_normal.x = this.m_normal1.x;
+ this.m_normal.y = this.m_normal1.y;
+ this.m_lowerLimit.x = -this.m_normal1.x;
+ this.m_lowerLimit.y = -this.m_normal1.y;
+ this.m_upperLimit.x = -this.m_normal1.x;
+ this.m_upperLimit.y = -this.m_normal1.y
+ } else {
+ this.m_normal.x = -this.m_normal1.x;
+ this.m_normal.y = -this.m_normal1.y;
+ this.m_lowerLimit.x = this.m_normal1.x;
+ this.m_lowerLimit.y = this.m_normal1.y;
+ this.m_upperLimit.x = this.m_normal1.x;
+ this.m_upperLimit.y = this.m_normal1.y
+ }
+ }
+ }
+ }
+ this.m_polygonB.count = polygonB.m_count;
+ for (var i = 0; i < polygonB.m_count; ++i) {
+ this.m_polygonB.vertices[i] = b2Mul_t_v2(this.m_xf, polygonB.m_vertices[i]);
+ this.m_polygonB.normals[i] = b2Mul_r_v2(this.m_xf.q, polygonB.m_normals[i])
+ }
+ this.m_radius = 2 * b2_polygonRadius;
+ manifold.pointCount = 0;
+ var edgeAxis = this.ComputeEdgeSeparation();
+ if (edgeAxis.type == b2EPAxis.e_unknown) {
+ return
+ }
+ if (edgeAxis.separation > this.m_radius) {
+ return
+ }
+ var polygonAxis = this.ComputePolygonSeparation();
+ if (polygonAxis.type != b2EPAxis.e_unknown && polygonAxis.separation > this.m_radius) {
+ return
+ }
+ var k_relativeTol = 0.98;
+ var k_absoluteTol = 0.001;
+ var primaryAxis = new b2EPAxis();
+ if (polygonAxis.type == b2EPAxis.e_unknown) {
+ primaryAxis = edgeAxis
+ } else {
+ if (polygonAxis.separation > k_relativeTol * edgeAxis.separation + k_absoluteTol) {
+ primaryAxis = polygonAxis
+ } else {
+ primaryAxis = edgeAxis
+ }
+ }
+ var ie = new Array(2);
+ var rf = new b2ReferenceFace();
+ if (primaryAxis.type == b2EPAxis.e_edgeA) {
+ manifold.type = b2Manifold.e_faceA;
+ var bestIndex = 0;
+ var bestValue = this.m_normal.x * this.m_polygonB.normals[0].x + this.m_normal.y * this.m_polygonB.normals[0].y;
+ for (var i = 1; i < this.m_polygonB.count; ++i) {
+ var value = this.m_normal.x * this.m_polygonB.normals[i].x + this.m_normal.y * this.m_polygonB.normals[i].y;
+ if (value < bestValue) {
+ bestValue = value;
+ bestIndex = i
+ }
+ }
+ var i1 = bestIndex;
+ var i2 = i1 + 1 < this.m_polygonB.count ? i1 + 1 : 0;
+ ie[0] = new b2ClipVertex();
+ ie[0].v.x = this.m_polygonB.vertices[i1].x;
+ ie[0].v.y = this.m_polygonB.vertices[i1].y;
+ ie[0].id.indexA = 0;
+ ie[0].id.indexB = i1;
+ ie[0].id.typeA = b2ContactID.e_face;
+ ie[0].id.typeB = b2ContactID.e_vertex;
+ ie[1] = new b2ClipVertex();
+ ie[1].v.x = this.m_polygonB.vertices[i2].x;
+ ie[1].v.y = this.m_polygonB.vertices[i2].y;
+ ie[1].id.indexA = 0;
+ ie[1].id.indexB = i2;
+ ie[1].id.typeA = b2ContactID.e_face;
+ ie[1].id.typeB = b2ContactID.e_vertex;
+ if (this.m_front) {
+ rf.i1 = 0;
+ rf.i2 = 1;
+ rf.v1.x = this.m_v1.x;
+ rf.v1.y = this.m_v1.y;
+ rf.v2.x = this.m_v2.x;
+ rf.v2.y = this.m_v2.y;
+ rf.normal.x = this.m_normal1.x;
+ rf.normal.y = this.m_normal1.y
+ } else {
+ rf.i1 = 1;
+ rf.i2 = 0;
+ rf.v1.x = this.m_v2.x;
+ rf.v1.y = this.m_v2.y;
+ rf.v2.x = this.m_v1.x;
+ rf.v2.y = this.m_v1.y;
+ rf.normal.x = -this.m_normal1.x;
+ rf.normal.y = -this.m_normal1.y
+ }
+ } else {
+ manifold.type = b2Manifold.e_faceB;
+ ie[0] = new b2ClipVertex();
+ ie[0].v = this.m_v1;
+ ie[0].id.indexA = 0;
+ ie[0].id.indexB = primaryAxis.index;
+ ie[0].id.typeA = b2ContactID.e_vertex;
+ ie[0].id.typeB = b2ContactID.e_face;
+ ie[1] = new b2ClipVertex();
+ ie[1].v = this.m_v2;
+ ie[1].id.indexA = 0;
+ ie[1].id.indexB = primaryAxis.index;
+ ie[1].id.typeA = b2ContactID.e_vertex;
+ ie[1].id.typeB = b2ContactID.e_face;
+ rf.i1 = primaryAxis.index;
+ rf.i2 = rf.i1 + 1 < this.m_polygonB.count ? rf.i1 + 1 : 0;
+ rf.v1.x = this.m_polygonB.vertices[rf.i1].x;
+ rf.v1.y = this.m_polygonB.vertices[rf.i1].y;
+ rf.v2.x = this.m_polygonB.vertices[rf.i2].x;
+ rf.v2.y = this.m_polygonB.vertices[rf.i2].y;
+ rf.normal.x = this.m_polygonB.normals[rf.i1].x;
+ rf.normal.y = this.m_polygonB.normals[rf.i1].y
+ }
+ rf.sideNormal1.x = rf.normal.y;
+ rf.sideNormal1.y = -rf.normal.x;
+ rf.sideNormal2.x = -rf.sideNormal1.x;
+ rf.sideNormal2.y = -rf.sideNormal1.y;
+ rf.sideOffset1 = rf.sideNormal1.x * rf.v1.x + rf.sideNormal1.y * rf.v1.y;
+ rf.sideOffset2 = rf.sideNormal2.x * rf.v2.x + rf.sideNormal2.y * rf.v2.y;
+ var clipPoints1 = new Array(2);
+ var clipPoints2 = new Array(2);
+ var np;
+ np = b2ClipSegmentToLine(clipPoints1, ie, rf.sideNormal1.x, rf.sideNormal1.y, rf.sideOffset1, rf.i1);
+ if (np < b2_maxManifoldPoints) {
+ return
+ }
+ np = b2ClipSegmentToLine(clipPoints2, clipPoints1, rf.sideNormal2.x, rf.sideNormal2.y, rf.sideOffset2, rf.i2);
+ if (np < b2_maxManifoldPoints) {
+ return
+ }
+ if (primaryAxis.type == b2EPAxis.e_edgeA) {
+ manifold.localNormal.x = rf.normal.x;
+ manifold.localNormal.y = rf.normal.y;
+ manifold.localPoint.x = rf.v1.x;
+ manifold.localPoint.y = rf.v1.y
+ } else {
+ manifold.localNormal.x = polygonB.m_normals[rf.i1].x;
+ manifold.localNormal.y = polygonB.m_normals[rf.i1].y;
+ manifold.localPoint.x = polygonB.m_vertices[rf.i1].x;
+ manifold.localPoint.y = polygonB.m_vertices[rf.i1].y
+ }
+ var pointCount = 0;
+ for (var i = 0; i < b2_maxManifoldPoints; ++i) {
+ var separation = rf.normal.x * (clipPoints2[i].v.x - rf.v1.x) + rf.normal.y * (clipPoints2[i].v.y - rf.v1.y);
+ if (separation <= this.m_radius) {
+ var cp = manifold.points[pointCount] = new b2ManifoldPoint();
+ if (primaryAxis.type == b2EPAxis.e_edgeA) {
+ cp.localPoint.Assign(b2MulT_t_v2(this.m_xf, clipPoints2[i].v));
+ cp.id.Assign(clipPoints2[i].id)
+ } else {
+ cp.localPoint.x = clipPoints2[i].v.x;
+ cp.localPoint.y = clipPoints2[i].v.y;
+ cp.id.typeA = clipPoints2[i].id.typeB;
+ cp.id.typeB = clipPoints2[i].id.typeA;
+ cp.id.indexA = clipPoints2[i].id.indexB;
+ cp.id.indexB = clipPoints2[i].id.indexA
+ }++pointCount
+ }
+ }
+ manifold.pointCount = pointCount
+ },
+ ComputeEdgeSeparation: function () {
+ var axis = new b2EPAxis();
+ axis.type = b2EPAxis.e_edgeA;
+ axis.index = this.m_front ? 0 : 1;
+ axis.separation = Number.MAX_VALUE;
+ for (var i = 0; i < this.m_polygonB.count; ++i) {
+ var s = this.m_normal.x * (this.m_polygonB.vertices[i].x - this.m_v1.x) + this.m_normal.y * (this.m_polygonB.vertices[i].y - this.m_v1.y);
+ if (s < axis.separation) {
+ axis.separation = s
+ }
+ }
+ return axis
+ },
+ ComputePolygonSeparation: function () {
+ var axis = new b2EPAxis();
+ axis.type = b2EPAxis.e_unknown;
+ axis.index = -1;
+ axis.separation = -Number.MAX_VALUE;
+ var perpx = -this.m_normal.y;
+ var perpy = this.m_normal.x;
+ for (var i = 0; i < this.m_polygonB.count; ++i) {
+ var nx = -this.m_polygonB.normals[i].x;
+ var ny = -this.m_polygonB.normals[i].y;
+ var s1 = nx * (this.m_polygonB.vertices[i].x - this.m_v1.x) + ny * (this.m_polygonB.vertices[i].y - this.m_v1.y);
+ var s2 = nx * (this.m_polygonB.vertices[i].x - this.m_v2.x) + ny * (this.m_polygonB.vertices[i].y - this.m_v2.y);
+ var s = b2Min(s1, s2);
+ if (s > this.m_radius) {
+ axis.type = b2EPAxis.e_edgeB;
+ axis.index = i;
+ axis.separation = s;
+ return axis
+ }
+ if (nx * perpx + ny * perpy >= 0) {
+ if ((nx - this.m_upperLimit.x) * this.m_normal.x + (ny - this.m_upperLimit.y) * this.m_normal.y < -b2_angularSlop) {
+ continue
+ }
+ } else {
+ if ((nx - this.m_lowerLimit.x) * this.m_normal.x + (ny - this.m_lowerLimit.y) * this.m_normal.y < -b2_angularSlop) {
+ continue
+ }
+ }
+ if (s > axis.separation) {
+ axis.type = b2EPAxis.e_edgeB;
+ axis.index = i;
+ axis.separation = s
+ }
+ }
+ return axis
+ }
+};
+b2EPCollider.e_isolated = 0;
+b2EPCollider.e_concave = 1;
+b2EPCollider.e_convex = 2;
+
+function b2CollideEdgeAndPolygon(manifold, edgeA, xfA, polygonB, xfB) {
+ b2CollideEdgeAndPolygon.collider.Collide(manifold, edgeA, xfA, polygonB, xfB)
+}
+b2CollideEdgeAndPolygon.collider = new b2EPCollider();
+
+function b2ClipSegmentToLine(vOut, vIn, normalx, normaly, offset, vertexIndexA) {
+ var numOut = 0;
+ var distance0 = (normalx * vIn[0].v.x + normaly * vIn[0].v.y) - offset;
+ var distance1 = (normalx * vIn[1].v.x + normaly * vIn[1].v.y) - offset;
+ if (distance0 <= 0) {
+ vOut[numOut++] = vIn[0]
+ }
+ if (distance1 <= 0) {
+ vOut[numOut++] = vIn[1]
+ }
+ if (distance0 * distance1 < 0) {
+ var interp = distance0 / (distance0 - distance1);
+ vOut[numOut] = new b2ClipVertex();
+ vOut[numOut].v.x = vIn[0].v.x + (interp * (vIn[1].v.x - vIn[0].v.x));
+ vOut[numOut].v.y = vIn[0].v.y + (interp * (vIn[1].v.y - vIn[0].v.y));
+ vOut[numOut].id.indexA = vertexIndexA;
+ vOut[numOut].id.indexB = vIn[0].id.indexB;
+ vOut[numOut].id.typeA = b2ContactID.e_vertex;
+ vOut[numOut].id.typeB = b2ContactID.e_face;
+ ++numOut
+ }
+ return numOut
+}
+
+function b2TestShapeOverlap(shapeA, indexA, shapeB, indexB, xfA, xfB) {
+ b2TestShapeOverlap.input.proxyA.Set(shapeA, indexA);
+ b2TestShapeOverlap.input.proxyB.Set(shapeB, indexB);
+ b2TestShapeOverlap.input.transformA = xfA;
+ b2TestShapeOverlap.input.transformB = xfB;
+ b2TestShapeOverlap.input.useRadii = true;
+ b2TestShapeOverlap.cache.count = 0;
+ b2DistanceFunc(b2TestShapeOverlap.output, b2TestShapeOverlap.cache, b2TestShapeOverlap.input);
+ return b2TestShapeOverlap.output.distance < 10 * b2_epsilon
+}
+b2TestShapeOverlap.input = new b2DistanceInput();
+b2TestShapeOverlap.cache = new b2SimplexCache();
+b2TestShapeOverlap.output = new b2DistanceOutput();
+
+function b2TestOverlap(a, b) {
+ return !((b.lowerBound.x - a.upperBound.x) > 0 || (b.lowerBound.y - a.upperBound.y) > 0 || (a.lowerBound.x - b.upperBound.x) > 0 || (a.lowerBound.y - b.upperBound.y) > 0)
+}
+"use strict";
+var b2_nullNode = -1;
+
+function b2TreeNode() {
+ this.aabb = new b2AABB();
+ this.userData = null;
+ this.parent = 0;
+ this.child1 = this.child2 = this.height = 0
+}
+b2TreeNode.prototype = {
+ IsLeaf: function () {
+ return this.child1 == b2_nullNode
+ }
+};
+
+function b2DynamicTree() {
+ this.m_root = b2_nullNode;
+ this.m_nodeCapacity = 16;
+ this.m_nodeCount = 0;
+ this.m_nodes = new Array(this.m_nodeCapacity);
+ for (var i = 0; i < this.m_nodeCapacity - 1; ++i) {
+ this.m_nodes[i] = new b2TreeNode();
+ this.m_nodes[i].parent = i + 1;
+ this.m_nodes[i].height = -1
+ }
+ this.m_nodes[this.m_nodeCapacity - 1] = new b2TreeNode();
+ this.m_nodes[this.m_nodeCapacity - 1].parent = b2_nullNode;
+ this.m_nodes[this.m_nodeCapacity - 1].height = -1;
+ this.m_freeList = 0;
+ this.m_path = 0;
+ this.m_insertionCount = 0
+}
+b2DynamicTree.aabbExtensionFattener = new b2Vec2(b2_aabbExtension, b2_aabbExtension);
+b2DynamicTree.prototype = {
+ CreateProxy: function (aabb, userData) {
+ var proxyId = this.AllocateNode();
+ this.m_nodes[proxyId].aabb.lowerBound.Assign(b2Vec2.Subtract(aabb.lowerBound, b2DynamicTree.aabbExtensionFattener));
+ this.m_nodes[proxyId].aabb.upperBound.Assign(b2Vec2.Add(aabb.upperBound, b2DynamicTree.aabbExtensionFattener));
+ this.m_nodes[proxyId].userData = userData;
+ this.m_nodes[proxyId].height = 0;
+ this.InsertLeaf(proxyId);
+ return proxyId
+ },
+ DestroyProxy: function (proxyId) {
+ this.RemoveLeaf(proxyId);
+ this.FreeNode(proxyId)
+ },
+ MoveProxy: function (proxyId, aabb, displacement) {
+ if (this.m_nodes[proxyId].aabb.Contains(aabb)) {
+ return false
+ }
+ this.RemoveLeaf(proxyId);
+ this.m_nodes[proxyId].aabb.Assign(aabb);
+ this.m_nodes[proxyId].aabb.lowerBound.Subtract(b2DynamicTree.aabbExtensionFattener);
+ this.m_nodes[proxyId].aabb.upperBound.Add(b2DynamicTree.aabbExtensionFattener);
+ var d = b2Vec2.Multiply(b2_aabbMultiplier, displacement);
+ if (d.x < 0) {
+ this.m_nodes[proxyId].aabb.lowerBound.x += d.x
+ } else {
+ this.m_nodes[proxyId].aabb.upperBound.x += d.x
+ }
+ if (d.y < 0) {
+ this.m_nodes[proxyId].aabb.lowerBound.y += d.y
+ } else {
+ this.m_nodes[proxyId].aabb.upperBound.y += d.y
+ }
+ this.InsertLeaf(proxyId);
+ return true
+ },
+ GetUserData: function (proxyId) {
+ return this.m_nodes[proxyId].userData
+ },
+ GetFatAABB: function (proxyId) {
+ return this.m_nodes[proxyId].aabb
+ },
+ Query: function (callback, aabb) {
+ var stack = [];
+ stack.push(this.m_root);
+ while (stack.length > 0) {
+ var nodeId = stack.pop();
+ if (nodeId == b2_nullNode) {
+ continue
+ }
+ var node = this.m_nodes[nodeId];
+ if (b2TestOverlap(node.aabb, aabb)) {
+ if (node.IsLeaf()) {
+ var proceed = callback.QueryCallback(nodeId);
+ if (proceed == false) {
+ return
+ }
+ } else {
+ stack.push(node.child1);
+ stack.push(node.child2)
+ }
+ }
+ }
+ },
+ RayCast: function (callback, input) {
+ var p1 = input.p1;
+ var p2 = input.p2;
+ var r = b2Vec2.Subtract(p2, p1);
+ r.Normalize();
+ var v = b2Cross_f_v2(1, r);
+ var abs_v = b2Abs_v2(v);
+ var maxFraction = input.maxFraction;
+ var segmentAABB = new b2AABB();
+ var t = b2Vec2.Add(p1, b2Vec2.Multiply(maxFraction, b2Vec2.Subtract(p2, p1)));
+ segmentAABB.lowerBound.Assign(b2Min_v2(p1, t));
+ segmentAABB.upperBound.Assign(b2Max_v2(p1, t));
+ var stack = [];
+ stack.push(this.m_root);
+ while (stack.length > 0) {
+ var nodeId = stack.pop();
+ if (nodeId == b2_nullNode) {
+ continue
+ }
+ var node = this.m_nodes[nodeId];
+ if (b2TestOverlap(node.aabb, segmentAABB) == false) {
+ continue
+ }
+ var c = node.aabb.GetCenter();
+ var h = node.aabb.GetExtents();
+ var separation = b2Abs(b2Dot_v2_v2(v, b2Vec2.Subtract(p1, c))) - b2Dot_v2_v2(abs_v, h);
+ if (separation > 0) {
+ continue
+ }
+ if (node.IsLeaf()) {
+ var subInput = new b2RayCastInput();
+ subInput.p1.Assign(input.p1);
+ subInput.p2.Assign(input.p2);
+ subInput.maxFraction = maxFraction;
+ var value = callback.RayCastCallback(subInput, nodeId);
+ if (value == 0) {
+ return
+ }
+ if (value > 0) {
+ maxFraction = value;
+ var t = b2Vec2.Add(p1, b2Vec2.Multiply(maxFraction, b2Vec2.Subtract(p2, p1)));
+ segmentAABB.lowerBound.Assign(b2Min_v2(p1, t));
+ segmentAABB.upperBound.Assign(b2Max_v2(p1, t))
+ }
+ } else {
+ stack.push(node.child1);
+ stack.push(node.child2)
+ }
+ }
+ },
+ Validate: function () {
+ this.ValidateStructure(this.m_root);
+ this.ValidateMetrics(this.m_root);
+ var freeCount = 0;
+ var freeIndex = this.m_freeList;
+ while (freeIndex != b2_nullNode) {
+ freeIndex = this.m_nodes[freeIndex].parent;
+ ++freeCount
+ }
+ },
+ GetHeight: function () {
+ if (this.m_root == b2_nullNode) {
+ return 0
+ }
+ return this.m_nodes[this.m_root].height
+ },
+ GetMaxBalance: function () {
+ var maxBalance = 0;
+ for (var i = 0; i < this.m_nodeCapacity; ++i) {
+ var node = this.m_nodes[i];
+ if (node.height <= 1) {
+ continue
+ }
+ var child1 = node.child1;
+ var child2 = node.child2;
+ var balance = b2Abs(this.m_nodes[child2].height - this.m_nodes[child1].height);
+ maxBalance = b2Max(maxBalance, balance)
+ }
+ return maxBalance
+ },
+ GetAreaRatio: function () {
+ if (this.m_root == b2_nullNode) {
+ return 0
+ }
+ var root = this.m_nodes[this.m_root];
+ var rootArea = root.aabb.GetPerimeter();
+ var totalArea = 0;
+ for (var i = 0; i < this.m_nodeCapacity; ++i) {
+ var node = this.m_nodes[i];
+ if (node.height < 0) {
+ continue
+ }
+ totalArea += node.aabb.GetPerimeter()
+ }
+ return totalArea / rootArea
+ },
+ RebuildBottomUp: function () {
+ var nodes = new Array(this.m_nodeCount);
+ var count = 0;
+ for (var i = 0; i < this.m_nodeCapacity; ++i) {
+ if (this.m_nodes[i].height < 0) {
+ continue
+ }
+ if (this.m_nodes[i].IsLeaf()) {
+ this.m_nodes[i].parent = b2_nullNode;
+ nodes[count] = i;
+ ++count
+ } else {
+ this.FreeNode(i)
+ }
+ }
+ while (count > 1) {
+ var minCost = b2_maxFloat;
+ var iMin = -1,
+ jMin = -1;
+ for (i = 0; i < count; ++i) {
+ var aabbi = this.m_nodes[nodes[i]].aabb;
+ for (var j = i + 1; j < count; ++j) {
+ var aabbj = this.m_nodes[nodes[j]].aabb;
+ var b = new b2AABB();
+ b.Combine(aabbi, aabbj);
+ var cost = b.GetPerimeter();
+ if (cost < minCost) {
+ iMin = i;
+ jMin = j;
+ minCost = cost
+ }
+ }
+ }
+ var index1 = nodes[iMin];
+ var index2 = nodes[jMin];
+ var child1 = this.m_nodes[index1];
+ var child2 = this.m_nodes[index2];
+ var parentIndex = this.AllocateNode();
+ var parent = this.m_nodes[parentIndex];
+ parent.child1 = index1;
+ parent.child2 = index2;
+ parent.height = 1 + b2Max(child1.height, child2.height);
+ parent.aabb.Combine(child1.aabb, child2.aabb);
+ parent.parent = b2_nullNode;
+ child1.parent = parentIndex;
+ child2.parent = parentIndex;
+ nodes[jMin] = nodes[count - 1];
+ nodes[iMin] = parentIndex;
+ --count
+ }
+ this.m_root = nodes[0];
+ this.Validate()
+ },
+ ShiftOrigin: function (newOrigin) {
+ for (var i = 0; i < this.m_nodeCapacity; ++i) {
+ this.m_nodes[i].aabb.lowerBound.Subtract(newOrigin);
+ this.m_nodes[i].aabb.upperBound.Subtract(newOrigin)
+ }
+ },
+ AllocateNode: function () {
+ if (this.m_freeList == b2_nullNode) {
+ var oldNodes = this.m_nodes;
+ this.m_nodeCapacity *= 2;
+ this.m_nodes = oldNodes.concat(new Array(this.m_nodeCapacity - this.m_nodeCount));
+ for (var i = this.m_nodeCount; i < this.m_nodeCapacity - 1; ++i) {
+ this.m_nodes[i] = new b2TreeNode();
+ this.m_nodes[i].parent = i + 1;
+ this.m_nodes[i].height = -1
+ }
+ this.m_nodes[this.m_nodeCapacity - 1] = new b2TreeNode();
+ this.m_nodes[this.m_nodeCapacity - 1].parent = b2_nullNode;
+ this.m_nodes[this.m_nodeCapacity - 1].height = -1;
+ this.m_freeList = this.m_nodeCount
+ }
+ var nodeId = this.m_freeList;
+ this.m_freeList = this.m_nodes[nodeId].parent;
+ this.m_nodes[nodeId].parent = b2_nullNode;
+ this.m_nodes[nodeId].child1 = b2_nullNode;
+ this.m_nodes[nodeId].child2 = b2_nullNode;
+ this.m_nodes[nodeId].height = 0;
+ this.m_nodes[nodeId].userData = null;
+ ++this.m_nodeCount;
+ return nodeId
+ },
+ FreeNode: function (nodeId) {
+ this.m_nodes[nodeId].parent = this.m_freeList;
+ this.m_nodes[nodeId].height = -1;
+ this.m_freeList = nodeId;
+ --this.m_nodeCount
+ },
+ InsertLeaf: function (leaf) {
+ ++this.m_insertionCount;
+ if (this.m_root == b2_nullNode) {
+ this.m_root = leaf;
+ this.m_nodes[this.m_root].parent = b2_nullNode;
+ return
+ }
+ var leafAABB = this.m_nodes[leaf].aabb;
+ var index = this.m_root;
+ while (this.m_nodes[index].IsLeaf() == false) {
+ var child1 = this.m_nodes[index].child1;
+ var child2 = this.m_nodes[index].child2;
+ var area = this.m_nodes[index].aabb.GetPerimeter();
+ var combinedAABB = new b2AABB();
+ combinedAABB.Combine(this.m_nodes[index].aabb, leafAABB);
+ var combinedArea = combinedAABB.GetPerimeter();
+ var cost = 2 * combinedArea;
+ var inheritanceCost = 2 * (combinedArea - area);
+ var cost1;
+ var aabb;
+ if (this.m_nodes[child1].IsLeaf()) {
+ aabb = new b2AABB();
+ aabb.Combine(leafAABB, this.m_nodes[child1].aabb);
+ cost1 = aabb.GetPerimeter() + inheritanceCost
+ } else {
+ aabb = new b2AABB();
+ aabb.Combine(leafAABB, this.m_nodes[child1].aabb);
+ var oldArea = this.m_nodes[child1].aabb.GetPerimeter();
+ var newArea = aabb.GetPerimeter();
+ cost1 = (newArea - oldArea) + inheritanceCost
+ }
+ var cost2;
+ if (this.m_nodes[child2].IsLeaf()) {
+ aabb = new b2AABB();
+ aabb.Combine(leafAABB, this.m_nodes[child2].aabb);
+ cost2 = aabb.GetPerimeter() + inheritanceCost
+ } else {
+ aabb = new b2AABB();
+ aabb.Combine(leafAABB, this.m_nodes[child2].aabb);
+ var oldArea = this.m_nodes[child2].aabb.GetPerimeter();
+ var newArea = aabb.GetPerimeter();
+ cost2 = newArea - oldArea + inheritanceCost
+ }
+ if (cost < cost1 && cost < cost2) {
+ break
+ }
+ if (cost1 < cost2) {
+ index = child1
+ } else {
+ index = child2
+ }
+ }
+ var sibling = index;
+ var oldParent = this.m_nodes[sibling].parent;
+ var newParent = this.AllocateNode();
+ this.m_nodes[newParent].parent = oldParent;
+ this.m_nodes[newParent].userData = null;
+ this.m_nodes[newParent].aabb.Combine(leafAABB, this.m_nodes[sibling].aabb);
+ this.m_nodes[newParent].height = this.m_nodes[sibling].height + 1;
+ if (oldParent != b2_nullNode) {
+ if (this.m_nodes[oldParent].child1 == sibling) {
+ this.m_nodes[oldParent].child1 = newParent
+ } else {
+ this.m_nodes[oldParent].child2 = newParent
+ }
+ this.m_nodes[newParent].child1 = sibling;
+ this.m_nodes[newParent].child2 = leaf;
+ this.m_nodes[sibling].parent = newParent;
+ this.m_nodes[leaf].parent = newParent
+ } else {
+ this.m_nodes[newParent].child1 = sibling;
+ this.m_nodes[newParent].child2 = leaf;
+ this.m_nodes[sibling].parent = newParent;
+ this.m_nodes[leaf].parent = newParent;
+ this.m_root = newParent
+ }
+ index = this.m_nodes[leaf].parent;
+ while (index != b2_nullNode) {
+ index = this.Balance(index);
+ var child1 = this.m_nodes[index].child1;
+ var child2 = this.m_nodes[index].child2;
+ this.m_nodes[index].height = 1 + b2Max(this.m_nodes[child1].height, this.m_nodes[child2].height);
+ this.m_nodes[index].aabb.Combine(this.m_nodes[child1].aabb, this.m_nodes[child2].aabb);
+ index = this.m_nodes[index].parent
+ }
+ },
+ RemoveLeaf: function (leaf) {
+ if (leaf == this.m_root) {
+ this.m_root = b2_nullNode;
+ return
+ }
+ var parent = this.m_nodes[leaf].parent;
+ var grandParent = this.m_nodes[parent].parent;
+ var sibling;
+ if (this.m_nodes[parent].child1 == leaf) {
+ sibling = this.m_nodes[parent].child2
+ } else {
+ sibling = this.m_nodes[parent].child1
+ }
+ if (grandParent != b2_nullNode) {
+ if (this.m_nodes[grandParent].child1 == parent) {
+ this.m_nodes[grandParent].child1 = sibling
+ } else {
+ this.m_nodes[grandParent].child2 = sibling
+ }
+ this.m_nodes[sibling].parent = grandParent;
+ this.FreeNode(parent);
+ var index = grandParent;
+ while (index != b2_nullNode) {
+ index = this.Balance(index);
+ var child1 = this.m_nodes[index].child1;
+ var child2 = this.m_nodes[index].child2;
+ this.m_nodes[index].aabb.Combine(this.m_nodes[child1].aabb, this.m_nodes[child2].aabb);
+ this.m_nodes[index].height = 1 + b2Max(this.m_nodes[child1].height, this.m_nodes[child2].height);
+ index = this.m_nodes[index].parent
+ }
+ } else {
+ this.m_root = sibling;
+ this.m_nodes[sibling].parent = b2_nullNode;
+ this.FreeNode(parent)
+ }
+ },
+ Balance: function (iA) {
+ var A = this.m_nodes[iA];
+ if (A.IsLeaf() || A.height < 2) {
+ return iA
+ }
+ var iB = A.child1;
+ var iC = A.child2;
+ var B = this.m_nodes[iB];
+ var C = this.m_nodes[iC];
+ var balance = C.height - B.height;
+ if (balance > 1) {
+ var iF = C.child1;
+ var iG = C.child2;
+ var F = this.m_nodes[iF];
+ var G = this.m_nodes[iG];
+ C.child1 = iA;
+ C.parent = A.parent;
+ A.parent = iC;
+ if (C.parent != b2_nullNode) {
+ if (this.m_nodes[C.parent].child1 == iA) {
+ this.m_nodes[C.parent].child1 = iC
+ } else {
+ this.m_nodes[C.parent].child2 = iC
+ }
+ } else {
+ this.m_root = iC
+ }
+ if (F.height > G.height) {
+ C.child2 = iF;
+ A.child2 = iG;
+ G.parent = iA;
+ A.aabb.Combine(B.aabb, G.aabb);
+ C.aabb.Combine(A.aabb, F.aabb);
+ A.height = 1 + b2Max(B.height, G.height);
+ C.height = 1 + b2Max(A.height, F.height)
+ } else {
+ C.child2 = iG;
+ A.child2 = iF;
+ F.parent = iA;
+ A.aabb.Combine(B.aabb, F.aabb);
+ C.aabb.Combine(A.aabb, G.aabb);
+ A.height = 1 + b2Max(B.height, F.height);
+ C.height = 1 + b2Max(A.height, G.height)
+ }
+ return iC
+ }
+ if (balance < -1) {
+ var iD = B.child1;
+ var iE = B.child2;
+ var D = this.m_nodes[iD];
+ var E = this.m_nodes[iE];
+ B.child1 = iA;
+ B.parent = A.parent;
+ A.parent = iB;
+ if (B.parent != b2_nullNode) {
+ if (this.m_nodes[B.parent].child1 == iA) {
+ this.m_nodes[B.parent].child1 = iB
+ } else {
+ this.m_nodes[B.parent].child2 = iB
+ }
+ } else {
+ this.m_root = iB
+ }
+ if (D.height > E.height) {
+ B.child2 = iD;
+ A.child1 = iE;
+ E.parent = iA;
+ A.aabb.Combine(C.aabb, E.aabb);
+ B.aabb.Combine(A.aabb, D.aabb);
+ A.height = 1 + b2Max(C.height, E.height);
+ B.height = 1 + b2Max(A.height, D.height)
+ } else {
+ B.child2 = iE;
+ A.child1 = iD;
+ D.parent = iA;
+ A.aabb.Combine(C.aabb, D.aabb);
+ B.aabb.Combine(A.aabb, E.aabb);
+ A.height = 1 + b2Max(C.height, D.height);
+ B.height = 1 + b2Max(A.height, E.height)
+ }
+ return iB
+ }
+ return iA
+ },
+ ComputeHeight: function (nodeId) {
+ if (typeof (nodeId) === "undefined") {
+ nodeId = this.m_root
+ }
+ var node = this.m_nodes[nodeId];
+ if (node.IsLeaf()) {
+ return 0
+ }
+ var height1 = this.ComputeHeight(node.child1);
+ var height2 = this.ComputeHeight(node.child2);
+ return 1 + b2Max(height1, height2)
+ },
+ ValidateStructure: function (index) {
+ if (index == b2_nullNode) {
+ return
+ }
+ var node = this.m_nodes[index];
+ var child1 = node.child1;
+ var child2 = node.child2;
+ if (node.IsLeaf()) {
+ return
+ }
+ this.ValidateStructure(child1);
+ this.ValidateStructure(child2)
+ },
+ ValidateMetrics: function (index) {
+ if (index == b2_nullNode) {
+ return
+ }
+ var node = this.m_nodes[index];
+ var child1 = node.child1;
+ var child2 = node.child2;
+ if (node.IsLeaf()) {
+ return
+ }
+ var height1 = this.m_nodes[child1].height;
+ var height2 = this.m_nodes[child2].height;
+ var height;
+ height = 1 + b2Max(height1, height2);
+ var aabb = new b2AABB();
+ aabb.Combine(this.m_nodes[child1].aabb, this.m_nodes[child2].aabb);
+ this.ValidateMetrics(child1);
+ this.ValidateMetrics(child2)
+ }
+};
+"use strict";
+
+function b2TOIInput() {
+ this.proxyA = new b2DistanceProxy();
+ this.proxyB = new b2DistanceProxy();
+ this.sweepA = new b2Sweep();
+ this.sweepB = new b2Sweep();
+ this.tMax = 0
+}
+
+function b2TOIOutput() {
+ this.state = 0;
+ this.t = 0
+}
+b2TOIOutput.e_unknown = 0;
+b2TOIOutput.e_failed = 1;
+b2TOIOutput.e_overlapped = 2;
+b2TOIOutput.e_touching = 3;
+b2TOIOutput.e_separated = 4;
+
+function b2SeparationFunction() {
+ this.m_proxyA = null;
+ this.m_proxyB = null;
+ this.m_sweepA = null;
+ this.m_sweepB = null;
+ this.m_type = 0;
+ this.m_localPoint = new b2Vec2();
+ this.m_axis = new b2Vec2()
+}
+var _local_xfA = new b2Transform();
+var _local_xfB = new b2Transform();
+b2SeparationFunction.prototype = {
+ Initialize: function (cache, proxyA, sweepA, proxyB, sweepB, t1) {
+ this.m_proxyA = proxyA;
+ this.m_proxyB = proxyB;
+ var count = cache.count;
+ this.m_sweepA = sweepA;
+ this.m_sweepB = sweepB;
+ this.m_sweepA.GetTransform(_local_xfA, t1);
+ this.m_sweepB.GetTransform(_local_xfB, t1);
+ if (count == 1) {
+ this.m_type = b2SeparationFunction.e_points;
+ var localPointA = this.m_proxyA.GetVertex(cache.indexA[0]);
+ var localPointB = this.m_proxyB.GetVertex(cache.indexB[0]);
+ var pointAx = (_local_xfA.q.c * localPointA.x - _local_xfA.q.s * localPointA.y) + _local_xfA.p.x;
+ var pointAy = (_local_xfA.q.s * localPointA.x + _local_xfA.q.c * localPointA.y) + _local_xfA.p.y;
+ var pointBx = (_local_xfB.q.c * localPointB.x - _local_xfB.q.s * localPointB.y) + _local_xfB.p.x;
+ var pointBy = (_local_xfB.q.s * localPointB.x + _local_xfB.q.c * localPointB.y) + _local_xfB.p.y;
+ this.m_axis.x = pointBx - pointAx;
+ this.m_axis.y = pointBy - pointAy;
+ var s = this.m_axis.Normalize();
+ return s
+ } else {
+ if (cache.indexA[0] == cache.indexA[1]) {
+ this.m_type = b2SeparationFunction.e_faceB;
+ var localPointB1 = proxyB.GetVertex(cache.indexB[0]);
+ var localPointB2 = proxyB.GetVertex(cache.indexB[1]);
+ this.m_axis.x = 1 * (localPointB2.y - localPointB1.y);
+ this.m_axis.y = -1 * (localPointB2.x - localPointB1.x);
+ this.m_axis.Normalize();
+ var normalx = _local_xfB.q.c * this.m_axis.x - _local_xfB.q.s * this.m_axis.y;
+ var normaly = _local_xfB.q.s * this.m_axis.x + _local_xfB.q.c * this.m_axis.y;
+ this.m_localPoint.x = 0.5 * (localPointB1.x + localPointB2.x);
+ this.m_localPoint.y = 0.5 * (localPointB1.y + localPointB2.y);
+ var pointBx = (_local_xfB.q.c * this.m_localPoint.x - _local_xfB.q.s * this.m_localPoint.y) + _local_xfB.p.x;
+ var pointBy = (_local_xfB.q.s * this.m_localPoint.x + _local_xfB.q.c * this.m_localPoint.y) + _local_xfB.p.y;
+ var localPointA = proxyA.GetVertex(cache.indexA[0]);
+ var pointAx = (_local_xfA.q.c * localPointA.x - _local_xfA.q.s * localPointA.y) + _local_xfA.p.x;
+ var pointAy = (_local_xfA.q.s * localPointA.x + _local_xfA.q.c * localPointA.y) + _local_xfA.p.y;
+ var s = (pointAx - pointBx) * normalx + (pointAy - pointBy) * normaly;
+ if (s < 0) {
+ this.m_axis.x = -this.m_axis.x;
+ this.m_axis.y = -this.m_axis.y;
+ s = -s
+ }
+ return s
+ } else {
+ this.m_type = b2SeparationFunction.e_faceA;
+ var localPointA1 = this.m_proxyA.GetVertex(cache.indexA[0]);
+ var localPointA2 = this.m_proxyA.GetVertex(cache.indexA[1]);
+ this.m_axis.x = 1 * (localPointA2.y - localPointA1.y);
+ this.m_axis.y = -1 * (localPointA2.x - localPointA1.x);
+ this.m_axis.Normalize();
+ var normalx = _local_xfA.q.c * this.m_axis.x - _local_xfA.q.s * this.m_axis.y;
+ var normaly = _local_xfA.q.s * this.m_axis.x + _local_xfA.q.c * this.m_axis.y;
+ this.m_localPoint.x = 0.5 * (localPointA1.x + localPointA2.x);
+ this.m_localPoint.y = 0.5 * (localPointA1.y + localPointA2.y);
+ var pointAx = (_local_xfA.q.c * this.m_localPoint.x - _local_xfA.q.s * this.m_localPoint.y) + _local_xfA.p.x;
+ var pointAy = (_local_xfA.q.s * this.m_localPoint.x + _local_xfA.q.c * this.m_localPoint.y) + _local_xfA.p.y;
+ var localPointB = this.m_proxyB.GetVertex(cache.indexB[0]);
+ var pointBx = (_local_xfB.q.c * localPointB.x - _local_xfB.q.s * localPointB.y) + _local_xfB.p.x;
+ var pointBy = (_local_xfB.q.s * localPointB.x + _local_xfB.q.c * localPointB.y) + _local_xfB.p.y;
+ var s = (pointBx - pointAx) * normalx + (pointBy - pointAy) * normaly;
+ if (s < 0) {
+ this.m_axis.x = -this.m_axis.x;
+ this.m_axis.y = -this.m_axis.y;
+ s = -s
+ }
+ return s
+ }
+ }
+ },
+ FindMinSeparation: function (indices, t) {
+ this.m_sweepA.GetTransform(_local_xfA, t);
+ this.m_sweepB.GetTransform(_local_xfB, t);
+ switch (this.m_type) {
+ case b2SeparationFunction.e_points:
+ var axisAx = _local_xfA.q.c * this.m_axis.x + _local_xfA.q.s * this.m_axis.y;
+ var axisAy = -_local_xfA.q.s * this.m_axis.x + _local_xfA.q.c * this.m_axis.y;
+ var axisBx = _local_xfB.q.c * -this.m_axis.x + _local_xfB.q.s * -this.m_axis.y;
+ var axisBy = -_local_xfB.q.s * -this.m_axis.x + _local_xfB.q.c * -this.m_axis.y;
+ indices[0] = this.m_proxyA.GetSupport(axisAx, axisAy);
+ indices[1] = this.m_proxyB.GetSupport(axisBx, axisBy);
+ var localPointA = this.m_proxyA.GetVertex(indices[0]);
+ var localPointB = this.m_proxyB.GetVertex(indices[1]);
+ var pointAx = (_local_xfA.q.c * localPointA.x - _local_xfA.q.s * localPointA.y) + _local_xfA.p.x;
+ var pointAy = (_local_xfA.q.s * localPointA.x + _local_xfA.q.c * localPointA.y) + _local_xfA.p.y;
+ var pointBx = (_local_xfB.q.c * localPointB.x - _local_xfB.q.s * localPointB.y) + _local_xfB.p.x;
+ var pointBy = (_local_xfB.q.s * localPointB.x + _local_xfB.q.c * localPointB.y) + _local_xfB.p.y;
+ return (pointBx - pointAx) * this.m_axis.x + (pointBy - pointAy) * this.m_axis.y;
+ case b2SeparationFunction.e_faceA:
+ var normalx = _local_xfA.q.c * this.m_axis.x - _local_xfA.q.s * this.m_axis.y;
+ var normaly = _local_xfA.q.s * this.m_axis.x + _local_xfA.q.c * this.m_axis.y;
+ var pointAx = (_local_xfA.q.c * this.m_localPoint.x - _local_xfA.q.s * this.m_localPoint.y) + _local_xfA.p.x;
+ var pointAy = (_local_xfA.q.s * this.m_localPoint.x + _local_xfA.q.c * this.m_localPoint.y) + _local_xfA.p.y;
+ var axisBx = _local_xfB.q.c * -normalx + _local_xfB.q.s * -normaly;
+ var axisBy = -_local_xfB.q.s * -normalx + _local_xfB.q.c * -normaly;
+ indices[0] = -1;
+ indices[1] = this.m_proxyB.GetSupport(axisBx, axisBy);
+ var localPointB = this.m_proxyB.GetVertex(indices[1]);
+ var pointBx = (_local_xfB.q.c * localPointB.x - _local_xfB.q.s * localPointB.y) + _local_xfB.p.x;
+ var pointBy = (_local_xfB.q.s * localPointB.x + _local_xfB.q.c * localPointB.y) + _local_xfB.p.y;
+ return (pointBx - pointAx) * normalx + (pointBy - pointAy) * normaly;
+ case b2SeparationFunction.e_faceB:
+ var normalx = _local_xfB.q.c * this.m_axis.x - _local_xfB.q.s * this.m_axis.y;
+ var normaly = _local_xfB.q.s * this.m_axis.x + _local_xfB.q.c * this.m_axis.y;
+ var pointBx = (_local_xfB.q.c * this.m_localPoint.x - _local_xfB.q.s * this.m_localPoint.y) + _local_xfB.p.x;
+ var pointBy = (_local_xfB.q.s * this.m_localPoint.x + _local_xfB.q.c * this.m_localPoint.y) + _local_xfB.p.y;
+ var axisAx = _local_xfA.q.c * -normalx + _local_xfA.q.s * -normaly;
+ var axisBy = -_local_xfA.q.s * -normalx + _local_xfA.q.c * -normaly;
+ indices[1] = -1;
+ indices[0] = this.m_proxyA.GetSupport(axisAx, axisBy);
+ var localPointA = this.m_proxyA.GetVertex(indices[0]);
+ var pointAx = (_local_xfA.q.c * localPointA.x - _local_xfA.q.s * localPointA.y) + _local_xfA.p.x;
+ var pointAy = (_local_xfA.q.s * localPointA.x + _local_xfA.q.c * localPointA.y) + _local_xfA.p.y;
+ return (pointAx - pointBx) * normalx + (pointAy - pointBy) * normaly
+ }
+ },
+ Evaluate: function (indexA, indexB, t) {
+ this.m_sweepA.GetTransform(_local_xfA, t);
+ this.m_sweepB.GetTransform(_local_xfB, t);
+ switch (this.m_type) {
+ case b2SeparationFunction.e_points:
+ var localPointA = this.m_proxyA.GetVertex(indexA);
+ var localPointB = this.m_proxyB.GetVertex(indexB);
+ var pointAx = (_local_xfA.q.c * localPointA.x - _local_xfA.q.s * localPointA.y) + _local_xfA.p.x;
+ var pointAy = (_local_xfA.q.s * localPointA.x + _local_xfA.q.c * localPointA.y) + _local_xfA.p.y;
+ var pointBx = (_local_xfB.q.c * localPointB.x - _local_xfB.q.s * localPointB.y) + _local_xfB.p.x;
+ var pointBy = (_local_xfB.q.s * localPointB.x + _local_xfB.q.c * localPointB.y) + _local_xfB.p.y;
+ var separation = (pointBx - pointAx) * this.m_axis.x + (pointBy - pointAy) * this.m_axis.y;
+ return separation;
+ case b2SeparationFunction.e_faceA:
+ var normalx = _local_xfA.q.c * this.m_axis.x - _local_xfA.q.s * this.m_axis.y;
+ var normaly = _local_xfA.q.s * this.m_axis.x + _local_xfA.q.c * this.m_axis.y;
+ var pointAx = (_local_xfA.q.c * this.m_localPoint.x - _local_xfA.q.s * this.m_localPoint.y) + _local_xfA.p.x;
+ var pointAy = (_local_xfA.q.s * this.m_localPoint.x + _local_xfA.q.c * this.m_localPoint.y) + _local_xfA.p.y;
+ var localPointB = this.m_proxyB.GetVertex(indexB);
+ var pointBx = (_local_xfB.q.c * localPointB.x - _local_xfB.q.s * localPointB.y) + _local_xfB.p.x;
+ var pointBy = (_local_xfB.q.s * localPointB.x + _local_xfB.q.c * localPointB.y) + _local_xfB.p.y;
+ var separation = (pointBx - pointAx) * normalx + (pointBy - pointAy) * normaly;
+ return separation;
+ case b2SeparationFunction.e_faceB:
+ var normalx = _local_xfB.q.c * this.m_axis.x - _local_xfB.q.s * this.m_axis.y;
+ var normaly = _local_xfB.q.s * this.m_axis.x + _local_xfB.q.c * this.m_axis.y;
+ var pointBx = (_local_xfB.q.c * this.m_localPoint.x - _local_xfB.q.s * this.m_localPoint.y) + _local_xfB.p.x;
+ var pointBy = (_local_xfB.q.s * this.m_localPoint.x + _local_xfB.q.c * this.m_localPoint.y) + _local_xfB.p.y;
+ var localPointA = this.m_proxyA.GetVertex(indexA);
+ var pointAx = (_local_xfA.q.c * localPointA.x - _local_xfA.q.s * localPointA.y) + _local_xfA.p.x;
+ var pointAy = (_local_xfA.q.s * localPointA.x + _local_xfA.q.c * localPointA.y) + _local_xfA.p.y;
+ var separation = (pointAx - pointBx) * normalx + (pointAy - pointBy) * normaly;
+ return separation
+ }
+ }
+};
+b2SeparationFunction.e_points = 0;
+b2SeparationFunction.e_faceA = 1;
+b2SeparationFunction.e_faceB = 2;
+var profile_toi = b2Profiler.create("toi", "solveTOI");
+
+function b2TimeOfImpact(output, input) {
+ profile_toi.start();
+ ++b2TimeOfImpact.b2_toiCalls;
+ output.state = b2TOIOutput.e_unknown;
+ output.t = input.tMax;
+ var proxyA = input.proxyA;
+ var proxyB = input.proxyB;
+ b2TimeOfImpact._temp_sweepA.Assign(input.sweepA);
+ b2TimeOfImpact._temp_sweepB.Assign(input.sweepB);
+ b2TimeOfImpact._temp_sweepA.Normalize();
+ b2TimeOfImpact._temp_sweepB.Normalize();
+ var tMax = input.tMax;
+ var totalRadius = proxyA.m_radius + proxyB.m_radius;
+ var target = b2Max(b2_linearSlop, totalRadius - 3 * b2_linearSlop);
+ var tolerance = 0.25 * b2_linearSlop;
+ var t1 = 0;
+ var k_maxIterations = 20;
+ var iter = 0;
+ var cache = new b2SimplexCache();
+ cache.count = 0;
+ var distanceInput = new b2DistanceInput();
+ distanceInput.proxyA.Assign(input.proxyA);
+ distanceInput.proxyB.Assign(input.proxyB);
+ distanceInput.useRadii = false;
+ for (;;) {
+ b2TimeOfImpact._temp_sweepA.GetTransform(distanceInput.transformA, t1);
+ b2TimeOfImpact._temp_sweepB.GetTransform(distanceInput.transformB, t1);
+ var distanceOutput = new b2DistanceOutput();
+ b2DistanceFunc(distanceOutput, cache, distanceInput);
+ if (distanceOutput.distance <= 0) {
+ output.state = b2TOIOutput.e_overlapped;
+ output.t = 0;
+ break
+ }
+ if (distanceOutput.distance < target + tolerance) {
+ output.state = b2TOIOutput.e_touching;
+ output.t = t1;
+ break
+ }
+ var fcn = new b2SeparationFunction();
+ fcn.Initialize(cache, proxyA, b2TimeOfImpact._temp_sweepA, proxyB, b2TimeOfImpact._temp_sweepB, t1);
+ var done = false;
+ var t2 = tMax;
+ var pushBackIter = 0;
+ for (;;) {
+ var indices = [];
+ var s2 = fcn.FindMinSeparation(indices, t2);
+ if (s2 > target + tolerance) {
+ output.state = b2TOIOutput.e_separated;
+ output.t = tMax;
+ done = true;
+ break
+ }
+ if (s2 > target - tolerance) {
+ t1 = t2;
+ break
+ }
+ var s1 = fcn.Evaluate(indices[0], indices[1], t1);
+ if (s1 < target - tolerance) {
+ output.state = b2TOIOutput.e_failed;
+ output.t = t1;
+ done = true;
+ break
+ }
+ if (s1 <= target + tolerance) {
+ output.state = b2TOIOutput.e_touching;
+ output.t = t1;
+ done = true;
+ break
+ }
+ var rootIterCount = 0;
+ var a1 = t1,
+ a2 = t2;
+ for (;;) {
+ var t;
+ if (rootIterCount & 1) {
+ t = a1 + (target - s1) * (a2 - a1) / (s2 - s1)
+ } else {
+ t = 0.5 * (a1 + a2)
+ }++rootIterCount;
+ ++b2TimeOfImpact.b2_toiRootIters;
+ var s = fcn.Evaluate(indices[0], indices[1], t);
+ if (b2Abs(s - target) < tolerance) {
+ t2 = t;
+ break
+ }
+ if (s > target) {
+ a1 = t;
+ s1 = s
+ } else {
+ a2 = t;
+ s2 = s
+ }
+ if (rootIterCount == 50) {
+ break
+ }
+ }
+ b2TimeOfImpact.b2_toiMaxRootIters = b2Max(b2TimeOfImpact.b2_toiMaxRootIters, rootIterCount);
+ ++pushBackIter;
+ if (pushBackIter == b2_maxPolygonVertices) {
+ break
+ }
+ }++iter;
+ ++b2TimeOfImpact.b2_toiIters;
+ if (done) {
+ break
+ }
+ if (iter == k_maxIterations) {
+ output.state = b2TOIOutput.e_failed;
+ output.t = t1;
+ break
+ }
+ }
+ b2TimeOfImpact.b2_toiMaxIters = b2Max(b2TimeOfImpact.b2_toiMaxIters, iter);
+ profile_toi.stop();
+ b2TimeOfImpact.b2_toiMaxTime = b2Max(b2TimeOfImpact.b2_toiMaxTime, profile_toi.elapsedTime);
+ b2TimeOfImpact.b2_toiTime += profile_toi.elapsedTime
+}
+b2TimeOfImpact._temp_sweepA = new b2Sweep();
+b2TimeOfImpact._temp_sweepB = new b2Sweep();
+b2TimeOfImpact.b2_toiTime = 0;
+b2TimeOfImpact.b2_toiMaxTime = 0;
+b2TimeOfImpact.b2_toiCalls = 0;
+b2TimeOfImpact.b2_toiIters = 0;
+b2TimeOfImpact.b2_toiMaxIters = 0;
+b2TimeOfImpact.b2_toiRootIters = 0;
+b2TimeOfImpact.b2_toiMaxRootIters = 0;
+"use strict";
+
+function b2BodyDef() {
+ this.type = b2Body.b2_staticBody;
+ this.position = new b2Vec2(0, 0);
+ this.angle = 0;
+ this.linearVelocity = new b2Vec2(0, 0);
+ this.angularVelocity = 0;
+ this.linearDamping = 0;
+ this.angularDamping = 0;
+ this.allowSleep = true;
+ this.awake = true;
+ this.fixedRotation = false;
+ this.bullet = false;
+ this.active = true;
+ this.userData = null;
+ this.gravityScale = 1;
+ Object.seal(this)
+}
+b2BodyDef.prototype = {
+ _deserialize: function (data) {
+ this.type = data.type;
+ this.position._deserialize(data.position);
+ this.angle = data.angle;
+ this.linearVelocity._deserialize(data.linearVelocity);
+ this.angularVelocity = data.angularVelocity;
+ this.linearDamping = data.linearDamping;
+ this.angularDamping = data.angularDamping;
+ this.allowSleep = data.allowSleep;
+ this.awake = data.awake;
+ this.fixedRotation = data.fixedRotation;
+ this.bullet = data.bullet;
+ this.active = data.active;
+ this.gravityScale = data.gravityScale
+ }
+};
+
+function b2Body(bd, world) {
+ this.m_islandIndex = 0;
+ this.m_flags = 0;
+ if (bd.bullet) {
+ this.m_flags |= b2Body.e_bulletFlag
+ }
+ if (bd.fixedRotation) {
+ this.m_flags |= b2Body.e_fixedRotationFlag
+ }
+ if (bd.allowSleep) {
+ this.m_flags |= b2Body.e_autoSleepFlag
+ }
+ if (bd.awake) {
+ this.m_flags |= b2Body.e_awakeFlag
+ }
+ if (bd.active) {
+ this.m_flags |= b2Body.e_activeFlag
+ }
+ this.m_world = world;
+ this.m_xf = new b2Transform();
+ this.m_xf.p.Assign(bd.position);
+ this.m_xf.q.Set(bd.angle);
+ this.m_sweep = new b2Sweep();
+ this.m_sweep.localCenter.SetZero();
+ this.m_sweep.c0.Assign(this.m_xf.p);
+ this.m_sweep.c.Assign(this.m_xf.p);
+ this.m_sweep.a0 = bd.angle;
+ this.m_sweep.a = bd.angle;
+ this.m_sweep.alpha0 = 0;
+ this.m_jointList = null;
+ this.m_contactList = null;
+ this.m_prev = null;
+ this.m_next = null;
+ this.m_linearVelocity = bd.linearVelocity.Clone();
+ this.m_angularVelocity = bd.angularVelocity;
+ this.m_linearDamping = bd.linearDamping;
+ this.m_angularDamping = bd.angularDamping;
+ this.m_gravityScale = bd.gravityScale;
+ this.m_force = new b2Vec2();
+ this.m_torque = 0;
+ this.m_sleepTime = 0;
+ this.m_type = bd.type;
+ if (this.m_type == b2Body.b2_dynamicBody) {
+ this.m_mass = 1;
+ this.m_invMass = 1
+ } else {
+ this.m_mass = 0;
+ this.m_invMass = 0
+ }
+ this.m_I = 0;
+ this.m_invI = 0;
+ this.m_userData = bd.userData;
+ this.m_fixtureList = null;
+ this.m_fixtureCount = 0
+}
+b2Body.b2_staticBody = 0;
+b2Body.b2_kinematicBody = 1;
+b2Body.b2_dynamicBody = 2;
+b2Body.e_islandFlag = 1;
+b2Body.e_awakeFlag = 2;
+b2Body.e_autoSleepFlag = 4;
+b2Body.e_bulletFlag = 8;
+b2Body.e_fixedRotationFlag = 16;
+b2Body.e_activeFlag = 32;
+b2Body.e_toiFlag = 64;
+b2Body.m_local_oldCenter = new b2Vec2();
+b2Body.m_local_xf1 = new b2Transform();
+b2Body.prototype = {
+ CreateFixture: function (def, density) {
+ if (typeof (density) !== "undefined") {
+ var ndef = new b2FixtureDef();
+ ndef.shape = def;
+ ndef.density = density;
+ return this.CreateFixture(ndef)
+ }
+ if (this.m_world.IsLocked() == true) {
+ return null
+ }
+ var fixture = new b2Fixture();
+ fixture.Create(this, def);
+ if (this.m_flags & b2Body.e_activeFlag) {
+ var broadPhase = this.m_world.m_contactManager.m_broadPhase;
+ fixture.CreateProxies(broadPhase, this.m_xf)
+ }
+ fixture.m_next = this.m_fixtureList;
+ this.m_fixtureList = fixture;
+ ++this.m_fixtureCount;
+ fixture.m_body = this;
+ if (fixture.m_density > 0) {
+ this.ResetMassData()
+ }
+ this.m_world.m_flags |= b2World.e_newFixture;
+ return fixture
+ },
+ DestroyFixture: function (fixture) {
+ if (this.m_world.IsLocked() == true) {
+ return
+ }
+ var node = this.m_fixtureList;
+ var found = false;
+ while (node != null) {
+ if (node == fixture) {
+ this.m_fixtureList = node = fixture.m_next;
+ found = true;
+ break
+ }
+ node = node.m_next
+ }
+ var edge = this.m_contactList;
+ while (edge) {
+ var c = edge.contact;
+ edge = edge.next;
+ var fixtureA = c.GetFixtureA();
+ var fixtureB = c.GetFixtureB();
+ if (fixture == fixtureA || fixture == fixtureB) {
+ this.m_world.m_contactManager.Destroy(c)
+ }
+ }
+ if (this.m_flags & b2Body.e_activeFlag) {
+ var broadPhase = this.m_world.m_contactManager.m_broadPhase;
+ fixture.DestroyProxies(broadPhase)
+ }
+ fixture.Destroy();
+ fixture.m_body = null;
+ fixture.m_next = null;
+ --this.m_fixtureCount;
+ this.ResetMassData()
+ },
+ SetTransform: function (position, angle) {
+ if (this.m_world.IsLocked() == true) {
+ return
+ }
+ this.m_xf.q.Set(angle);
+ this.m_xf.p.Assign(position);
+ this.m_sweep.c.Assign(b2Mul_t_v2(this.m_xf, this.m_sweep.localCenter));
+ this.m_sweep.a = angle;
+ this.m_sweep.c0.Assign(this.m_sweep.c);
+ this.m_sweep.a0 = angle;
+ var broadPhase = this.m_world.m_contactManager.m_broadPhase;
+ for (var f = this.m_fixtureList; f; f = f.m_next) {
+ f.Synchronize(broadPhase, this.m_xf, this.m_xf)
+ }
+ },
+ GetTransform: function () {
+ return this.m_xf
+ },
+ GetPosition: function () {
+ return this.m_xf.p
+ },
+ GetAngle: function () {
+ return this.m_sweep.a
+ },
+ GetWorldCenter: function () {
+ return this.m_sweep.c
+ },
+ GetLocalCenter: function () {
+ return this.m_sweep.localCenter
+ },
+ SetLinearVelocity: function (v) {
+ if (this.m_type == b2Body.b2_staticBody) {
+ return
+ }
+ if (b2Dot_v2_v2(v, v) > 0) {
+ this.SetAwake(true)
+ }
+ this.m_linearVelocity = v
+ },
+ GetLinearVelocity: function () {
+ return this.m_linearVelocity
+ },
+ SetAngularVelocity: function (w) {
+ if (this.m_type == b2Body.b2_staticBody) {
+ return
+ }
+ if (w * w > 0) {
+ this.SetAwake(true)
+ }
+ this.m_angularVelocity = w
+ },
+ GetAngularVelocity: function () {
+ return this.m_angularVelocity
+ },
+ ApplyForce: function (force, point, wake) {
+ if (this.m_type != b2Body.b2_dynamicBody) {
+ return
+ }
+ if (wake && (this.m_flags & b2Body.e_awakeFlag) == 0) {
+ this.SetAwake(true)
+ }
+ if (this.m_flags & b2Body.e_awakeFlag) {
+ this.m_force.Add(force);
+ this.m_torque += b2Cross_v2_v2(b2Vec2.Subtract(point, this.m_sweep.c), force)
+ }
+ },
+ ApplyForceToCenter: function (force, wake) {
+ if (this.m_type != b2Body.b2_dynamicBody) {
+ return
+ }
+ if (wake && (this.m_flags & b2Body.e_awakeFlag) == 0) {
+ this.SetAwake(true)
+ }
+ if (this.m_flags & b2Body.e_awakeFlag) {
+ this.m_force.Add(force)
+ }
+ },
+ ApplyTorque: function (torque, wake) {
+ if (this.m_type != b2Body.b2_dynamicBody) {
+ return
+ }
+ if (wake && (this.m_flags & b2Body.e_awakeFlag) == 0) {
+ this.SetAwake(true)
+ }
+ if (this.m_flags & b2Body.e_awakeFlag) {
+ this.m_torque += torque
+ }
+ },
+ ApplyLinearImpulse: function (impulse, point, wake) {
+ if (this.m_type != b2Body.b2_dynamicBody) {
+ return
+ }
+ if (wake && (this.m_flags & b2Body.e_awakeFlag) == 0) {
+ this.SetAwake(true)
+ }
+ if (this.m_flags & b2Body.e_awakeFlag) {
+ this.m_linearVelocity.Add(b2Vec2.Multiply(this.m_invMass, impulse));
+ this.m_angularVelocity += this.m_invI * b2Cross_v2_v2(b2Vec2.Subtract(point, this.m_sweep.c), impulse)
+ }
+ },
+ ApplyAngularImpulse: function (impulse, wake) {
+ if (this.m_type != b2Body.b2_dynamicBody) {
+ return
+ }
+ if (wake && (this.m_flags & b2Body.e_awakeFlag) == 0) {
+ this.SetAwake(true)
+ }
+ if (this.m_flags & b2Body.e_awakeFlag) {
+ this.m_angularVelocity += this.m_invI * impulse
+ }
+ },
+ GetMass: function () {
+ return this.m_mass
+ },
+ GetInertia: function () {
+ return this.m_I + this.m_mass * b2Dot_v2_v2(this.m_sweep.localCenter, this.m_sweep.localCenter)
+ },
+ GetMassData: function (data) {
+ data.mass = this.m_mass;
+ data.I = this.m_I + this.m_mass * b2Dot_v2_v2(this.m_sweep.localCenter, this.m_sweep.localCenter);
+ data.center = this.m_sweep.localCenter
+ },
+ SetMassData: function (massData) {
+ if (this.m_world.IsLocked() == true) {
+ return
+ }
+ if (this.m_type != b2Body.b2_dynamicBody) {
+ return
+ }
+ this.m_invMass = 0;
+ this.m_I = 0;
+ this.m_invI = 0;
+ this.m_mass = massData.mass;
+ if (this.m_mass <= 0) {
+ this.m_mass = 1
+ }
+ this.m_invMass = 1 / this.m_mass;
+ if (massData.I > 0 && (this.m_flags & b2Body.e_fixedRotationFlag) == 0) {
+ this.m_I = massData.I - this.m_mass * b2Dot_v2_v2(massData.center, massData.center);
+ this.m_invI = 1 / this.m_I
+ }
+ b2Body.m_local_oldCenter.Assign(this.m_sweep.c);
+ this.m_sweep.localCenter.Assign(massData.center);
+ this.m_sweep.c0.Assign(b2Mul_t_v2(this.m_xf, this.m_sweep.localCenter));
+ this.m_sweep.c.Assign(this.m_sweep.c0);
+ this.m_linearVelocity.Add(b2Cross_f_v2(this.m_angularVelocity, b2Vec2.Subtract(this.m_sweep.c, b2Body.m_local_oldCenter)))
+ },
+ ResetMassData: function () {
+ this.m_mass = 0;
+ this.m_invMass = 0;
+ this.m_I = 0;
+ this.m_invI = 0;
+ this.m_sweep.localCenter.SetZero();
+ if (this.m_type == b2Body.b2_staticBody || this.m_type == b2Body.b2_kinematicBody) {
+ this.m_sweep.c0.Assign(this.m_xf.p);
+ this.m_sweep.c.Assign(this.m_xf.p);
+ this.m_sweep.a0 = this.m_sweep.a;
+ return
+ }
+ var localCenter = new b2Vec2(0, 0);
+ for (var f = this.m_fixtureList; f; f = f.m_next) {
+ if (f.m_density == 0) {
+ continue
+ }
+ var massData = new b2MassData();
+ f.GetMassData(massData);
+ this.m_mass += massData.mass;
+ localCenter.Add(b2Vec2.Multiply(massData.mass, massData.center));
+ this.m_I += massData.I
+ }
+ if (this.m_mass > 0) {
+ this.m_invMass = 1 / this.m_mass;
+ localCenter.Multiply(this.m_invMass)
+ } else {
+ this.m_mass = 1;
+ this.m_invMass = 1
+ }
+ if (this.m_I > 0 && (this.m_flags & b2Body.e_fixedRotationFlag) == 0) {
+ this.m_I -= this.m_mass * b2Dot_v2_v2(localCenter, localCenter);
+ this.m_invI = 1 / this.m_I
+ } else {
+ this.m_I = 0;
+ this.m_invI = 0
+ }
+ b2Body.m_local_oldCenter.Assign(this.m_sweep.c);
+ this.m_sweep.localCenter.Assign(localCenter);
+ this.m_sweep.c0.Assign(b2Mul_t_v2(this.m_xf, this.m_sweep.localCenter));
+ this.m_sweep.c.Assign(this.m_sweep.c0);
+ this.m_linearVelocity.Add(b2Cross_f_v2(this.m_angularVelocity, b2Vec2.Subtract(this.m_sweep.c, b2Body.m_local_oldCenter)))
+ },
+ GetWorldPoint: function (localPoint) {
+ return b2Mul_t_v2(this.m_xf, localPoint)
+ },
+ GetWorldVector: function (localVector) {
+ return b2Mul_r_v2(this.m_xf.q, localVector)
+ },
+ GetLocalPoint: function (worldPoint) {
+ return b2MulT_t_v2(this.m_xf, worldPoint)
+ },
+ GetLocalVector: function (worldVector) {
+ return b2MulT_r_v2(this.m_xf.q, worldVector)
+ },
+ GetLinearVelocityFromWorldPoint: function (worldPoint) {
+ return b2Vec2.Add(this.m_linearVelocity, b2Cross_f_v2(this.m_angularVelocity, b2Vec2.Subtract(worldPoint, this.m_sweep.c)))
+ },
+ GetLinearVelocityFromLocalPoint: function (localPoint) {
+ return this.GetLinearVelocityFromWorldPoint(this.GetWorldPoint(localPoint))
+ },
+ GetLinearDamping: function () {
+ return this.m_linearDamping
+ },
+ SetLinearDamping: function (linearDamping) {
+ this.m_linearDamping = linearDamping
+ },
+ GetAngularDamping: function () {
+ return this.m_angularDamping
+ },
+ SetAngularDamping: function (angularDamping) {
+ this.m_angularDamping = angularDamping
+ },
+ GetGravityScale: function () {
+ return this.m_gravityScale
+ },
+ SetGravityScale: function (scale) {
+ this.m_gravityScale = scale
+ },
+ SetType: function (type) {
+ if (this.m_world.IsLocked() == true) {
+ return
+ }
+ if (this.m_type == type) {
+ return
+ }
+ this.m_type = type;
+ this.ResetMassData();
+ if (this.m_type == b2Body.b2_staticBody) {
+ this.m_linearVelocity.SetZero();
+ this.m_angularVelocity = 0;
+ this.m_sweep.a0 = this.m_sweep.a;
+ this.m_sweep.c0.Assign(this.m_sweep.c);
+ this.SynchronizeFixtures()
+ }
+ this.SetAwake(true);
+ this.m_force.SetZero();
+ this.m_torque = 0;
+ var ce = this.m_contactList;
+ while (ce) {
+ var ce0 = ce;
+ ce = ce.next;
+ this.m_world.m_contactManager.Destroy(ce0.contact)
+ }
+ this.m_contactList = null;
+ var broadPhase = this.m_world.m_contactManager.m_broadPhase;
+ for (var f = this.m_fixtureList; f; f = f.m_next) {
+ var proxyCount = f.m_proxyCount;
+ for (var i = 0; i < proxyCount; ++i) {
+ broadPhase.TouchProxy(f.m_proxies[i].proxyId)
+ }
+ }
+ },
+ GetType: function () {
+ return this.m_type
+ },
+ SetBullet: function (flag) {
+ if (flag) {
+ this.m_flags |= b2Body.e_bulletFlag
+ } else {
+ this.m_flags &= ~b2Body.e_bulletFlag
+ }
+ },
+ IsBullet: function () {
+ return (this.m_flags & b2Body.e_bulletFlag) == b2Body.e_bulletFlag
+ },
+ SetSleepingAllowed: function (flag) {
+ if (flag) {
+ this.m_flags |= b2Body.e_autoSleepFlag
+ } else {
+ this.m_flags &= ~b2Body.e_autoSleepFlag;
+ this.SetAwake(true)
+ }
+ },
+ IsSleepingAllowed: function () {
+ return (this.m_flags & b2Body.e_autoSleepFlag) == b2Body.e_autoSleepFlag
+ },
+ SetAwake: function (flag) {
+ if (flag) {
+ if ((this.m_flags & b2Body.e_awakeFlag) == 0) {
+ this.m_flags |= b2Body.e_awakeFlag;
+ this.m_sleepTime = 0
+ }
+ } else {
+ this.m_flags &= ~b2Body.e_awakeFlag;
+ this.m_sleepTime = 0;
+ this.m_linearVelocity.SetZero();
+ this.m_angularVelocity = 0;
+ this.m_force.SetZero();
+ this.m_torque = 0
+ }
+ },
+ IsAwake: function () {
+ return (this.m_flags & b2Body.e_awakeFlag) == b2Body.e_awakeFlag
+ },
+ SetActive: function (flag) {
+ if (flag == this.IsActive()) {
+ return
+ }
+ if (flag) {
+ this.m_flags |= b2Body.e_activeFlag;
+ var broadPhase = this.m_world.m_contactManager.m_broadPhase;
+ for (var f = this.m_fixtureList; f; f = f.m_next) {
+ f.CreateProxies(broadPhase, this.m_xf)
+ }
+ } else {
+ this.m_flags &= ~b2Body.e_activeFlag;
+ var broadPhase = this.m_world.m_contactManager.m_broadPhase;
+ for (var f = this.m_fixtureList; f; f = f.m_next) {
+ f.DestroyProxies(broadPhase)
+ }
+ var ce = this.m_contactList;
+ while (ce) {
+ var ce0 = ce;
+ ce = ce.next;
+ this.m_world.m_contactManager.Destroy(ce0.contact)
+ }
+ this.m_contactList = null
+ }
+ },
+ IsActive: function () {
+ return (this.m_flags & b2Body.e_activeFlag) == b2Body.e_activeFlag
+ },
+ SetFixedRotation: function (flag) {
+ var status = (this.m_flags & b2Body.e_fixedRotationFlag) == b2Body.e_fixedRotationFlag;
+ if (status == flag) {
+ return
+ }
+ if (flag) {
+ this.m_flags |= b2Body.e_fixedRotationFlag
+ } else {
+ this.m_flags &= ~b2Body.e_fixedRotationFlag
+ }
+ this.m_angularVelocity = 0;
+ this.ResetMassData()
+ },
+ IsFixedRotation: function () {
+ return (this.m_flags & b2Body.e_fixedRotationFlag) == b2Body.e_fixedRotationFlag
+ },
+ GetFixtureList: function () {
+ return this.m_fixtureList
+ },
+ GetJointList: function () {
+ return this.m_jointList
+ },
+ GetContactList: function () {
+ return this.m_contactList
+ },
+ GetNext: function () {
+ return this.m_next
+ },
+ GetUserData: function () {
+ return this.m_userData
+ },
+ SetUserData: function (data) {
+ this.m_userData = data
+ },
+ GetWorld: function () {
+ return this.m_world
+ },
+ SynchronizeFixtures: function () {
+ b2Body.m_local_xf1.q.Set(this.m_sweep.a0);
+ b2Body.m_local_xf1.p.Assign(b2Vec2.Subtract(this.m_sweep.c0, b2Mul_r_v2(b2Body.m_local_xf1.q, this.m_sweep.localCenter)));
+ var broadPhase = this.m_world.m_contactManager.m_broadPhase;
+ for (var f = this.m_fixtureList; f; f = f.m_next) {
+ f.Synchronize(broadPhase, b2Body.m_local_xf1, this.m_xf)
+ }
+ },
+ SynchronizeTransform: function () {
+ this.m_xf.q.Set(this.m_sweep.a);
+ this.m_xf.p.Assign(b2Vec2.Subtract(this.m_sweep.c, b2Mul_r_v2(this.m_xf.q, this.m_sweep.localCenter)))
+ },
+ ShouldCollide: function (other) {
+ if (this.m_type != b2Body.b2_dynamicBody && other.m_type != b2Body.b2_dynamicBody) {
+ return false
+ }
+ for (var jn = this.m_jointList; jn; jn = jn.next) {
+ if (jn.other == other) {
+ if (jn.joint.m_collideConnected == false) {
+ return false
+ }
+ }
+ }
+ return true
+ },
+ Advance: function (alpha) {
+ this.m_sweep.Advance(alpha);
+ this.m_sweep.c.Assign(this.m_sweep.c0);
+ this.m_sweep.a = this.m_sweep.a0;
+ this.m_xf.q.Set(this.m_sweep.a);
+ this.m_xf.p.Assign(b2Vec2.Subtract(this.m_sweep.c, b2Mul_r_v2(this.m_xf.q, this.m_sweep.localCenter)))
+ },
+ _serialize: function (out) {
+ var obj = out || {};
+ obj.fixtures = null;
+ obj.type = this.m_type;
+ obj.position = this.GetPosition()._serialize();
+ obj.angle = this.GetAngle();
+ obj.linearVelocity = this.GetLinearVelocity()._serialize();
+ obj.angularVelocity = this.GetAngularVelocity();
+ obj.linearDamping = this.GetLinearDamping();
+ obj.angularDamping = this.GetAngularDamping();
+ obj.allowSleep = this.IsSleepingAllowed();
+ obj.awake = this.IsAwake();
+ obj.fixedRotation = this.IsFixedRotation();
+ obj.bullet = this.IsBullet();
+ obj.active = this.IsActive();
+ obj.gravityScale = this.GetGravityScale();
+ return obj
+ }
+};
+"use strict";
+
+function b2Filter() {
+ this.categoryBits = 1;
+ this.maskBits = 65535;
+ this.groupIndex = 0
+}
+b2Filter.prototype = {
+ Clone: function () {
+ var filter = new b2Filter();
+ filter.categoryBits = this.categoryBits;
+ filter.maskBits = this.maskBits;
+ filter.groupIndex = this.groupIndex;
+ return filter
+ },
+ Assign: function (filter) {
+ this.categoryBits = filter.categoryBits;
+ this.maskBits = filter.maskBits;
+ this.groupIndex = filter.groupIndex
+ },
+ _serialize: function (out) {
+ var obj = out || {};
+ obj.categoryBits = this.categoryBits;
+ obj.maskBits = this.maskBits;
+ obj.groupIndex = this.groupIndex;
+ return obj
+ },
+ _deserialize: function (data) {
+ this.categoryBits = data.categoryBits;
+ this.maskBits = data.maskBits;
+ this.groupIndex = data.groupIndex
+ }
+};
+
+function b2FixtureDef() {
+ this.shape = null;
+ this.userData = null;
+ this.friction = 0.2;
+ this.restitution = 0;
+ this.density = 0;
+ this.isSensor = false;
+ this.filter = new b2Filter();
+ Object.seal(this)
+}
+b2FixtureDef.prototype = {
+ _deserialize: function (data) {
+ this.friction = data.friction;
+ this.restitution = data.restitution;
+ this.density = data.density;
+ this.isSensor = data.isSensor;
+ this.filter._deserialize(data.filter)
+ }
+};
+
+function b2FixtureProxy() {
+ this.aabb = new b2AABB();
+ this.fixture = null;
+ this.childIndex = 0;
+ this.proxyId = 0
+}
+
+function b2Fixture() {
+ this.m_userData = null;
+ this.m_body = null;
+ this.m_next = null;
+ this.m_proxies = null;
+ this.m_proxyCount = 0;
+ this.m_shape = null;
+ this.m_density = 0;
+ this.m_filter = new b2Filter();
+ this.m_isSensor = false;
+ this.m_friction = 0;
+ this.m_restitution = 0
+}
+b2Fixture.prototype = {
+ GetType: function () {
+ return this.m_shape.GetType()
+ },
+ GetShape: function () {
+ return this.m_shape
+ },
+ SetSensor: function (sensor) {
+ if (sensor != this.m_isSensor) {
+ this.m_body.SetAwake(true);
+ this.m_isSensor = sensor
+ }
+ },
+ IsSensor: function () {
+ return this.m_isSensor
+ },
+ SetFilterData: function (filter) {
+ this.m_filter = filter;
+ this.Refilter()
+ },
+ GetFilterData: function () {
+ return this.m_filter
+ },
+ Refilter: function () {
+ if (this.m_body == null) {
+ return
+ }
+ var edge = this.m_body.GetContactList();
+ while (edge) {
+ var contact = edge.contact;
+ var fixtureA = contact.GetFixtureA();
+ var fixtureB = contact.GetFixtureB();
+ if (fixtureA == this || fixtureB == this) {
+ contact.FlagForFiltering()
+ }
+ edge = edge.next
+ }
+ var world = this.m_body.GetWorld();
+ if (world == null) {
+ return
+ }
+ var broadPhase = world.m_contactManager.m_broadPhase;
+ for (var i = 0; i < this.m_proxyCount; ++i) {
+ broadPhase.TouchProxy(this.m_proxies[i].proxyId)
+ }
+ },
+ GetBody: function () {
+ return this.m_body
+ },
+ GetNext: function () {
+ return this.m_next
+ },
+ GetUserData: function () {
+ return this.m_userData
+ },
+ SetUserData: function (data) {
+ this.m_userData = data
+ },
+ TestPoint: function (p) {
+ return this.m_shape.TestPoint(this.m_body.GetTransform(), p)
+ },
+ RayCast: function (output, input, childIndex) {
+ return this.m_shape.RayCast(output, input, this.m_body.GetTransform(), childIndex)
+ },
+ GetMassData: function (massData) {
+ this.m_shape.ComputeMass(massData, this.m_density)
+ },
+ SetDensity: function (density) {
+ this.m_density = density
+ },
+ GetDensity: function () {
+ return this.m_density
+ },
+ GetFriction: function () {
+ return this.m_friction
+ },
+ SetFriction: function (friction) {
+ this.m_friction = friction
+ },
+ GetRestitution: function () {
+ return this.m_restitution
+ },
+ SetRestitution: function (restitution) {
+ this.m_restitution = restitution
+ },
+ GetAABB: function (childIndex) {
+ return this.m_proxies[childIndex].aabb
+ },
+ Create: function (body, def) {
+ this.m_userData = def.userData;
+ this.m_friction = def.friction;
+ this.m_restitution = def.restitution;
+ this.m_body = body;
+ this.m_next = null;
+ this.m_filter.Assign(def.filter);
+ this.m_isSensor = def.isSensor;
+ this.m_shape = def.shape.Clone();
+ var childCount = this.m_shape.GetChildCount();
+ this.m_proxies = new Array(childCount);
+ for (var i = 0; i < childCount; ++i) {
+ this.m_proxies[i] = new b2FixtureProxy();
+ this.m_proxies[i].fixture = null;
+ this.m_proxies[i].proxyId = b2BroadPhase.e_nullProxy
+ }
+ this.m_proxyCount = 0;
+ this.m_density = def.density
+ },
+ Destroy: function () {
+ this.m_proxies = null;
+ this.m_shape = null
+ },
+ CreateProxies: function (broadPhase, xf) {
+ this.m_proxyCount = this.m_shape.GetChildCount();
+ for (var i = 0; i < this.m_proxyCount; ++i) {
+ var proxy = this.m_proxies[i];
+ this.m_shape.ComputeAABB(proxy.aabb, xf, i);
+ proxy.proxyId = broadPhase.CreateProxy(proxy.aabb, proxy);
+ proxy.fixture = this;
+ proxy.childIndex = i
+ }
+ },
+ DestroyProxies: function (broadPhase) {
+ for (var i = 0; i < this.m_proxyCount; ++i) {
+ var proxy = this.m_proxies[i];
+ broadPhase.DestroyProxy(proxy.proxyId);
+ proxy.proxyId = b2BroadPhase.e_nullProxy
+ }
+ this.m_proxyCount = 0
+ },
+ Synchronize: function (broadPhase, transform1, transform2) {
+ if (this.m_proxyCount == 0) {
+ return
+ }
+ for (var i = 0; i < this.m_proxyCount; ++i) {
+ var proxy = this.m_proxies[i];
+ var aabb1 = new b2AABB(),
+ aabb2 = new b2AABB();
+ this.m_shape.ComputeAABB(aabb1, transform1, proxy.childIndex);
+ this.m_shape.ComputeAABB(aabb2, transform2, proxy.childIndex);
+ proxy.aabb.Combine(aabb1, aabb2);
+ var displacement = b2Vec2.Subtract(transform2.p, transform1.p);
+ broadPhase.MoveProxy(proxy.proxyId, proxy.aabb, displacement)
+ }
+ },
+ _serialize: function (out) {
+ var obj = out || {};
+ obj.shape = null;
+ obj.friction = this.m_friction;
+ obj.restitution = this.m_restitution;
+ obj.density = this.m_density;
+ obj.isSensor = this.m_isSensor;
+ obj.filter = this.m_filter._serialize();
+ return obj
+ }
+};
+"use strict";
+
+function b2DestructionListener() {}
+b2DestructionListener.prototype = {
+ SayGoodbyeJoint: function (joint) {},
+ SayGoodbyeFixture: function (fixture) {}
+};
+
+function b2ContactFilter() {}
+b2ContactFilter.prototype = {
+ ShouldCollide: function (fixtureA, fixtureB) {
+ var filterA = fixtureA.GetFilterData();
+ var filterB = fixtureB.GetFilterData();
+ if (filterA.groupIndex == filterB.groupIndex && filterA.groupIndex != 0) {
+ return filterA.groupIndex > 0
+ }
+ var collide = (filterA.maskBits & filterB.categoryBits) != 0 && (filterA.categoryBits & filterB.maskBits) != 0;
+ return collide
+ }
+};
+
+function b2ContactImpulse() {
+ this.normalImpulses = new Array(b2_maxManifoldPoints);
+ this.tangentImpulses = new Array(b2_maxManifoldPoints);
+ this.count = 0
+}
+
+function b2ContactListener() {}
+b2ContactListener.prototype = {
+ BeginContact: function (contact) {},
+ EndContact: function (contact) {},
+ PreSolve: function (contact, oldManifold) {},
+ PostSolve: function (contact, impulse) {}
+};
+
+function b2QueryCallback() {}
+b2QueryCallback.prototype = {
+ ReportFixture: function (fixture) {}
+};
+
+function b2RayCastCallback() {}
+b2RayCastCallback.prototype = {
+ ReportFixture: function (fixture, point, normal, fraction) {}
+};
+"use strict";
+
+function b2TimeStep() {
+ this.dt = 0;
+ this.inv_dt = 0;
+ this.dtRatio = 0;
+ this.velocityIterations = 0;
+ this.positionIterations = 0;
+ this.warmStarting = false
+}
+
+function b2Position() {
+ this.c = new b2Vec2();
+ this.a = 0
+}
+
+function b2Velocity() {
+ this.v = new b2Vec2();
+ this.w = 0
+}
+
+function b2SolverData() {
+ this.step = new b2TimeStep();
+ this.positions = null;
+ this.velocities = null
+}
+"use strict";
+var profile_world_step = b2Profiler.create("step");
+var profile_world_collide = b2Profiler.create("collide", "step");
+var profile_world_solve = b2Profiler.create("solve", "step");
+var profile_world_solveTOI = b2Profiler.create("solveTOI", "step");
+var profile_world_broadphase = b2Profiler.create("broadphase", "step");
+
+function b2World(gravity) {
+ this.m_contactManager = new b2ContactManager();
+ this.m_destructionListener = null;
+ this.g_debugDraw = null;
+ this.m_bodyList = null;
+ this.m_jointList = null;
+ this.m_bodyCount = 0;
+ this.m_jointCount = 0;
+ this.m_warmStarting = true;
+ this.m_continuousPhysics = true;
+ this.m_subStepping = false;
+ this.m_stepComplete = true;
+ this.m_allowSleep = true;
+ this.m_gravity = gravity;
+ this.m_flags = b2World.e_clearForces;
+ this.m_inv_dt0 = 0;
+ this.p_step = new b2TimeStep();
+ this.p_island = new b2Island()
+}
+
+function b2WorldQueryWrapper() {
+ this.broadPhase = null;
+ this.callback = null
+}
+b2WorldQueryWrapper.prototype = {
+ QueryCallback: function (proxyId) {
+ var proxy = this.broadPhase.GetUserData(proxyId);
+ return this.callback.ReportFixture(proxy.fixture)
+ }
+};
+
+function b2WorldRayCastWrapper() {
+ this.broadPhase = null;
+ this.callback = null
+}
+b2WorldRayCastWrapper.prototype = {
+ RayCastCallback: function (input, proxyId) {
+ var userData = this.broadPhase.GetUserData(proxyId);
+ var proxy = userData;
+ var fixture = proxy.fixture;
+ var index = proxy.childIndex;
+ var output = new b2RayCastOutput();
+ var hit = fixture.RayCast(output, input, index);
+ if (hit) {
+ var fraction = output.fraction;
+ var point = b2Vec2.Add(b2Vec2.Multiply((1 - fraction), input.p1), b2Vec2.Multiply(fraction, input.p2));
+ return this.callback.ReportFixture(fixture, point, output.normal, fraction)
+ }
+ return input.maxFraction
+ }
+};
+b2World.m_local_sweep_backupA = new b2Sweep();
+b2World.m_local_sweep_backupB = new b2Sweep();
+b2World.m_local_sweep_backupC = new b2Sweep();
+b2World.prototype = {
+ Destroy: function () {
+ var b = this.m_bodyList;
+ while (b) {
+ var bNext = b.m_next;
+ var f = b.m_fixtureList;
+ while (f) {
+ var fNext = f.m_next;
+ f.m_proxyCount = 0;
+ f.Destroy();
+ f = fNext
+ }
+ b = bNext
+ }
+ },
+ SetDestructionListener: function (listener) {
+ this.m_destructionListener = listener
+ },
+ SetContactFilter: function (filter) {
+ this.m_contactManager.m_contactFilter = filter
+ },
+ SetContactListener: function (listener) {
+ this.m_contactManager.m_contactListener = listener
+ },
+ SetDebugDraw: function (debugDraw) {
+ this.g_debugDraw = debugDraw
+ },
+ CreateBody: function (def) {
+ if (this.IsLocked()) {
+ return null
+ }
+ var b = new b2Body(def, this);
+ b.m_prev = null;
+ b.m_next = this.m_bodyList;
+ if (this.m_bodyList) {
+ this.m_bodyList.m_prev = b
+ }
+ this.m_bodyList = b;
+ ++this.m_bodyCount;
+ return b
+ },
+ DestroyBody: function (b) {
+ if (this.IsLocked()) {
+ return
+ }
+ var je = b.m_jointList;
+ while (je) {
+ var je0 = je;
+ je = je.next;
+ if (this.m_destructionListener) {
+ this.m_destructionListener.SayGoodbyeJoint(je0.joint)
+ }
+ this.DestroyJoint(je0.joint);
+ b.m_jointList = je
+ }
+ b.m_jointList = null;
+ var ce = b.m_contactList;
+ while (ce) {
+ var ce0 = ce;
+ ce = ce.next;
+ this.m_contactManager.Destroy(ce0.contact)
+ }
+ b.m_contactList = null;
+ var f = b.m_fixtureList;
+ while (f) {
+ var f0 = f;
+ f = f.m_next;
+ if (this.m_destructionListener) {
+ this.m_destructionListener.SayGoodbyeFixture(f0)
+ }
+ f0.DestroyProxies(this.m_contactManager.m_broadPhase);
+ f0.Destroy();
+ b.m_fixtureList = f;
+ b.m_fixtureCount -= 1
+ }
+ b.m_fixtureList = null;
+ b.m_fixtureCount = 0;
+ if (b.m_prev) {
+ b.m_prev.m_next = b.m_next
+ }
+ if (b.m_next) {
+ b.m_next.m_prev = b.m_prev
+ }
+ if (b == this.m_bodyList) {
+ this.m_bodyList = b.m_next
+ }
+ b.m_destroyed = true;
+ --this.m_bodyCount
+ },
+ CreateJoint: function (def) {
+ if (this.IsLocked()) {
+ return null
+ }
+ var j = b2Joint.Create(def);
+ j.m_prev = null;
+ j.m_next = this.m_jointList;
+ if (this.m_jointList) {
+ this.m_jointList.m_prev = j
+ }
+ this.m_jointList = j;
+ ++this.m_jointCount;
+ j.m_edgeA.joint = j;
+ j.m_edgeA.other = j.m_bodyB;
+ j.m_edgeA.prev = null;
+ j.m_edgeA.next = j.m_bodyA.m_jointList;
+ if (j.m_bodyA.m_jointList) {
+ j.m_bodyA.m_jointList.prev = j.m_edgeA
+ }
+ j.m_bodyA.m_jointList = j.m_edgeA;
+ j.m_edgeB.joint = j;
+ j.m_edgeB.other = j.m_bodyA;
+ j.m_edgeB.prev = null;
+ j.m_edgeB.next = j.m_bodyB.m_jointList;
+ if (j.m_bodyB.m_jointList) {
+ j.m_bodyB.m_jointList.prev = j.m_edgeB
+ }
+ j.m_bodyB.m_jointList = j.m_edgeB;
+ var bodyA = def.bodyA;
+ var bodyB = def.bodyB;
+ if (def.collideConnected == false) {
+ var edge = bodyB.GetContactList();
+ while (edge) {
+ if (edge.other == bodyA) {
+ edge.contact.FlagForFiltering()
+ }
+ edge = edge.next
+ }
+ }
+ return j
+ },
+ DestroyJoint: function (j) {
+ if (this.IsLocked()) {
+ return
+ }
+ var collideConnected = j.m_collideConnected;
+ if (j.m_prev) {
+ j.m_prev.m_next = j.m_next
+ }
+ if (j.m_next) {
+ j.m_next.m_prev = j.m_prev
+ }
+ if (j == this.m_jointList) {
+ this.m_jointList = j.m_next
+ }
+ var bodyA = j.m_bodyA;
+ var bodyB = j.m_bodyB;
+ bodyA.SetAwake(true);
+ bodyB.SetAwake(true);
+ if (j.m_edgeA.prev) {
+ j.m_edgeA.prev.next = j.m_edgeA.next
+ }
+ if (j.m_edgeA.next) {
+ j.m_edgeA.next.prev = j.m_edgeA.prev
+ }
+ if (j.m_edgeA == bodyA.m_jointList) {
+ bodyA.m_jointList = j.m_edgeA.next
+ }
+ j.m_edgeA.prev = null;
+ j.m_edgeA.next = null;
+ if (j.m_edgeB.prev) {
+ j.m_edgeB.prev.next = j.m_edgeB.next
+ }
+ if (j.m_edgeB.next) {
+ j.m_edgeB.next.prev = j.m_edgeB.prev
+ }
+ if (j.m_edgeB == bodyB.m_jointList) {
+ bodyB.m_jointList = j.m_edgeB.next
+ }
+ j.m_edgeB.prev = null;
+ j.m_edgeB.next = null;
+ b2Joint.Destroy(j);
+ --this.m_jointCount;
+ if (collideConnected == false) {
+ var edge = bodyB.GetContactList();
+ while (edge) {
+ if (edge.other == bodyA) {
+ edge.contact.FlagForFiltering()
+ }
+ edge = edge.next
+ }
+ }
+ },
+ Step: function (dt, velocityIterations, positionIterations) {
+ profile_world_step.start();
+ if (this.m_flags & b2World.e_newFixture) {
+ this.m_contactManager.FindNewContacts();
+ this.m_flags &= ~b2World.e_newFixture
+ }
+ this.m_flags |= b2World.e_locked;
+ this.p_step.dt = dt;
+ this.p_step.velocityIterations = velocityIterations;
+ this.p_step.positionIterations = positionIterations;
+ if (dt > 0) {
+ this.p_step.inv_dt = 1 / dt
+ } else {
+ this.p_step.inv_dt = 0
+ }
+ this.p_step.dtRatio = this.m_inv_dt0 * dt;
+ this.p_step.warmStarting = this.m_warmStarting;
+ profile_world_collide.start();
+ this.m_contactManager.Collide();
+ profile_world_collide.stop();
+ if (this.m_stepComplete && this.p_step.dt > 0) {
+ profile_world_solve.start();
+ this.Solve(this.p_step);
+ profile_world_solve.stop()
+ }
+ if (this.m_continuousPhysics && this.p_step.dt > 0) {
+ profile_world_solveTOI.start();
+ this.SolveTOI(this.p_step);
+ profile_world_solveTOI.stop()
+ }
+ if (this.p_step.dt > 0) {
+ this.m_inv_dt0 = this.p_step.inv_dt
+ }
+ if (this.m_flags & b2World.e_clearForces) {
+ this.ClearForces()
+ }
+ this.m_flags &= ~b2World.e_locked;
+ profile_world_step.stop()
+ },
+ ClearForces: function () {
+ for (var body = this.m_bodyList; body; body = body.GetNext()) {
+ body.m_force.x = body.m_force.y = 0;
+ body.m_torque = 0
+ }
+ },
+ DrawDebugData: function () {
+ if (this.g_debugDraw == null) {
+ return
+ }
+ var flags = this.g_debugDraw.GetFlags();
+ if (flags & b2Draw.e_shapeBit) {
+ for (var b = this.m_bodyList; b; b = b.GetNext()) {
+ var xf = b.GetTransform();
+ for (var f = b.GetFixtureList(); f; f = f.GetNext()) {
+ if (b.IsActive() == false) {
+ this.DrawShape(f, xf, new b2Color(0.5, 0.5, 0.3))
+ } else {
+ if (b.GetType() == b2Body.b2_staticBody) {
+ this.DrawShape(f, xf, new b2Color(0.5, 0.9, 0.5))
+ } else {
+ if (b.GetType() == b2Body.b2_kinematicBody) {
+ this.DrawShape(f, xf, new b2Color(0.5, 0.5, 0.9))
+ } else {
+ if (b.IsAwake() == false) {
+ this.DrawShape(f, xf, new b2Color(0.6, 0.6, 0.6))
+ } else {
+ this.DrawShape(f, xf, new b2Color(0.9, 0.7, 0.7))
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if (flags & b2Draw.e_jointBit) {
+ for (var j = this.m_jointList; j; j = j.GetNext()) {
+ this.DrawJoint(j)
+ }
+ }
+ if (flags & b2Draw.e_pairBit) {
+ var color = new b2Color(0.3, 0.9, 0.9);
+ for (var c = this.m_contactManager.m_contactList; c; c = c.GetNext()) {
+ var fixtureA = c.GetFixtureA();
+ var fixtureB = c.GetFixtureB();
+ var cA = fixtureA.GetAABB(c.GetChildIndexA()).GetCenter();
+ var cB = fixtureB.GetAABB(c.GetChildIndexB()).GetCenter();
+ this.g_debugDraw.DrawSegment(cA, cB, color)
+ }
+ }
+ if (flags & b2Draw.e_aabbBit) {
+ var color = new b2Color(0.9, 0.3, 0.9);
+ var bp = this.m_contactManager.m_broadPhase;
+ for (var b = this.m_bodyList; b; b = b.GetNext()) {
+ if (b.IsActive() == false) {
+ continue
+ }
+ for (var f = b.GetFixtureList(); f; f = f.GetNext()) {
+ for (var i = 0; i < f.m_proxyCount; ++i) {
+ var proxy = f.m_proxies[i];
+ var aabb = bp.GetFatAABB(proxy.proxyId);
+ var vs = [];
+ vs[0] = new b2Vec2(aabb.lowerBound.x, aabb.lowerBound.y);
+ vs[1] = new b2Vec2(aabb.upperBound.x, aabb.lowerBound.y);
+ vs[2] = new b2Vec2(aabb.upperBound.x, aabb.upperBound.y);
+ vs[3] = new b2Vec2(aabb.lowerBound.x, aabb.upperBound.y);
+ this.g_debugDraw.DrawPolygon(vs, 4, color)
+ }
+ }
+ }
+ }
+ if (flags & b2Draw.e_centerOfMassBit) {
+ for (var b = this.m_bodyList; b; b = b.GetNext()) {
+ var xf = b.GetTransform().Clone();
+ xf.p = b.GetWorldCenter();
+ this.g_debugDraw.DrawTransform(xf)
+ }
+ }
+ },
+ QueryAABB: function (callback, aabb) {
+ var wrapper = new b2WorldQueryWrapper();
+ wrapper.broadPhase = this.m_contactManager.m_broadPhase;
+ wrapper.callback = callback;
+ this.m_contactManager.m_broadPhase.Query(wrapper, aabb)
+ },
+ RayCast: function (callback, point1, point2) {
+ var wrapper = new b2WorldRayCastWrapper();
+ wrapper.broadPhase = this.m_contactManager.m_broadPhase;
+ wrapper.callback = callback;
+ var input = new b2RayCastInput();
+ input.maxFraction = 1;
+ input.p1 = point1;
+ input.p2 = point2;
+ this.m_contactManager.m_broadPhase.RayCast(wrapper, input)
+ },
+ GetBodyList: function () {
+ return this.m_bodyList
+ },
+ GetJointList: function () {
+ return this.m_jointList
+ },
+ GetContactList: function () {
+ return this.m_contactManager.m_contactList
+ },
+ SetAllowSleeping: function (flag) {
+ if (flag == this.m_allowSleep) {
+ return
+ }
+ this.m_allowSleep = flag;
+ if (this.m_allowSleep == false) {
+ for (var b = this.m_bodyList; b; b = b.m_next) {
+ b.SetAwake(true)
+ }
+ }
+ },
+ GetAllowSleeping: function () {
+ return this.m_allowSleep
+ },
+ SetWarmStarting: function (flag) {
+ this.m_warmStarting = flag
+ },
+ GetWarmStarting: function () {
+ return this.m_warmStarting
+ },
+ SetContinuousPhysics: function (flag) {
+ this.m_continuousPhysics = flag
+ },
+ GetContinuousPhysics: function () {
+ return this.m_continuousPhysics
+ },
+ SetSubStepping: function (flag) {
+ this.m_subStepping = flag
+ },
+ GetSubStepping: function () {
+ return this.m_subStepping
+ },
+ GetProxyCount: function () {
+ return this.m_contactManager.m_broadPhase.GetProxyCount()
+ },
+ GetBodyCount: function () {
+ return this.m_bodyCount
+ },
+ GetJointCount: function () {
+ return this.m_jointCount
+ },
+ GetContactCount: function () {
+ return this.m_contactManager.m_contactCount
+ },
+ GetTreeHeight: function () {
+ return this.m_contactManager.m_broadPhase.GetTreeHeight()
+ },
+ GetTreeBalance: function () {
+ return this.m_contactManager.m_broadPhase.GetTreeBalance()
+ },
+ GetTreeQuality: function () {
+ return this.m_contactManager.m_broadPhase.GetTreeQuality()
+ },
+ SetGravity: function (gravity) {
+ this.m_gravity = gravity
+ },
+ GetGravity: function () {
+ return this.m_gravity
+ },
+ IsLocked: function () {
+ return (this.m_flags & b2World.e_locked) == b2World.e_locked
+ },
+ SetAutoClearForces: function (flag) {
+ if (flag) {
+ this.m_flags |= b2World.e_clearForces
+ } else {
+ this.m_flags &= ~b2World.e_clearForces
+ }
+ },
+ GetAutoClearForces: function () {
+ return (this.m_flags & b2World.e_clearForces) == b2World.e_clearForces
+ },
+ ShiftOrigin: function (newOrigin) {
+ if ((this.m_flags & b2World.e_locked) == b2World.e_locked) {
+ return
+ }
+ for (var b = this.m_bodyList; b; b = b.m_next) {
+ b.m_xf.p.Subtract(newOrigin);
+ b.m_sweep.c0.Subtract(newOrigin);
+ b.m_sweep.c.Subtract(newOrigin)
+ }
+ for (var j = this.m_jointList; j; j = j.m_next) {
+ j.ShiftOrigin(newOrigin)
+ }
+ this.m_contactManager.m_broadPhase.ShiftOrigin(newOrigin)
+ },
+ GetContactManager: function () {
+ return this.m_contactManager
+ },
+ Solve: function (step) {
+ this.p_island.Initialize(this.m_bodyCount, this.m_contactManager.m_contactCount, this.m_jointCount, this.m_contactManager.m_contactListener);
+ for (var b = this.m_bodyList; b; b = b.m_next) {
+ b.m_flags &= ~b2Body.e_islandFlag
+ }
+ for (var c = this.m_contactManager.m_contactList; c; c = c.m_next) {
+ c.m_flags &= ~b2Contact.e_islandFlag
+ }
+ for (var j = this.m_jointList; j; j = j.m_next) {
+ j.m_islandFlag = false
+ }
+ var stackSize = this.m_bodyCount;
+ var stack = new Array(stackSize);
+ for (var seed = this.m_bodyList; seed; seed = seed.m_next) {
+ if (seed.m_flags & b2Body.e_islandFlag) {
+ continue
+ }
+ if (seed.IsAwake() == false || seed.IsActive() == false) {
+ continue
+ }
+ if (seed.GetType() == b2Body.b2_staticBody) {
+ continue
+ }
+ this.p_island.Clear();
+ var stackCount = 0;
+ stack[stackCount++] = seed;
+ seed.m_flags |= b2Body.e_islandFlag;
+ while (stackCount > 0) {
+ var b = stack[--stackCount];
+ this.p_island.AddBody(b);
+ b.SetAwake(true);
+ if (b.GetType() == b2Body.b2_staticBody) {
+ continue
+ }
+ for (var ce = b.m_contactList; ce; ce = ce.next) {
+ var contact = ce.contact;
+ if (contact.m_flags & b2Contact.e_islandFlag) {
+ continue
+ }
+ if (contact.IsEnabled() == false || contact.IsTouching() == false) {
+ continue
+ }
+ var sensorA = contact.m_fixtureA.m_isSensor;
+ var sensorB = contact.m_fixtureB.m_isSensor;
+ if (sensorA || sensorB) {
+ continue
+ }
+ this.p_island.AddContact(contact);
+ contact.m_flags |= b2Contact.e_islandFlag;
+ var other = ce.other;
+ if (other.m_flags & b2Body.e_islandFlag) {
+ continue
+ }
+ stack[stackCount++] = other;
+ other.m_flags |= b2Body.e_islandFlag
+ }
+ for (var je = b.m_jointList; je; je = je.next) {
+ if (je.joint.m_islandFlag == true) {
+ continue
+ }
+ var other = je.other;
+ if (other.IsActive() == false) {
+ continue
+ }
+ this.p_island.AddJoint(je.joint);
+ je.joint.m_islandFlag = true;
+ if (other.m_flags & b2Body.e_islandFlag) {
+ continue
+ }
+ stack[stackCount++] = other;
+ other.m_flags |= b2Body.e_islandFlag
+ }
+ }
+ this.p_island.Solve(step, this.m_gravity, this.m_allowSleep);
+ for (var i = 0; i < this.p_island.m_bodyCount; ++i) {
+ var b = this.p_island.m_bodies[i];
+ if (b.GetType() == b2Body.b2_staticBody) {
+ b.m_flags &= ~b2Body.e_islandFlag
+ }
+ }
+ }
+ profile_world_broadphase.start();
+ for (var b = this.m_bodyList; b; b = b.GetNext()) {
+ if ((b.m_flags & b2Body.e_islandFlag) == 0) {
+ continue
+ }
+ if (b.GetType() == b2Body.b2_staticBody) {
+ continue
+ }
+ b.SynchronizeFixtures()
+ }
+ this.m_contactManager.FindNewContacts();
+ profile_world_broadphase.stop()
+ },
+ SolveTOI: function (step) {
+ this.p_island.Initialize(2 * b2_maxTOIContacts, b2_maxTOIContacts, 0, this.m_contactManager.m_contactListener);
+ if (this.m_stepComplete) {
+ for (var b = this.m_bodyList; b; b = b.m_next) {
+ b.m_flags &= ~b2Body.e_islandFlag;
+ b.m_sweep.alpha0 = 0
+ }
+ for (var c = this.m_contactManager.m_contactList; c; c = c.m_next) {
+ c.m_flags &= ~(b2Contact.e_toiFlag | b2Contact.e_islandFlag);
+ c.m_toiCount = 0;
+ c.m_toi = 1
+ }
+ }
+ for (;;) {
+ var minContact = null;
+ var minAlpha = 1;
+ for (var c = this.m_contactManager.m_contactList; c; c = c.m_next) {
+ if (c.IsEnabled() == false) {
+ continue
+ }
+ if (c.m_toiCount > b2_maxSubSteps) {
+ continue
+ }
+ var alpha = 1;
+ if (c.m_flags & b2Contact.e_toiFlag) {
+ alpha = c.m_toi
+ } else {
+ var fA = c.GetFixtureA();
+ var fB = c.GetFixtureB();
+ if (fA.IsSensor() || fB.IsSensor()) {
+ continue
+ }
+ var bA = fA.GetBody();
+ var bB = fB.GetBody();
+ var typeA = bA.m_type;
+ var typeB = bB.m_type;
+ var activeA = bA.IsAwake() && typeA != b2Body.b2_staticBody;
+ var activeB = bB.IsAwake() && typeB != b2Body.b2_staticBody;
+ if (activeA == false && activeB == false) {
+ continue
+ }
+ var collideA = bA.IsBullet() || typeA != b2Body.b2_dynamicBody;
+ var collideB = bB.IsBullet() || typeB != b2Body.b2_dynamicBody;
+ if (collideA == false && collideB == false) {
+ continue
+ }
+ var alpha0 = bA.m_sweep.alpha0;
+ if (bA.m_sweep.alpha0 < bB.m_sweep.alpha0) {
+ alpha0 = bB.m_sweep.alpha0;
+ bA.m_sweep.Advance(alpha0)
+ } else {
+ if (bB.m_sweep.alpha0 < bA.m_sweep.alpha0) {
+ alpha0 = bA.m_sweep.alpha0;
+ bB.m_sweep.Advance(alpha0)
+ }
+ }
+ var indexA = c.GetChildIndexA();
+ var indexB = c.GetChildIndexB();
+ var input = new b2TOIInput();
+ input.proxyA.Set(fA.GetShape(), indexA);
+ input.proxyB.Set(fB.GetShape(), indexB);
+ input.sweepA.Assign(bA.m_sweep);
+ input.sweepB.Assign(bB.m_sweep);
+ input.tMax = 1;
+ var output = new b2TOIOutput();
+ b2TimeOfImpact(output, input);
+ var beta = output.t;
+ if (output.state == b2TOIOutput.e_touching) {
+ alpha = b2Min(alpha0 + (1 - alpha0) * beta, 1)
+ } else {
+ alpha = 1
+ }
+ c.m_toi = alpha;
+ c.m_flags |= b2Contact.e_toiFlag
+ }
+ if (alpha < minAlpha) {
+ minContact = c;
+ minAlpha = alpha
+ }
+ }
+ if (minContact == null || 1 - 10 * b2_epsilon < minAlpha) {
+ this.m_stepComplete = true;
+ break
+ }
+ var fA = minContact.GetFixtureA();
+ var fB = minContact.GetFixtureB();
+ var bA = fA.GetBody();
+ var bB = fB.GetBody();
+ b2World.m_local_sweep_backupA.Assign(bA.m_sweep);
+ b2World.m_local_sweep_backupB.Assign(bB.m_sweep);
+ bA.Advance(minAlpha);
+ bB.Advance(minAlpha);
+ minContact.Update(this.m_contactManager.m_contactListener);
+ minContact.m_flags &= ~b2Contact.e_toiFlag;
+ ++minContact.m_toiCount;
+ if (minContact.IsEnabled() == false || minContact.IsTouching() == false) {
+ minContact.SetEnabled(false);
+ bA.m_sweep.Assign(b2World.m_local_sweep_backupA);
+ bB.m_sweep.Assign(b2World.m_local_sweep_backupB);
+ bA.SynchronizeTransform();
+ bB.SynchronizeTransform();
+ continue
+ }
+ bA.SetAwake(true);
+ bB.SetAwake(true);
+ this.p_island.Clear();
+ this.p_island.AddBody(bA);
+ this.p_island.AddBody(bB);
+ this.p_island.AddContact(minContact);
+ bA.m_flags |= b2Body.e_islandFlag;
+ bB.m_flags |= b2Body.e_islandFlag;
+ minContact.m_flags |= b2Contact.e_islandFlag;
+ var bodies = [bA, bB];
+ for (var i = 0; i < 2; ++i) {
+ var body = bodies[i];
+ if (body.m_type == b2Body.b2_dynamicBody) {
+ for (var ce = body.m_contactList; ce; ce = ce.next) {
+ if (this.p_island.m_bodyCount == this.p_island.m_bodyCapacity) {
+ break
+ }
+ if (this.p_island.m_contactCount == this.p_island.m_contactCapacity) {
+ break
+ }
+ var contact = ce.contact;
+ if (contact.m_flags & b2Contact.e_islandFlag) {
+ continue
+ }
+ var other = ce.other;
+ if (other.m_type == b2Body.b2_dynamicBody && body.IsBullet() == false && other.IsBullet() == false) {
+ continue
+ }
+ var sensorA = contact.m_fixtureA.m_isSensor;
+ var sensorB = contact.m_fixtureB.m_isSensor;
+ if (sensorA || sensorB) {
+ continue
+ }
+ b2World.m_local_sweep_backupC.Assign(other.m_sweep);
+ if ((other.m_flags & b2Body.e_islandFlag) == 0) {
+ other.Advance(minAlpha)
+ }
+ contact.Update(this.m_contactManager.m_contactListener);
+ if (contact.IsEnabled() == false) {
+ other.m_sweep.Assign(b2World.m_local_sweep_backupC);
+ other.SynchronizeTransform();
+ continue
+ }
+ if (contact.IsTouching() == false) {
+ other.m_sweep.Assign(b2World.m_local_sweep_backupC);
+ other.SynchronizeTransform();
+ continue
+ }
+ contact.m_flags |= b2Contact.e_islandFlag;
+ this.p_island.AddContact(contact);
+ if (other.m_flags & b2Body.e_islandFlag) {
+ continue
+ }
+ other.m_flags |= b2Body.e_islandFlag;
+ if (other.m_type != b2Body.b2_staticBody) {
+ other.SetAwake(true)
+ }
+ this.p_island.AddBody(other)
+ }
+ }
+ }
+ var subStep = new b2TimeStep();
+ subStep.dt = (1 - minAlpha) * step.dt;
+ subStep.inv_dt = 1 / subStep.dt;
+ subStep.dtRatio = 1;
+ subStep.positionIterations = 20;
+ subStep.velocityIterations = step.velocityIterations;
+ subStep.warmStarting = false;
+ this.p_island.SolveTOI(subStep, bA.m_islandIndex, bB.m_islandIndex);
+ for (var i = 0; i < this.p_island.m_bodyCount; ++i) {
+ var body = this.p_island.m_bodies[i];
+ body.m_flags &= ~b2Body.e_islandFlag;
+ if (body.m_type != b2Body.b2_dynamicBody) {
+ continue
+ }
+ body.SynchronizeFixtures();
+ for (var ce = body.m_contactList; ce; ce = ce.next) {
+ ce.contact.m_flags &= ~(b2Contact.e_toiFlag | b2Contact.e_islandFlag)
+ }
+ }
+ this.m_contactManager.FindNewContacts();
+ if (this.m_subStepping) {
+ this.m_stepComplete = false;
+ break
+ }
+ }
+ },
+ DrawJoint: function (joint) {
+ var bodyA = joint.GetBodyA();
+ var bodyB = joint.GetBodyB();
+ var xf1 = bodyA.GetTransform();
+ var xf2 = bodyB.GetTransform();
+ var x1 = xf1.p;
+ var x2 = xf2.p;
+ var p1 = joint.GetAnchorA();
+ var p2 = joint.GetAnchorB();
+ var color = new b2Color(0.5, 0.8, 0.8);
+ switch (joint.GetType()) {
+ case b2Joint.e_distanceJoint:
+ this.g_debugDraw.DrawSegment(p1, p2, color);
+ break;
+ case b2Joint.e_pulleyJoint:
+ var pulley = joint;
+ var s1 = pulley.GetGroundAnchorA();
+ var s2 = pulley.GetGroundAnchorB();
+ this.g_debugDraw.DrawSegment(s1, p1, color);
+ this.g_debugDraw.DrawSegment(s2, p2, color);
+ this.g_debugDraw.DrawSegment(s1, s2, color);
+ break;
+ case b2Joint.e_mouseJoint:
+ break;
+ case b2Joint.e_motorJoint:
+ this.g_debugDraw.DrawPoint(joint.GetLinearOffset(), 5, color);
+ default:
+ this.g_debugDraw.DrawSegment(x1, p1, color);
+ this.g_debugDraw.DrawSegment(p1, p2, color);
+ this.g_debugDraw.DrawSegment(x2, p2, color)
+ }
+ },
+ DrawShape: function (fixture, xf, color) {
+ switch (fixture.GetType()) {
+ case b2Shape.e_circle:
+ var circle = fixture.GetShape();
+ var center = b2Mul_t_v2(xf, circle.m_p);
+ var radius = circle.m_radius;
+ var axis = b2Mul_r_v2(xf.q, new b2Vec2(1, 0));
+ this.g_debugDraw.DrawSolidCircle(center, radius, axis, color);
+ break;
+ case b2Shape.e_edge:
+ var edge = fixture.GetShape();
+ var v1 = b2Mul_t_v2(xf, edge.m_vertex1);
+ var v2 = b2Mul_t_v2(xf, edge.m_vertex2);
+ this.g_debugDraw.DrawSegment(v1, v2, color);
+ break;
+ case b2Shape.e_chain:
+ var chain = fixture.GetShape();
+ var count = chain.m_count;
+ var vertices = chain.m_vertices;
+ var v1 = b2Mul_t_v2(xf, vertices[0]);
+ for (var i = 1; i < count; ++i) {
+ var v2 = b2Mul_t_v2(xf, vertices[i]);
+ this.g_debugDraw.DrawSegment(v1, v2, color);
+ v1 = v2
+ }
+ break;
+ case b2Shape.e_polygon:
+ var poly = fixture.GetShape();
+ var vertexCount = poly.m_count;
+ var vertices = new Array(b2_maxPolygonVertices);
+ for (var i = 0; i < vertexCount; ++i) {
+ vertices[i] = b2Mul_t_v2(xf, poly.m_vertices[i])
+ }
+ this.g_debugDraw.DrawSolidPolygon(vertices, vertexCount, color);
+ break;
+ default:
+ break
+ }
+ }
+};
+b2World.e_newFixture = 1;
+b2World.e_locked = 2;
+b2World.e_clearForces = 4;
+"use strict";
+
+function b2MixFriction(friction1, friction2) {
+ return b2Sqrt(friction1 * friction2)
+}
+
+function b2MixRestitution(restitution1, restitution2) {
+ return restitution1 > restitution2 ? restitution1 : restitution2
+}
+
+function b2ContactRegister() {
+ this.fcn = null;
+ this.primary = false
+}
+
+function b2ContactEdge() {
+ this.other = null;
+ this.contact = null;
+ this.prev = null;
+ this.next = null
+}
+
+function b2Contact() {
+ this.m_nodeA = new b2ContactEdge();
+ this.m_nodeB = new b2ContactEdge();
+ this.m_manifold = new b2Manifold()
+}
+b2Contact.m_local_tempManifold = new b2Manifold();
+b2Contact.prototype = {
+ Create: function (fA, indexA, fB, indexB) {
+ this.m_toi = 0;
+ this.m_flags = b2Contact.e_enabledFlag;
+ this.m_fixtureA = fA || null;
+ this.m_fixtureB = fB || null;
+ this.m_indexA = indexA || 0;
+ this.m_indexB = indexB || 0;
+ this.m_manifold.pointCount = 0;
+ this.m_prev = null;
+ this.m_next = null;
+ this.m_nodeA.contact = null;
+ this.m_nodeA.prev = null;
+ this.m_nodeA.next = null;
+ this.m_nodeA.other = null;
+ this.m_nodeB.contact = null;
+ this.m_nodeB.prev = null;
+ this.m_nodeB.next = null;
+ this.m_nodeB.other = null;
+ this.m_toiCount = 0;
+ if (fA) {
+ this.m_friction = b2MixFriction(this.m_fixtureA.m_friction, this.m_fixtureB.m_friction);
+ this.m_restitution = b2MixRestitution(this.m_fixtureA.m_restitution, this.m_fixtureB.m_restitution)
+ } else {
+ this.m_friction = 0;
+ this.m_restitution = 0
+ }
+ this.m_tangentSpeed = 0
+ },
+ GetManifold: function () {
+ return this.m_manifold
+ },
+ GetWorldManifold: function (worldManifold) {
+ var bodyA = this.m_fixtureA.GetBody();
+ var bodyB = this.m_fixtureB.GetBody();
+ var shapeA = this.m_fixtureA.GetShape();
+ var shapeB = this.m_fixtureB.GetShape();
+ worldManifold.Initialize(this.m_manifold, bodyA.GetTransform(), shapeA.m_radius, bodyB.GetTransform(), shapeB.m_radius)
+ },
+ IsTouching: function () {
+ return (this.m_flags & b2Contact.e_touchingFlag) == b2Contact.e_touchingFlag
+ },
+ SetEnabled: function (flag) {
+ if (flag) {
+ this.m_flags |= b2Contact.e_enabledFlag
+ } else {
+ this.m_flags &= ~b2Contact.e_enabledFlag
+ }
+ },
+ IsEnabled: function () {
+ return (this.m_flags & b2Contact.e_enabledFlag) == b2Contact.e_enabledFlag
+ },
+ GetNext: function () {
+ return this.m_next
+ },
+ GetFixtureA: function () {
+ return this.m_fixtureA
+ },
+ GetChildIndexA: function () {
+ return this.m_indexA
+ },
+ GetFixtureB: function () {
+ return this.m_fixtureB
+ },
+ GetChildIndexB: function () {
+ return this.m_indexB
+ },
+ SetFriction: function (friction) {
+ this.m_friction = friction
+ },
+ GetFriction: function () {
+ return this.m_friction
+ },
+ ResetFriction: function () {
+ this.m_friction = b2MixFriction(this.m_fixtureA.m_friction, this.m_fixtureB.m_friction)
+ },
+ SetRestitution: function (restitution) {
+ this.m_restitution = restitution
+ },
+ GetRestitution: function () {
+ return this.m_restitution
+ },
+ ResetRestitution: function () {
+ this.m_restitution = b2MixRestitution(this.m_fixtureA.m_restitution, this.m_fixtureB.m_restitution)
+ },
+ SetTangentSpeed: function (speed) {
+ this.m_tangentSpeed = speed
+ },
+ GetTangentSpeed: function () {
+ return this.m_tangentSpeed
+ },
+ Evaluate: function (manifold, xfA, xfB) {},
+ FlagForFiltering: function () {
+ this.m_flags |= b2Contact.e_filterFlag
+ },
+ m_oldManifold: null,
+ Update: function (listener) {
+ b2Contact.m_local_tempManifold.Assign(this.m_manifold);
+ this.m_flags |= b2Contact.e_enabledFlag;
+ var touching = false;
+ var wasTouching = (this.m_flags & b2Contact.e_touchingFlag) == b2Contact.e_touchingFlag;
+ var sensorA = this.m_fixtureA.IsSensor();
+ var sensorB = this.m_fixtureB.IsSensor();
+ var sensor = sensorA || sensorB;
+ var bodyA = this.m_fixtureA.GetBody();
+ var bodyB = this.m_fixtureB.GetBody();
+ var xfA = bodyA.GetTransform();
+ var xfB = bodyB.GetTransform();
+ if (sensor) {
+ var shapeA = this.m_fixtureA.GetShape();
+ var shapeB = this.m_fixtureB.GetShape();
+ touching = b2TestShapeOverlap(shapeA, this.m_indexA, shapeB, this.m_indexB, xfA, xfB);
+ this.m_manifold.pointCount = 0
+ } else {
+ this.Evaluate(this.m_manifold, xfA, xfB);
+ touching = this.m_manifold.pointCount > 0;
+ for (var i = 0; i < this.m_manifold.pointCount; ++i) {
+ var mp2 = this.m_manifold.points[i];
+ mp2.normalImpulse = 0;
+ mp2.tangentImpulse = 0;
+ var id2 = mp2.id;
+ for (var j = 0; j < b2Contact.m_local_tempManifold.pointCount; ++j) {
+ var mp1 = b2Contact.m_local_tempManifold.points[j];
+ if (mp1.id.Get() == id2.Get()) {
+ mp2.normalImpulse = mp1.normalImpulse;
+ mp2.tangentImpulse = mp1.tangentImpulse;
+ break
+ }
+ }
+ }
+ if (touching != wasTouching) {
+ bodyA.SetAwake(true);
+ bodyB.SetAwake(true)
+ }
+ }
+ if (touching) {
+ this.m_flags |= b2Contact.e_touchingFlag
+ } else {
+ this.m_flags &= ~b2Contact.e_touchingFlag
+ }
+ if (wasTouching == false && touching == true && listener) {
+ listener.BeginContact(this)
+ }
+ if (wasTouching == true && touching == false && listener) {
+ listener.EndContact(this)
+ }
+ if (sensor == false && touching && listener) {
+ listener.PreSolve(this, b2Contact.m_local_tempManifold)
+ }
+ }
+};
+b2Contact.e_islandFlag = 1;
+b2Contact.e_touchingFlag = 2;
+b2Contact.e_enabledFlag = 4;
+b2Contact.e_filterFlag = 8;
+b2Contact.e_bulletHitFlag = 16;
+b2Contact.e_toiFlag = 32;
+
+function b2CircleContact() {
+ this.parent.call(this)
+}
+b2CircleContact.prototype = {
+ Evaluate: function (manifold, xfA, xfB) {
+ b2CollideCircles(manifold, this.m_fixtureA.GetShape(), xfA, this.m_fixtureB.GetShape(), xfB)
+ },
+ Create: function (fixtureA, unused1, fixtureB, unused2) {
+ this.parent.prototype.Create.call(this, fixtureA, 0, fixtureB, 0)
+ }
+};
+b2CircleContact._extend(b2Contact);
+var _local_temp_edgeShape = new b2EdgeShape();
+
+function b2ChainAndCircleContact() {
+ this.parent.call(this)
+}
+b2ChainAndCircleContact.prototype = {
+ Evaluate: function (manifold, xfA, xfB) {
+ var chain = this.m_fixtureA.GetShape();
+ chain.GetChildEdge(_local_temp_edgeShape, this.m_indexA);
+ b2CollideEdgeAndCircle(manifold, _local_temp_edgeShape, xfA, this.m_fixtureB.GetShape(), xfB)
+ },
+ Create: function (fixtureA, indexA, fixtureB, indexB) {
+ this.parent.prototype.Create.call(this, fixtureA, indexA, fixtureB, indexB)
+ }
+};
+b2ChainAndCircleContact._extend(b2Contact);
+
+function b2ChainAndPolygonContact() {
+ this.parent.call(this)
+}
+b2ChainAndPolygonContact.prototype = {
+ Evaluate: function (manifold, xfA, xfB) {
+ var chain = this.m_fixtureA.GetShape();
+ chain.GetChildEdge(_local_temp_edgeShape, this.m_indexA);
+ b2CollideEdgeAndPolygon(manifold, _local_temp_edgeShape, xfA, this.m_fixtureB.GetShape(), xfB)
+ },
+ Create: function (fixtureA, indexA, fixtureB, indexB) {
+ this.parent.prototype.Create.call(this, fixtureA, indexA, fixtureB, indexB)
+ }
+};
+b2ChainAndPolygonContact.Create = function (fixtureA, indexA, fixtureB, indexB) {
+ return new b2ChainAndPolygonContact(fixtureA, indexA, fixtureB, indexB)
+};
+b2ChainAndPolygonContact._extend(b2Contact);
+
+function b2EdgeAndCircleContact() {
+ this.parent.call(this)
+}
+b2EdgeAndCircleContact.prototype = {
+ Evaluate: function (manifold, xfA, xfB) {
+ b2CollideEdgeAndCircle(manifold, this.m_fixtureA.GetShape(), xfA, this.m_fixtureB.GetShape(), xfB)
+ },
+ Create: function (fixtureA, indexA, fixtureB, indexB) {
+ this.parent.prototype.Create.call(this, fixtureA, 0, fixtureB, 0)
+ }
+};
+b2EdgeAndCircleContact.Create = function (fixtureA, indexA, fixtureB, indexB) {
+ return new b2EdgeAndCircleContact(fixtureA, fixtureB)
+};
+b2EdgeAndCircleContact._extend(b2Contact);
+
+function b2EdgeAndPolygonContact() {
+ this.parent.call(this)
+}
+b2EdgeAndPolygonContact.prototype = {
+ Evaluate: function (manifold, xfA, xfB) {
+ b2CollideEdgeAndPolygon(manifold, this.m_fixtureA.GetShape(), xfA, this.m_fixtureB.GetShape(), xfB)
+ },
+ Create: function (fixtureA, indexA, fixtureB, indexB) {
+ this.parent.prototype.Create.call(this, fixtureA, 0, fixtureB, 0)
+ }
+};
+b2EdgeAndPolygonContact.Create = function (fixtureA, indexA, fixtureB, indexB) {
+ return new b2EdgeAndPolygonContact(fixtureA, fixtureB)
+};
+b2EdgeAndPolygonContact._extend(b2Contact);
+
+function b2PolygonAndCircleContact() {
+ this.parent.call(this)
+}
+b2PolygonAndCircleContact.prototype = {
+ Evaluate: function (manifold, xfA, xfB) {
+ b2CollidePolygonAndCircle(manifold, this.m_fixtureA.GetShape(), xfA, this.m_fixtureB.GetShape(), xfB)
+ },
+ Create: function (fixtureA, indexA, fixtureB, indexB) {
+ this.parent.prototype.Create.call(this, fixtureA, 0, fixtureB, 0)
+ }
+};
+b2PolygonAndCircleContact.Create = function (fixtureA, indexA, fixtureB, indexB) {
+ return new b2PolygonAndCircleContact(fixtureA, fixtureB)
+};
+b2PolygonAndCircleContact._extend(b2Contact);
+
+function b2PolygonContact() {
+ this.parent.call(this)
+}
+b2PolygonContact.prototype = {
+ Evaluate: function (manifold, xfA, xfB) {
+ b2CollidePolygons(manifold, this.m_fixtureA.GetShape(), xfA, this.m_fixtureB.GetShape(), xfB)
+ },
+ Create: function (fixtureA, indexA, fixtureB, indexB) {
+ this.parent.prototype.Create.call(this, fixtureA, 0, fixtureB, 0)
+ }
+};
+b2PolygonContact.Create = function (fixtureA, indexA, fixtureB, indexB) {
+ return new b2PolygonContact(fixtureA, fixtureB)
+};
+b2PolygonContact._extend(b2Contact);
+b2Contact.AddType = function (fcn, type1, type2) {
+ if (!b2Contact.s_registers[type1]) {
+ b2Contact.s_registers[type1] = []
+ }
+ b2Contact.s_registers[type1][type2] = new b2ContactRegister();
+ b2Contact.s_registers[type1][type2].fcn = fcn;
+ b2Contact.s_registers[type1][type2].primary = true;
+ if (type1 != type2) {
+ if (!b2Contact.s_registers[type2]) {
+ b2Contact.s_registers[type2] = []
+ }
+ b2Contact.s_registers[type2][type1] = new b2ContactRegister();
+ b2Contact.s_registers[type2][type1].fcn = fcn;
+ b2Contact.s_registers[type2][type1].primary = false
+ }
+ fcn.garbage = [];
+ fcn.alloc = 2
+};
+b2Contact.InitializeRegisters = function () {
+ b2Contact.AddType(b2CircleContact, b2Shape.e_circle, b2Shape.e_circle);
+ b2Contact.AddType(b2PolygonAndCircleContact, b2Shape.e_polygon, b2Shape.e_circle);
+ b2Contact.AddType(b2PolygonContact, b2Shape.e_polygon, b2Shape.e_polygon);
+ b2Contact.AddType(b2EdgeAndCircleContact, b2Shape.e_edge, b2Shape.e_circle);
+ b2Contact.AddType(b2EdgeAndPolygonContact, b2Shape.e_edge, b2Shape.e_polygon);
+ b2Contact.AddType(b2ChainAndCircleContact, b2Shape.e_chain, b2Shape.e_circle);
+ b2Contact.AddType(b2ChainAndPolygonContact, b2Shape.e_chain, b2Shape.e_polygon)
+};
+b2Contact.RetrieveGarbage = function (fcn) {
+ var contact;
+ if (contact = fcn.garbage.pop()) {
+ return contact
+ }
+ for (var i = 0; i < fcn.alloc - 1; ++i) {
+ fcn.garbage.push(new fcn())
+ }
+ fcn.alloc += 32;
+ return new fcn()
+};
+b2Contact.Create = function (fixtureA, indexA, fixtureB, indexB) {
+ if (b2Contact.s_initialized == false) {
+ b2Contact.InitializeRegisters();
+ b2Contact.s_initialized = true
+ }
+ var type1 = fixtureA.GetType();
+ var type2 = fixtureB.GetType();
+ var fcn = b2Contact.s_registers[type1][type2].fcn;
+ if (fcn) {
+ var contact = b2Contact.RetrieveGarbage(fcn);
+ if (b2Contact.s_registers[type1][type2].primary) {
+ contact.Create(fixtureA, indexA, fixtureB, indexB)
+ } else {
+ contact.Create(fixtureB, indexB, fixtureA, indexA)
+ }
+ return contact
+ }
+ return null
+};
+b2Contact.Destroy = function (contact) {
+ var fixtureA = contact.m_fixtureA;
+ var fixtureB = contact.m_fixtureB;
+ if (contact.m_manifold.pointCount > 0 && fixtureA.IsSensor() == false && fixtureB.IsSensor() == false) {
+ fixtureA.GetBody().SetAwake(true);
+ fixtureB.GetBody().SetAwake(true)
+ }
+ var typeA = fixtureA.GetType();
+ var typeB = fixtureB.GetType();
+ b2Contact.s_registers[typeA][typeB].fcn.garbage.push(contact)
+};
+b2Contact.s_registers = [];
+b2Contact.s_initialized = false;
+"use strict";
+var b2_defaultFilter = new b2ContactFilter();
+var b2_defaultListener = new b2ContactListener();
+
+function b2ContactManager() {
+ this.m_broadPhase = new b2BroadPhase();
+ this.m_contactList = null;
+ this.m_contactCount = 0;
+ this.m_contactFilter = b2_defaultFilter;
+ this.m_contactListener = b2_defaultListener
+}
+b2ContactManager.prototype = {
+ AddPair: function (proxyUserDataA, proxyUserDataB) {
+ var proxyA = proxyUserDataA;
+ var proxyB = proxyUserDataB;
+ var fixtureA = proxyA.fixture;
+ var fixtureB = proxyB.fixture;
+ var indexA = proxyA.childIndex;
+ var indexB = proxyB.childIndex;
+ var bodyA = fixtureA.GetBody();
+ var bodyB = fixtureB.GetBody();
+ if (bodyA == bodyB) {
+ return
+ }
+ var edge = bodyB.GetContactList();
+ while (edge) {
+ if (edge.other == bodyA) {
+ var fA = edge.contact.GetFixtureA();
+ var fB = edge.contact.GetFixtureB();
+ var iA = edge.contact.GetChildIndexA();
+ var iB = edge.contact.GetChildIndexB();
+ if (fA == fixtureA && fB == fixtureB && iA == indexA && iB == indexB) {
+ return
+ }
+ if (fA == fixtureB && fB == fixtureA && iA == indexB && iB == indexA) {
+ return
+ }
+ }
+ edge = edge.next
+ }
+ if (bodyB.ShouldCollide(bodyA) == false) {
+ return
+ }
+ if (this.m_contactFilter && this.m_contactFilter.ShouldCollide(fixtureA, fixtureB) == false) {
+ return
+ }
+ var c = b2Contact.Create(fixtureA, indexA, fixtureB, indexB);
+ if (c == null) {
+ return
+ }
+ fixtureA = c.GetFixtureA();
+ fixtureB = c.GetFixtureB();
+ indexA = c.GetChildIndexA();
+ indexB = c.GetChildIndexB();
+ bodyA = fixtureA.GetBody();
+ bodyB = fixtureB.GetBody();
+ c.m_prev = null;
+ c.m_next = this.m_contactList;
+ if (this.m_contactList != null) {
+ this.m_contactList.m_prev = c
+ }
+ this.m_contactList = c;
+ c.m_nodeA.contact = c;
+ c.m_nodeA.other = bodyB;
+ c.m_nodeA.prev = null;
+ c.m_nodeA.next = bodyA.m_contactList;
+ if (bodyA.m_contactList != null) {
+ bodyA.m_contactList.prev = c.m_nodeA
+ }
+ bodyA.m_contactList = c.m_nodeA;
+ c.m_nodeB.contact = c;
+ c.m_nodeB.other = bodyA;
+ c.m_nodeB.prev = null;
+ c.m_nodeB.next = bodyB.m_contactList;
+ if (bodyB.m_contactList != null) {
+ bodyB.m_contactList.prev = c.m_nodeB
+ }
+ bodyB.m_contactList = c.m_nodeB;
+ if (fixtureA.IsSensor() == false && fixtureB.IsSensor() == false) {
+ bodyA.SetAwake(true);
+ bodyB.SetAwake(true)
+ }++this.m_contactCount
+ },
+ FindNewContacts: function () {
+ this.m_broadPhase.UpdatePairs(this)
+ },
+ Destroy: function (c) {
+ var fixtureA = c.GetFixtureA();
+ var fixtureB = c.GetFixtureB();
+ var bodyA = fixtureA.GetBody();
+ var bodyB = fixtureB.GetBody();
+ if (this.m_contactListener && c.IsTouching()) {
+ this.m_contactListener.EndContact(c)
+ }
+ if (c.m_prev) {
+ c.m_prev.m_next = c.m_next
+ }
+ if (c.m_next) {
+ c.m_next.m_prev = c.m_prev
+ }
+ if (c == this.m_contactList) {
+ this.m_contactList = c.m_next
+ }
+ if (c.m_nodeA.prev) {
+ c.m_nodeA.prev.next = c.m_nodeA.next
+ }
+ if (c.m_nodeA.next) {
+ c.m_nodeA.next.prev = c.m_nodeA.prev
+ }
+ if (c.m_nodeA == bodyA.m_contactList) {
+ bodyA.m_contactList = c.m_nodeA.next
+ }
+ if (c.m_nodeB.prev) {
+ c.m_nodeB.prev.next = c.m_nodeB.next
+ }
+ if (c.m_nodeB.next) {
+ c.m_nodeB.next.prev = c.m_nodeB.prev
+ }
+ if (c.m_nodeB == bodyB.m_contactList) {
+ bodyB.m_contactList = c.m_nodeB.next
+ }
+ b2Contact.Destroy(c);
+ --this.m_contactCount
+ },
+ Collide: function () {
+ var c = this.m_contactList;
+ while (c) {
+ var fixtureA = c.GetFixtureA();
+ var fixtureB = c.GetFixtureB();
+ var indexA = c.GetChildIndexA();
+ var indexB = c.GetChildIndexB();
+ var bodyA = fixtureA.GetBody();
+ var bodyB = fixtureB.GetBody();
+ if (c.m_flags & b2Contact.e_filterFlag) {
+ if (bodyB.ShouldCollide(bodyA) == false) {
+ var cNuke = c;
+ c = cNuke.GetNext();
+ this.Destroy(cNuke);
+ continue
+ }
+ if (this.m_contactFilter && this.m_contactFilter.ShouldCollide(fixtureA, fixtureB) == false) {
+ var cNuke = c;
+ c = cNuke.GetNext();
+ this.Destroy(cNuke);
+ continue
+ }
+ c.m_flags &= ~b2Contact.e_filterFlag
+ }
+ var activeA = bodyA.IsAwake() && bodyA.m_type != b2Body.b2_staticBody;
+ var activeB = bodyB.IsAwake() && bodyB.m_type != b2Body.b2_staticBody;
+ if (activeA == false && activeB == false) {
+ c = c.GetNext();
+ continue
+ }
+ var proxyIdA = fixtureA.m_proxies[indexA].proxyId;
+ var proxyIdB = fixtureB.m_proxies[indexB].proxyId;
+ var overlap = this.m_broadPhase.TestOverlap(proxyIdA, proxyIdB);
+ if (overlap == false) {
+ var cNuke = c;
+ c = cNuke.GetNext();
+ this.Destroy(cNuke);
+ continue
+ }
+ c.Update(this.m_contactListener);
+ c = c.GetNext()
+ }
+ }
+};
+
+function b2VelocityConstraintPoint() {
+ this.rA = new b2Vec2();
+ this.rB = new b2Vec2();
+ this.normalImpulse = 0;
+ this.tangentImpulse = 0;
+ this.normalMass = 0;
+ this.tangentMass = 0;
+ this.velocityBias = 0
+}
+
+function b2ContactPositionConstraint() {
+ this.localPoints = new Array(b2_maxManifoldPoints);
+ this.localNormal = new b2Vec2();
+ this.localPoint = new b2Vec2();
+ this.indexA = 0;
+ this.indexB = 0;
+ this.invMassA = 0, this.invMassB = 0;
+ this.localCenterA = new b2Vec2(), this.localCenterB = new b2Vec2();
+ this.invIA = 0, this.invIB = 0;
+ this.type = 0;
+ this.radiusA = 0, this.radiusB = 0;
+ this.pointCount = 0
+}
+
+function b2ContactVelocityConstraint() {
+ this.points = new Array(b2_maxManifoldPoints);
+ for (var i = 0; i < this.points.length; ++i) {
+ this.points[i] = new b2VelocityConstraintPoint()
+ }
+ this.normal = new b2Vec2();
+ this.normalMass = new b2Mat22();
+ this.K = new b2Mat22();
+ this.indexA = 0;
+ this.indexB = 0;
+ this.invMassA = 0, this.invMassB = 0;
+ this.invIA = 0, this.invIB = 0;
+ this.friction = 0;
+ this.restitution = 0;
+ this.tangentSpeed = 0;
+ this.pointCount = 0;
+ this.contactIndex = 0
+}
+
+function b2PositionSolverManifold() {
+ this.normal = new b2Vec2();
+ this.point = new b2Vec2();
+ this.separation = 0
+}
+b2PositionSolverManifold.prototype = {
+ Initialize: function (pc, xfA, xfB, index) {
+ switch (pc.type) {
+ case b2Manifold.e_circles:
+ var pointAx = (xfA.q.c * pc.localPoint.x - xfA.q.s * pc.localPoint.y) + xfA.p.x;
+ var pointAy = (xfA.q.s * pc.localPoint.x + xfA.q.c * pc.localPoint.y) + xfA.p.y;
+ var pointBx = (xfB.q.c * pc.localPoints[0].x - xfB.q.s * pc.localPoints[0].y) + xfB.p.x;
+ var pointBy = (xfB.q.s * pc.localPoints[0].x + xfB.q.c * pc.localPoints[0].y) + xfB.p.y;
+ this.point.x = 0.5 * (pointAx + pointBx);
+ this.point.y = 0.5 * (pointAy + pointBy);
+ this.normal.x = pointBx - pointAx;
+ this.normal.y = pointBy - pointAy;
+ var tempnx = this.normal.x;
+ var tempny = this.normal.y;
+ this.normal.Normalize();
+ this.separation = (tempnx * this.normal.x + tempny * this.normal.y) - pc.radiusA - pc.radiusB;
+ break;
+ case b2Manifold.e_faceA:
+ this.normal.x = xfA.q.c * pc.localNormal.x - xfA.q.s * pc.localNormal.y;
+ this.normal.y = xfA.q.s * pc.localNormal.x + xfA.q.c * pc.localNormal.y;
+ var planePointx = (xfA.q.c * pc.localPoint.x - xfA.q.s * pc.localPoint.y) + xfA.p.x;
+ var planePointy = (xfA.q.s * pc.localPoint.x + xfA.q.c * pc.localPoint.y) + xfA.p.y;
+ var clipPointx = (xfB.q.c * pc.localPoints[index].x - xfB.q.s * pc.localPoints[index].y) + xfB.p.x;
+ var clipPointy = (xfB.q.s * pc.localPoints[index].x + xfB.q.c * pc.localPoints[index].y) + xfB.p.y;
+ this.separation = ((clipPointx - planePointx) * this.normal.x + (clipPointy - planePointy) * this.normal.y) - pc.radiusA - pc.radiusB;
+ this.point.x = clipPointx;
+ this.point.y = clipPointy;
+ break;
+ case b2Manifold.e_faceB:
+ this.normal.x = xfB.q.c * pc.localNormal.x - xfB.q.s * pc.localNormal.y;
+ this.normal.y = xfB.q.s * pc.localNormal.x + xfB.q.c * pc.localNormal.y;
+ var planePointx = (xfB.q.c * pc.localPoint.x - xfB.q.s * pc.localPoint.y) + xfB.p.x;
+ var planePointy = (xfB.q.s * pc.localPoint.x + xfB.q.c * pc.localPoint.y) + xfB.p.y;
+ var clipPointx = (xfA.q.c * pc.localPoints[index].x - xfA.q.s * pc.localPoints[index].y) + xfA.p.x;
+ var clipPointy = (xfA.q.s * pc.localPoints[index].x + xfA.q.c * pc.localPoints[index].y) + xfA.p.y;
+ this.separation = ((clipPointx - planePointx) * this.normal.x + (clipPointy - planePointy) * this.normal.y) - pc.radiusA - pc.radiusB;
+ this.point.x = clipPointx;
+ this.point.y = clipPointy;
+ this.normal.x = -this.normal.x;
+ this.normal.y = -this.normal.y;
+ break
+ }
+ }
+};
+
+function b2ContactSolverDef() {
+ this.step = new b2TimeStep();
+ this.contacts = null;
+ this.count = 0;
+ this.positions = null;
+ this.velocities = null
+}
+
+function b2ContactSolver() {
+ this.m_positionConstraints = [];
+ this.m_velocityConstraints = []
+}
+b2ContactSolver.cs_xfA = new b2Transform();
+b2ContactSolver.cs_xfB = new b2Transform();
+b2ContactSolver.temp_solver_manifold = new b2PositionSolverManifold();
+b2ContactSolver.prototype = {
+ Init: function (def) {
+ this.m_step = def.step;
+ this.m_count = def.count;
+ this.m_positionConstraints.length = this.m_count;
+ this.m_velocityConstraints.length = this.m_count;
+ this.m_positions = def.positions;
+ this.m_velocities = def.velocities;
+ this.m_contacts = def.contacts;
+ for (var i = 0; i < this.m_count; ++i) {
+ var contact = this.m_contacts[i];
+ var fixtureA = contact.m_fixtureA;
+ var fixtureB = contact.m_fixtureB;
+ var shapeA = fixtureA.GetShape();
+ var shapeB = fixtureB.GetShape();
+ var radiusA = shapeA.m_radius;
+ var radiusB = shapeB.m_radius;
+ var bodyA = fixtureA.GetBody();
+ var bodyB = fixtureB.GetBody();
+ var manifold = contact.GetManifold();
+ var pointCount = manifold.pointCount;
+ var vc = this.m_velocityConstraints[i] || new b2ContactVelocityConstraint();
+ vc.friction = contact.m_friction;
+ vc.restitution = contact.m_restitution;
+ vc.tangentSpeed = contact.m_tangentSpeed;
+ vc.indexA = bodyA.m_islandIndex;
+ vc.indexB = bodyB.m_islandIndex;
+ vc.invMassA = bodyA.m_invMass;
+ vc.invMassB = bodyB.m_invMass;
+ vc.invIA = bodyA.m_invI;
+ vc.invIB = bodyB.m_invI;
+ vc.contactIndex = i;
+ vc.pointCount = pointCount;
+ vc.K.SetZero();
+ vc.normalMass.SetZero();
+ this.m_velocityConstraints[i] = vc;
+ var pc = this.m_positionConstraints[i] || new b2ContactPositionConstraint();
+ pc.indexA = bodyA.m_islandIndex;
+ pc.indexB = bodyB.m_islandIndex;
+ pc.invMassA = bodyA.m_invMass;
+ pc.invMassB = bodyB.m_invMass;
+ pc.localCenterA.x = bodyA.m_sweep.localCenter.x;
+ pc.localCenterA.y = bodyA.m_sweep.localCenter.y;
+ pc.localCenterB.x = bodyB.m_sweep.localCenter.x;
+ pc.localCenterB.y = bodyB.m_sweep.localCenter.y;
+ pc.invIA = bodyA.m_invI;
+ pc.invIB = bodyB.m_invI;
+ pc.localNormal.x = manifold.localNormal.x;
+ pc.localNormal.y = manifold.localNormal.y;
+ pc.localPoint.x = manifold.localPoint.x;
+ pc.localPoint.y = manifold.localPoint.y;
+ pc.pointCount = pointCount;
+ pc.radiusA = radiusA;
+ pc.radiusB = radiusB;
+ pc.type = manifold.type;
+ this.m_positionConstraints[i] = pc;
+ for (var j = 0; j < pointCount; ++j) {
+ var cp = manifold.points[j];
+ var vcp = vc.points[j];
+ if (this.m_step.warmStarting) {
+ vcp.normalImpulse = this.m_step.dtRatio * cp.normalImpulse;
+ vcp.tangentImpulse = this.m_step.dtRatio * cp.tangentImpulse
+ } else {
+ vcp.normalImpulse = 0;
+ vcp.tangentImpulse = 0
+ }
+ vcp.rA.SetZero();
+ vcp.rB.SetZero();
+ vcp.normalMass = 0;
+ vcp.tangentMass = 0;
+ vcp.velocityBias = 0;
+ pc.localPoints[j] = cp.localPoint
+ }
+ }
+ },
+ InitializeVelocityConstraints: function () {
+ for (var i = 0; i < this.m_count; ++i) {
+ var vc = this.m_velocityConstraints[i];
+ var pc = this.m_positionConstraints[i];
+ var radiusA = pc.radiusA;
+ var radiusB = pc.radiusB;
+ var manifold = this.m_contacts[vc.contactIndex].GetManifold();
+ var indexA = vc.indexA;
+ var indexB = vc.indexB;
+ var mA = vc.invMassA;
+ var mB = vc.invMassB;
+ var iA = vc.invIA;
+ var iB = vc.invIB;
+ var localCenterA = pc.localCenterA;
+ var localCenterB = pc.localCenterB;
+ var cA = this.m_positions[indexA].c;
+ var aA = this.m_positions[indexA].a;
+ var vA = this.m_velocities[indexA].v;
+ var wA = this.m_velocities[indexA].w;
+ var cB = this.m_positions[indexB].c;
+ var aB = this.m_positions[indexB].a;
+ var vB = this.m_velocities[indexB].v;
+ var wB = this.m_velocities[indexB].w;
+ b2ContactSolver.cs_xfA.q.Set(aA);
+ b2ContactSolver.cs_xfB.q.Set(aB);
+ b2ContactSolver.cs_xfA.p.x = cA.x - (b2ContactSolver.cs_xfA.q.c * localCenterA.x - b2ContactSolver.cs_xfA.q.s * localCenterA.y);
+ b2ContactSolver.cs_xfA.p.y = cA.y - (b2ContactSolver.cs_xfA.q.s * localCenterA.x + b2ContactSolver.cs_xfA.q.c * localCenterA.y);
+ b2ContactSolver.cs_xfB.p.x = cB.x - (b2ContactSolver.cs_xfB.q.c * localCenterB.x - b2ContactSolver.cs_xfB.q.s * localCenterB.y);
+ b2ContactSolver.cs_xfB.p.y = cB.y - (b2ContactSolver.cs_xfB.q.s * localCenterB.x + b2ContactSolver.cs_xfB.q.c * localCenterB.y);
+ var worldManifold = new b2WorldManifold();
+ worldManifold.Initialize(manifold, b2ContactSolver.cs_xfA, radiusA, b2ContactSolver.cs_xfB, radiusB);
+ vc.normal.x = worldManifold.normal.x;
+ vc.normal.y = worldManifold.normal.y;
+ var pointCount = vc.pointCount;
+ for (var j = 0; j < pointCount; ++j) {
+ var vcp = vc.points[j];
+ vcp.rA.x = worldManifold.points[j].x - cA.x;
+ vcp.rA.y = worldManifold.points[j].y - cA.y;
+ vcp.rB.x = worldManifold.points[j].x - cB.x;
+ vcp.rB.y = worldManifold.points[j].y - cB.y;
+ var rnA = vcp.rA.x * vc.normal.y - vcp.rA.y * vc.normal.x;
+ var rnB = vcp.rB.x * vc.normal.y - vcp.rB.y * vc.normal.x;
+ var kNormal = mA + mB + iA * rnA * rnA + iB * rnB * rnB;
+ vcp.normalMass = kNormal > 0 ? 1 / kNormal : 0;
+ var tangentx = 1 * vc.normal.y;
+ var tangenty = -1 * vc.normal.x;
+ var rtA = vcp.rA.x * tangenty - vcp.rA.y * tangentx;
+ var rtB = vcp.rB.x * tangenty - vcp.rB.y * tangentx;
+ var kTangent = mA + mB + iA * rtA * rtA + iB * rtB * rtB;
+ vcp.tangentMass = kTangent > 0 ? 1 / kTangent : 0;
+ vcp.velocityBias = 0;
+ var vRel = vc.normal.x * (((vB.x + (-wB * vcp.rB.y)) - vA.x) - (-wA * vcp.rA.y)) + vc.normal.y * (((vB.y + (wB * vcp.rB.x)) - vA.y) - (wA * vcp.rA.x));
+ if (vRel < -b2_velocityThreshold) {
+ vcp.velocityBias = -vc.restitution * vRel
+ }
+ }
+ if (vc.pointCount == 2) {
+ var vcp1 = vc.points[0];
+ var vcp2 = vc.points[1];
+ var rn1A = vcp1.rA.x * vc.normal.y - vcp1.rA.y * vc.normal.x;
+ var rn1B = vcp1.rB.x * vc.normal.y - vcp1.rB.y * vc.normal.x;
+ var rn2A = vcp2.rA.x * vc.normal.y - vcp2.rA.y * vc.normal.x;
+ var rn2B = vcp2.rB.x * vc.normal.y - vcp2.rB.y * vc.normal.x;
+ var k11 = mA + mB + iA * rn1A * rn1A + iB * rn1B * rn1B;
+ var k22 = mA + mB + iA * rn2A * rn2A + iB * rn2B * rn2B;
+ var k12 = mA + mB + iA * rn1A * rn2A + iB * rn1B * rn2B;
+ var k_maxConditionNumber = 1000;
+ if (k11 * k11 < k_maxConditionNumber * (k11 * k22 - k12 * k12)) {
+ vc.K.ex.x = k11;
+ vc.K.ex.y = k12;
+ vc.K.ey.x = k12;
+ vc.K.ey.y = k22;
+ vc.normalMass.Assign(vc.K.GetInverse())
+ } else {
+ vc.pointCount = 1
+ }
+ }
+ }
+ },
+ WarmStart: function () {
+ for (var i = 0; i < this.m_count; ++i) {
+ var vc = this.m_velocityConstraints[i];
+ var indexA = vc.indexA;
+ var indexB = vc.indexB;
+ var mA = vc.invMassA;
+ var iA = vc.invIA;
+ var mB = vc.invMassB;
+ var iB = vc.invIB;
+ var pointCount = vc.pointCount;
+ var vA = this.m_velocities[indexA].v;
+ var wA = this.m_velocities[indexA].w;
+ var vB = this.m_velocities[indexB].v;
+ var wB = this.m_velocities[indexB].w;
+ var normal = vc.normal;
+ var tangentx = 1 * normal.y;
+ var tangenty = -1 * normal.x;
+ for (var j = 0; j < pointCount; ++j) {
+ var vcp = vc.points[j];
+ var Px = (vcp.normalImpulse * normal.x) + (vcp.tangentImpulse * tangentx);
+ var Py = (vcp.normalImpulse * normal.y) + (vcp.tangentImpulse * tangenty);
+ wA -= iA * (vcp.rA.x * Py - vcp.rA.y * Px);
+ vA.x -= mA * Px;
+ vA.y -= mA * Py;
+ wB += iB * (vcp.rB.x * Py - vcp.rB.y * Px);
+ vB.x += mB * Px;
+ vB.y += mB * Py
+ }
+ this.m_velocities[indexA].w = wA;
+ this.m_velocities[indexB].w = wB
+ }
+ },
+ SolveVelocityConstraints: function () {
+ for (var i = 0; i < this.m_count; ++i) {
+ var vc = this.m_velocityConstraints[i];
+ var indexA = vc.indexA;
+ var indexB = vc.indexB;
+ var mA = vc.invMassA;
+ var iA = vc.invIA;
+ var mB = vc.invMassB;
+ var iB = vc.invIB;
+ var pointCount = vc.pointCount;
+ var vA = this.m_velocities[indexA].v;
+ var wA = this.m_velocities[indexA].w;
+ var vB = this.m_velocities[indexB].v;
+ var wB = this.m_velocities[indexB].w;
+ var normal = vc.normal;
+ var tangentx = 1 * normal.y;
+ var tangenty = -1 * normal.x;
+ var friction = vc.friction;
+ for (var j = 0; j < pointCount; ++j) {
+ var vcp = vc.points[j];
+ var dvx = vB.x + (-wB * vcp.rB.y) - vA.x - (-wA * vcp.rA.y);
+ var dvy = vB.y + (wB * vcp.rB.x) - vA.y - (wA * vcp.rA.x);
+ var vt = (dvx * tangentx + dvy * tangenty) - vc.tangentSpeed;
+ var lambda = vcp.tangentMass * (-vt);
+ var maxFriction = friction * vcp.normalImpulse;
+ var newImpulse = b2Clamp(vcp.tangentImpulse + lambda, -maxFriction, maxFriction);
+ lambda = newImpulse - vcp.tangentImpulse;
+ vcp.tangentImpulse = newImpulse;
+ var Px = lambda * tangentx;
+ var Py = lambda * tangenty;
+ vA.x -= mA * Px;
+ vA.y -= mA * Py;
+ wA -= iA * (vcp.rA.x * Py - vcp.rA.y * Px);
+ vB.x += mB * Px;
+ vB.y += mB * Py;
+ wB += iB * (vcp.rB.x * Py - vcp.rB.y * Px)
+ }
+ if (vc.pointCount == 1) {
+ vcp = vc.points[0];
+ dvx = vB.x + (-wB * vcp.rB.y) - vA.x - (-wA * vcp.rA.y);
+ dvy = vB.y + (wB * vcp.rB.x) - vA.y - (wA * vcp.rA.x);
+ var vn = dvx * normal.x + dvy * normal.y;
+ var lambda = -vcp.normalMass * (vn - vcp.velocityBias);
+ var newImpulse = b2Max(vcp.normalImpulse + lambda, 0);
+ lambda = newImpulse - vcp.normalImpulse;
+ vcp.normalImpulse = newImpulse;
+ Px = lambda * normal.x;
+ Py = lambda * normal.y;
+ vA.x -= mA * Px;
+ vA.y -= mA * Py;
+ wA -= iA * (vcp.rA.x * Py - vcp.rA.y * Px);
+ vB.x += mB * Px;
+ vB.y += mB * Py;
+ wB += iB * (vcp.rB.x * Py - vcp.rB.y * Px)
+ } else {
+ var cp1 = vc.points[0];
+ var cp2 = vc.points[1];
+ var ax = cp1.normalImpulse;
+ var ay = cp2.normalImpulse;
+ var dv1x = vB.x + (-wB * cp1.rB.y) - vA.x - (-wA * cp1.rA.y);
+ var dv1y = vB.y + (wB * cp1.rB.x) - vA.y - (wA * cp1.rA.x);
+ var dv2x = vB.x + (-wB * cp2.rB.y) - vA.x - (-wA * cp2.rA.y);
+ var dv2y = vB.y + (wB * cp2.rB.x) - vA.y - (wA * cp2.rA.x);
+ var vn1 = dv1x * normal.x + dv1y * normal.y;
+ var vn2 = dv2x * normal.x + dv2y * normal.y;
+ var bx = vn1 - cp1.velocityBias;
+ var by = vn2 - cp2.velocityBias;
+ bx -= vc.K.ex.x * ax + vc.K.ey.x * ay;
+ by -= vc.K.ex.y * ax + vc.K.ey.y * ay;
+ for (;;) {
+ var xx = -(vc.normalMass.ex.x * bx + vc.normalMass.ey.x * by);
+ var xy = -(vc.normalMass.ex.y * bx + vc.normalMass.ey.y * by);
+ if (xx >= 0 && xy >= 0) {
+ var dx = xx - ax;
+ var dy = xy - ay;
+ var P1x = dx * normal.x;
+ var P1y = dx * normal.y;
+ var P2x = dy * normal.x;
+ var P2y = dy * normal.y;
+ vA.x -= mA * (P1x + P2x);
+ vA.y -= mA * (P1y + P2y);
+ wA -= iA * ((cp1.rA.x * P1y - cp1.rA.y * P1x) + (cp2.rA.x * P2y - cp2.rA.y * P2x));
+ vB.x += mB * (P1x + P2x);
+ vB.y += mB * (P1y + P2y);
+ wB += iB * ((cp1.rB.x * P1y - cp1.rB.y * P1x) + (cp2.rB.x * P2y - cp2.rB.y * P2x));
+ cp1.normalImpulse = xx;
+ cp2.normalImpulse = xy;
+ break
+ }
+ xx = -cp1.normalMass * bx;
+ xy = 0;
+ vn1 = 0;
+ vn2 = vc.K.ex.y * xx + by;
+ if (xx >= 0 && vn2 >= 0) {
+ dx = xx - ax;
+ dy = xy - ay;
+ P1x = dx * normal.x;
+ P1y = dx * normal.y;
+ P2x = dy * normal.x;
+ P2y = dy * normal.y;
+ vA.x -= mA * (P1x + P2x);
+ vA.y -= mA * (P1y + P2y);
+ wA -= iA * ((cp1.rA.x * P1y - cp1.rA.y * P1x) + (cp2.rA.x * P2y - cp2.rA.y * P2x));
+ vB.x += mB * (P1x + P2x);
+ vB.y += mB * (P1y + P2y);
+ wB += iB * ((cp1.rB.x * P1y - cp1.rB.y * P1x) + (cp2.rB.x * P2y - cp2.rB.y * P2x));
+ cp1.normalImpulse = xx;
+ cp2.normalImpulse = xy;
+ break
+ }
+ xx = 0;
+ xy = -cp2.normalMass * by;
+ vn1 = vc.K.ey.x * xy + bx;
+ vn2 = 0;
+ if (xy >= 0 && vn1 >= 0) {
+ dx = xx - ax;
+ dy = xy - ay;
+ P1x = dx * normal.x;
+ P1y = dx * normal.y;
+ P2x = dy * normal.x;
+ P2y = dy * normal.y;
+ vA.x -= mA * (P1x + P2x);
+ vA.y -= mA * (P1y + P2y);
+ wA -= iA * ((cp1.rA.x * P1y - cp1.rA.y * P1x) + (cp2.rA.x * P2y - cp2.rA.y * P2x));
+ vB.x += mB * (P1x + P2x);
+ vB.y += mB * (P1y + P2y);
+ wB += iB * ((cp1.rB.x * P1y - cp1.rB.y * P1x) + (cp2.rB.x * P2y - cp2.rB.y * P2x));
+ cp1.normalImpulse = xx;
+ cp2.normalImpulse = xy;
+ break
+ }
+ xx = 0;
+ xy = 0;
+ vn1 = bx;
+ vn2 = by;
+ if (vn1 >= 0 && vn2 >= 0) {
+ dx = xx - ax;
+ dy = xy - ay;
+ P1x = dx * normal.x;
+ P1y = dx * normal.y;
+ P2x = dy * normal.x;
+ P2y = dy * normal.y;
+ vA.x -= mA * (P1x + P2x);
+ vA.y -= mA * (P1y + P2y);
+ wA -= iA * ((cp1.rA.x * P1y - cp1.rA.y * P1x) + (cp2.rA.x * P2y - cp2.rA.y * P2x));
+ vB.x += mB * (P1x + P2x);
+ vB.y += mB * (P1y + P2y);
+ wB += iB * ((cp1.rB.x * P1y - cp1.rB.y * P1x) + (cp2.rB.x * P2y - cp2.rB.y * P2x));
+ cp1.normalImpulse = xx;
+ cp2.normalImpulse = xy;
+ break
+ }
+ break
+ }
+ }
+ this.m_velocities[indexA].w = wA;
+ this.m_velocities[indexB].w = wB
+ }
+ },
+ StoreImpulses: function () {
+ for (var i = 0; i < this.m_count; ++i) {
+ var vc = this.m_velocityConstraints[i];
+ var manifold = this.m_contacts[vc.contactIndex].GetManifold();
+ for (var j = 0; j < vc.pointCount; ++j) {
+ manifold.points[j].normalImpulse = vc.points[j].normalImpulse;
+ manifold.points[j].tangentImpulse = vc.points[j].tangentImpulse
+ }
+ }
+ },
+ SolvePositionConstraints: function () {
+ var minSeparation = 0;
+ for (var i = 0; i < this.m_count; ++i) {
+ var pc = this.m_positionConstraints[i];
+ var indexA = pc.indexA;
+ var indexB = pc.indexB;
+ var localCenterA = pc.localCenterA;
+ var mA = pc.invMassA;
+ var iA = pc.invIA;
+ var localCenterB = pc.localCenterB;
+ var mB = pc.invMassB;
+ var iB = pc.invIB;
+ var pointCount = pc.pointCount;
+ var cA = this.m_positions[indexA].c;
+ var aA = this.m_positions[indexA].a;
+ var cB = this.m_positions[indexB].c;
+ var aB = this.m_positions[indexB].a;
+ for (var j = 0; j < pointCount; ++j) {
+ b2ContactSolver.cs_xfA.q.Set(aA);
+ b2ContactSolver.cs_xfB.q.Set(aB);
+ b2ContactSolver.cs_xfA.p.x = cA.x - (b2ContactSolver.cs_xfA.q.c * localCenterA.x - b2ContactSolver.cs_xfA.q.s * localCenterA.y);
+ b2ContactSolver.cs_xfA.p.y = cA.y - (b2ContactSolver.cs_xfA.q.s * localCenterA.x + b2ContactSolver.cs_xfA.q.c * localCenterA.y);
+ b2ContactSolver.cs_xfB.p.x = cB.x - (b2ContactSolver.cs_xfB.q.c * localCenterB.x - b2ContactSolver.cs_xfB.q.s * localCenterB.y);
+ b2ContactSolver.cs_xfB.p.y = cB.y - (b2ContactSolver.cs_xfB.q.s * localCenterB.x + b2ContactSolver.cs_xfB.q.c * localCenterB.y);
+ b2ContactSolver.temp_solver_manifold.Initialize(pc, b2ContactSolver.cs_xfA, b2ContactSolver.cs_xfB, j);
+ var normal = b2ContactSolver.temp_solver_manifold.normal;
+ var point = b2ContactSolver.temp_solver_manifold.point;
+ var separation = b2ContactSolver.temp_solver_manifold.separation;
+ var rAx = point.x - cA.x;
+ var rAy = point.y - cA.y;
+ var rBx = point.x - cB.x;
+ var rBy = point.y - cB.y;
+ minSeparation = b2Min(minSeparation, separation);
+ var C = b2Clamp(b2_baumgarte * (separation + b2_linearSlop), -b2_maxLinearCorrection, 0);
+ var rnA = rAx * normal.y - rAy * normal.x;
+ var rnB = rBx * normal.y - rBy * normal.x;
+ var K = mA + mB + iA * rnA * rnA + iB * rnB * rnB;
+ var impulse = K > 0 ? -C / K : 0;
+ var Px = impulse * normal.x;
+ var Py = impulse * normal.y;
+ cA.x -= mA * Px;
+ cA.y -= mA * Py;
+ aA -= iA * (rAx * Py - rAy * Px);
+ cB.x += mB * Px;
+ cB.y += mB * Py;
+ aB += iB * (rBx * Py - rBy * Px)
+ }
+ this.m_positions[indexA].a = aA;
+ this.m_positions[indexB].a = aB
+ }
+ return minSeparation >= -3 * b2_linearSlop
+ },
+ SolveTOIPositionConstraints: function (toiIndexA, toiIndexB) {
+ var minSeparation = 0;
+ for (var i = 0; i < this.m_count; ++i) {
+ var pc = this.m_positionConstraints[i];
+ var indexA = pc.indexA;
+ var indexB = pc.indexB;
+ var localCenterA = pc.localCenterA;
+ var localCenterB = pc.localCenterB;
+ var pointCount = pc.pointCount;
+ var mA = 0;
+ var iA = 0;
+ if (indexA == toiIndexA || indexA == toiIndexB) {
+ mA = pc.invMassA;
+ iA = pc.invIA
+ }
+ var mB = 0;
+ var iB = 0;
+ if (indexB == toiIndexA || indexB == toiIndexB) {
+ mB = pc.invMassB;
+ iB = pc.invIB
+ }
+ var cA = this.m_positions[indexA].c;
+ var aA = this.m_positions[indexA].a;
+ var cB = this.m_positions[indexB].c;
+ var aB = this.m_positions[indexB].a;
+ for (var j = 0; j < pointCount; ++j) {
+ b2ContactSolver.cs_xfA.q.Set(aA);
+ b2ContactSolver.cs_xfB.q.Set(aB);
+ b2ContactSolver.cs_xfA.p.Assign(b2Vec2.Subtract(cA, b2Mul_r_v2(b2ContactSolver.cs_xfA.q, localCenterA)));
+ b2ContactSolver.cs_xfB.p.Assign(b2Vec2.Subtract(cB, b2Mul_r_v2(b2ContactSolver.cs_xfB.q, localCenterB)));
+ b2ContactSolver.temp_solver_manifold.Initialize(pc, b2ContactSolver.cs_xfA, b2ContactSolver.cs_xfB, j);
+ var normal = b2ContactSolver.temp_solver_manifold.normal;
+ var point = b2ContactSolver.temp_solver_manifold.point;
+ var separation = b2ContactSolver.temp_solver_manifold.separation;
+ var rA = b2Vec2.Subtract(point, cA);
+ var rB = b2Vec2.Subtract(point, cB);
+ minSeparation = b2Min(minSeparation, separation);
+ var C = b2Clamp(b2_toiBaugarte * (separation + b2_linearSlop), -b2_maxLinearCorrection, 0);
+ var rnA = b2Cross_v2_v2(rA, normal);
+ var rnB = b2Cross_v2_v2(rB, normal);
+ var K = mA + mB + iA * rnA * rnA + iB * rnB * rnB;
+ var impulse = K > 0 ? -C / K : 0;
+ var P = b2Vec2.Multiply(impulse, normal);
+ cA.Subtract(b2Vec2.Multiply(mA, P));
+ aA -= iA * b2Cross_v2_v2(rA, P);
+ cB.Add(b2Vec2.Multiply(mB, P));
+ aB += iB * b2Cross_v2_v2(rB, P)
+ }
+ this.m_positions[indexA].a = aA;
+ this.m_positions[indexB].a = aB
+ }
+ return minSeparation >= -1.5 * b2_linearSlop
+ }
+};
+"use strict";
+
+function b2Island() {
+ this.m_bodies = [];
+ this.m_contacts = [];
+ this.m_joints = [];
+ this.m_velocities = [];
+ this.m_positions = []
+}
+var profile_solve_init = b2Profiler.create("solve initialization", "solve");
+var profile_solve_init_warmStarting = b2Profiler.create("warm starting", "solve initialization");
+var profile_solve_velocity = b2Profiler.create("solve velocities", "solve");
+var profile_solve_position = b2Profiler.create("solve positions", "solve");
+b2Island._solverData = new b2SolverData();
+b2Island._solverDef = new b2ContactSolverDef();
+b2Island._solver = new b2ContactSolver();
+b2Island.prototype = {
+ Clear: function () {
+ this.m_bodyCount = 0;
+ this.m_contactCount = 0;
+ this.m_jointCount = 0
+ },
+ Initialize: function (bodyCapacity, contactCapacity, jointCapacity, listener) {
+ this.m_listener = listener;
+ this.m_bodyCapacity = bodyCapacity;
+ this.m_contactCapacity = contactCapacity;
+ this.m_jointCapacity = jointCapacity;
+ this.m_bodyCount = 0;
+ this.m_contactCount = 0;
+ this.m_jointCount = 0;
+ this.m_bodies.length = bodyCapacity;
+ this.m_contacts.length = contactCapacity;
+ this.m_joints.length = jointCapacity;
+ this.m_velocities.length = bodyCapacity;
+ this.m_positions.length = bodyCapacity
+ },
+ Solve: function (step, gravity, allowSleep) {
+ profile_solve_init.start();
+ var h = step.dt;
+ for (var i = 0; i < this.m_bodyCount; ++i) {
+ var b = this.m_bodies[i];
+ this.m_positions[i].c.Assign(b.m_sweep.c);
+ var a = b.m_sweep.a;
+ this.m_velocities[i].v.Assign(b.m_linearVelocity);
+ var w = b.m_angularVelocity;
+ b.m_sweep.c0.Assign(b.m_sweep.c);
+ b.m_sweep.a0 = b.m_sweep.a;
+ if (b.m_type == b2Body.b2_dynamicBody) {
+ this.m_velocities[i].v.x += h * ((b.m_gravityScale * gravity.x) + (b.m_invMass * b.m_force.x));
+ this.m_velocities[i].v.y += h * ((b.m_gravityScale * gravity.y) + (b.m_invMass * b.m_force.y));
+ w += h * b.m_invI * b.m_torque;
+ this.m_velocities[i].v.x *= 1 / (1 + h * b.m_linearDamping);
+ this.m_velocities[i].v.y *= 1 / (1 + h * b.m_linearDamping);
+ w *= 1 / (1 + h * b.m_angularDamping)
+ }
+ this.m_positions[i].a = a;
+ this.m_velocities[i].w = w
+ }
+ b2Island._solverData.step = step;
+ b2Island._solverData.positions = this.m_positions;
+ b2Island._solverData.velocities = this.m_velocities;
+ b2Island._solverDef.step = step;
+ b2Island._solverDef.contacts = this.m_contacts;
+ b2Island._solverDef.count = this.m_contactCount;
+ b2Island._solverDef.positions = this.m_positions;
+ b2Island._solverDef.velocities = this.m_velocities;
+ b2Island._solverDef.allocator = this.m_allocator;
+ b2Island._solver.Init(b2Island._solverDef);
+ b2Island._solver.InitializeVelocityConstraints();
+ if (step.warmStarting) {
+ profile_solve_init_warmStarting.start();
+ b2Island._solver.WarmStart();
+ profile_solve_init_warmStarting.stop()
+ }
+ for (var i = 0; i < this.m_jointCount; ++i) {
+ this.m_joints[i].InitVelocityConstraints(b2Island._solverData)
+ }
+ profile_solve_init.stop();
+ profile_solve_velocity.start();
+ for (var i = 0; i < step.velocityIterations; ++i) {
+ for (var j = 0; j < this.m_jointCount; ++j) {
+ this.m_joints[j].SolveVelocityConstraints(b2Island._solverData)
+ }
+ b2Island._solver.SolveVelocityConstraints()
+ }
+ b2Island._solver.StoreImpulses();
+ profile_solve_velocity.stop();
+ profile_solve_position.start();
+ for (var i = 0; i < this.m_bodyCount; ++i) {
+ var c = this.m_positions[i].c;
+ var a = this.m_positions[i].a;
+ var v = this.m_velocities[i].v;
+ var w = this.m_velocities[i].w;
+ var translationx = h * v.x;
+ var translationy = h * v.y;
+ var translationl = translationx * translationx + translationy * translationy;
+ if (translationl > b2_maxTranslationSquared) {
+ var ratio = b2_maxTranslation / b2Sqrt(translationl);
+ v.x *= ratio;
+ v.y *= ratio
+ }
+ var rotation = h * w;
+ if (rotation * rotation > b2_maxRotationSquared) {
+ var ratio = b2_maxRotation / b2Abs(rotation);
+ w *= ratio
+ }
+ c.x += h * v.x;
+ c.y += h * v.y;
+ a += h * w;
+ this.m_positions[i].a = a;
+ this.m_velocities[i].w = w
+ }
+ var positionSolved = false;
+ for (var i = 0; i < step.positionIterations; ++i) {
+ var contactsOkay = b2Island._solver.SolvePositionConstraints();
+ var jointsOkay = true;
+ for (var j = 0; j < this.m_jointCount; ++j) {
+ var jointOkay = this.m_joints[j].SolvePositionConstraints(b2Island._solverData);
+ jointsOkay = jointsOkay && jointOkay
+ }
+ if (contactsOkay && jointsOkay) {
+ positionSolved = true;
+ break
+ }
+ }
+ for (var i = 0; i < this.m_bodyCount; ++i) {
+ var body = this.m_bodies[i];
+ body.m_sweep.c.Assign(this.m_positions[i].c);
+ body.m_sweep.a = this.m_positions[i].a;
+ body.m_linearVelocity.Assign(this.m_velocities[i].v);
+ body.m_angularVelocity = this.m_velocities[i].w;
+ body.SynchronizeTransform()
+ }
+ profile_solve_position.stop();
+ this.Report(b2Island._solver.m_velocityConstraints);
+ if (allowSleep) {
+ var minSleepTime = b2_maxFloat;
+ var linTolSqr = b2_linearSleepTolerance * b2_linearSleepTolerance;
+ var angTolSqr = b2_angularSleepTolerance * b2_angularSleepTolerance;
+ for (var i = 0; i < this.m_bodyCount; ++i) {
+ var b = this.m_bodies[i];
+ if (b.GetType() == b2Body.b2_staticBody) {
+ continue
+ }
+ if ((b.m_flags & b2Body.e_autoSleepFlag) == 0 || b.m_angularVelocity * b.m_angularVelocity > angTolSqr || b2Dot_v2_v2(b.m_linearVelocity, b.m_linearVelocity) > linTolSqr) {
+ b.m_sleepTime = 0;
+ minSleepTime = 0
+ } else {
+ b.m_sleepTime += h;
+ minSleepTime = b2Min(minSleepTime, b.m_sleepTime)
+ }
+ }
+ if (minSleepTime >= b2_timeToSleep && positionSolved) {
+ for (var i = 0; i < this.m_bodyCount; ++i) {
+ var b = this.m_bodies[i];
+ b.SetAwake(false)
+ }
+ }
+ }
+ },
+ SolveTOI: function (subStep, toiIndexA, toiIndexB) {
+ for (var i = 0; i < this.m_bodyCount; ++i) {
+ var b = this.m_bodies[i];
+ this.m_positions[i].c.Assign(b.m_sweep.c);
+ this.m_positions[i].a = b.m_sweep.a;
+ this.m_velocities[i].v.Assign(b.m_linearVelocity);
+ this.m_velocities[i].w = b.m_angularVelocity
+ }
+ b2Island._solverDef.contacts = this.m_contacts;
+ b2Island._solverDef.count = this.m_contactCount;
+ b2Island._solverDef.step = subStep;
+ b2Island._solverDef.positions = this.m_positions;
+ b2Island._solverDef.velocities = this.m_velocities;
+ b2Island._solver.Init(b2Island._solverDef);
+ for (var i = 0; i < subStep.positionIterations; ++i) {
+ var contactsOkay = b2Island._solver.SolveTOIPositionConstraints(toiIndexA, toiIndexB);
+ if (contactsOkay) {
+ break
+ }
+ }
+ this.m_bodies[toiIndexA].m_sweep.c0.Assign(this.m_positions[toiIndexA].c);
+ this.m_bodies[toiIndexA].m_sweep.a0 = this.m_positions[toiIndexA].a;
+ this.m_bodies[toiIndexB].m_sweep.c0.Assign(this.m_positions[toiIndexB].c);
+ this.m_bodies[toiIndexB].m_sweep.a0 = this.m_positions[toiIndexB].a;
+ b2Island._solver.InitializeVelocityConstraints();
+ for (var i = 0; i < subStep.velocityIterations; ++i) {
+ b2Island._solver.SolveVelocityConstraints()
+ }
+ var h = subStep.dt;
+ for (var i = 0; i < this.m_bodyCount; ++i) {
+ var c = this.m_positions[i].c;
+ var a = this.m_positions[i].a;
+ var v = this.m_velocities[i].v;
+ var w = this.m_velocities[i].w;
+ var translation = b2Vec2.Multiply(h, v);
+ if (b2Dot_v2_v2(translation, translation) > b2_maxTranslationSquared) {
+ var ratio = b2_maxTranslation / translation.Length();
+ v.Multiply(ratio)
+ }
+ var rotation = h * w;
+ if (rotation * rotation > b2_maxRotationSquared) {
+ var ratio = b2_maxRotation / b2Abs(rotation);
+ w *= ratio
+ }
+ c.Add(b2Vec2.Multiply(h, v));
+ a += h * w;
+ this.m_positions[i].a = a;
+ this.m_velocities[i].w = w;
+ var body = this.m_bodies[i];
+ body.m_sweep.c.Assign(c);
+ body.m_sweep.a = a;
+ body.m_linearVelocity.Assign(v);
+ body.m_angularVelocity = w;
+ body.SynchronizeTransform()
+ }
+ this.Report(b2Island._solver.m_velocityConstraints)
+ },
+ AddBody: function (body) {
+ body.m_islandIndex = this.m_bodyCount;
+ this.m_bodies[this.m_bodyCount] = body;
+ if (!this.m_positions[this.m_bodyCount]) {
+ this.m_positions[this.m_bodyCount] = new b2Position();
+ this.m_velocities[this.m_bodyCount] = new b2Velocity()
+ }++this.m_bodyCount
+ },
+ AddContact: function (contact) {
+ this.m_contacts[this.m_contactCount++] = contact
+ },
+ AddJoint: function (joint) {
+ this.m_joints[this.m_jointCount++] = joint
+ },
+ Report: function (constraints) {
+ if (this.m_listener == null) {
+ return
+ }
+ for (var i = 0; i < this.m_contactCount; ++i) {
+ var c = this.m_contacts[i];
+ var vc = constraints[i];
+ var impulse = new b2ContactImpulse();
+ impulse.count = vc.pointCount;
+ for (var j = 0; j < vc.pointCount; ++j) {
+ impulse.normalImpulses[j] = vc.points[j].normalImpulse;
+ impulse.tangentImpulses[j] = vc.points[j].tangentImpulse
+ }
+ this.m_listener.PostSolve(c, impulse)
+ }
+ }
+};
+
+function b2Jacobian() {
+ this.linear = new b2Vec2();
+ this.angularA = 0;
+ this.angularB = 0
+}
+
+function b2JointEdge() {
+ this.other = null;
+ this.joint = null;
+ this.prev = null;
+ this.next = null
+}
+
+function b2JointDef() {
+ this.type = b2Joint.e_unknownJoint;
+ this.userData = null;
+ this.bodyA = null;
+ this.bodyB = null;
+ this.collideConnected = false
+}
+b2JointDef.prototype = {
+ _deserialize: function (data, bodies, joints) {
+ this.bodyA = bodies[data.bodyA];
+ this.bodyB = bodies[data.bodyB];
+ this.collideConnected = data.collideConnected
+ }
+};
+
+function b2Joint(def) {
+ this.m_type = def.type;
+ this.m_prev = null;
+ this.m_next = null;
+ this.m_bodyA = def.bodyA;
+ this.m_bodyB = def.bodyB;
+ this.m_index = 0;
+ this.m_collideConnected = def.collideConnected;
+ this.m_islandFlag = false;
+ this.m_userData = def.userData;
+ this.m_edgeA = new b2JointEdge();
+ this.m_edgeA.joint = null;
+ this.m_edgeA.other = null;
+ this.m_edgeA.prev = null;
+ this.m_edgeA.next = null;
+ this.m_edgeB = new b2JointEdge();
+ this.m_edgeB.joint = null;
+ this.m_edgeB.other = null;
+ this.m_edgeB.prev = null;
+ this.m_edgeB.next = null
+}
+b2Joint.prototype = {
+ GetType: function () {
+ return this.m_type
+ },
+ GetBodyA: function () {
+ return this.m_bodyA
+ },
+ GetBodyB: function () {
+ return this.m_bodyB
+ },
+ GetAnchorA: function () {},
+ GetAnchorB: function () {},
+ GetReactionForce: function (inv_dt) {},
+ GetReactionTorque: function (inv_dt) {},
+ GetNext: function () {
+ return this.m_next
+ },
+ GetUserData: function () {
+ return this.m_userData
+ },
+ SetUserData: function (data) {
+ this.m_userData = data
+ },
+ IsActive: function () {
+ return this.m_bodyA.IsActive() && this.m_bodyB.IsActive()
+ },
+ GetCollideConnected: function () {
+ return this.m_collideConnected
+ },
+ ShiftOrigin: function (newOrigin) {},
+ InitVelocityConstraints: function (data) {},
+ SolveVelocityConstraints: function (data) {},
+ SolvePositionConstraints: function (data) {},
+ _serialize: function (out) {
+ var obj = out || {};
+ obj.bodyA = null;
+ obj.bodyB = null;
+ obj.type = this.m_type;
+ obj.collideConnected = this.m_collideConnected;
+ return obj
+ }
+};
+b2Joint.e_inactiveLimit = 0;
+b2Joint.e_atLowerLimit = 1;
+b2Joint.e_atUpperLimit = 2;
+b2Joint.e_equalLimits = 3;
+b2Joint.e_unknownJoint = 0;
+b2Joint.e_revoluteJoint = 1;
+b2Joint.e_prismaticJoint = 2;
+b2Joint.e_distanceJoint = 3;
+b2Joint.e_pulleyJoint = 4;
+b2Joint.e_mouseJoint = 5;
+b2Joint.e_gearJoint = 6;
+b2Joint.e_wheelJoint = 7;
+b2Joint.e_weldJoint = 8;
+b2Joint.e_frictionJoint = 9;
+b2Joint.e_ropeJoint = 10;
+b2Joint.e_motorJoint = 11;
+b2Joint.Create = function (def) {
+ var joint = null;
+ switch (def.type) {
+ case b2Joint.e_distanceJoint:
+ joint = new b2DistanceJoint(def);
+ break;
+ case b2Joint.e_mouseJoint:
+ joint = new b2MouseJoint(def);
+ break;
+ case b2Joint.e_prismaticJoint:
+ joint = new b2PrismaticJoint(def);
+ break;
+ case b2Joint.e_revoluteJoint:
+ joint = new b2RevoluteJoint(def);
+ break;
+ case b2Joint.e_pulleyJoint:
+ joint = new b2PulleyJoint(def);
+ break;
+ case b2Joint.e_gearJoint:
+ joint = new b2GearJoint(def);
+ break;
+ case b2Joint.e_wheelJoint:
+ joint = new b2WheelJoint(def);
+ break;
+ case b2Joint.e_weldJoint:
+ joint = new b2WeldJoint(def);
+ break;
+ case b2Joint.e_frictionJoint:
+ joint = new b2FrictionJoint(def);
+ break;
+ case b2Joint.e_ropeJoint:
+ joint = new b2RopeJoint(def);
+ break;
+ case b2Joint.e_motorJoint:
+ joint = new b2MotorJoint(def);
+ break
+ }
+ return joint
+};
+b2Joint.Destroy = function (joint) {};
+
+function b2RevoluteJointDef() {
+ this.parent.call(this);
+ this.type = b2Joint.e_revoluteJoint;
+ this.localAnchorA = new b2Vec2();
+ this.localAnchorB = new b2Vec2();
+ this.referenceAngle = 0;
+ this.lowerAngle = 0;
+ this.upperAngle = 0;
+ this.maxMotorTorque = 0;
+ this.motorSpeed = 0;
+ this.enableLimit = false;
+ this.enableMotor = false;
+ Object.seal(this)
+}
+b2RevoluteJointDef.prototype = {
+ Initialize: function (bA, bB, anchor) {
+ this.bodyA = bA;
+ this.bodyB = bB;
+ this.localAnchorA = this.bodyA.GetLocalPoint(anchor);
+ this.localAnchorB = this.bodyB.GetLocalPoint(anchor);
+ this.referenceAngle = this.bodyB.GetAngle() - this.bodyA.GetAngle()
+ },
+ _deserialize: function (data, bodies, joints) {
+ this.parent.prototype._deserialize.call(this, data, bodies, joints);
+ this.localAnchorA._deserialize(data.localAnchorA);
+ this.localAnchorB._deserialize(data.localAnchorB);
+ this.referenceAngle = data.referenceAngle;
+ this.lowerAngle = data.lowerAngle;
+ this.upperAngle = data.upperAngle;
+ this.maxMotorTorque = data.maxMotorTorque;
+ this.motorSpeed = data.motorSpeed;
+ this.enableLimit = data.enableLimit;
+ this.enableMotor = data.enableMotor
+ }
+};
+b2RevoluteJointDef._extend(b2JointDef);
+
+function b2RevoluteJoint(def) {
+ this.parent.call(this, def);
+ this.m_localAnchorA = def.localAnchorA.Clone();
+ this.m_localAnchorB = def.localAnchorB.Clone();
+ this.m_referenceAngle = def.referenceAngle;
+ this.m_impulse = new b2Vec3();
+ this.m_motorImpulse = 0;
+ this.m_lowerAngle = def.lowerAngle;
+ this.m_upperAngle = def.upperAngle;
+ this.m_maxMotorTorque = def.maxMotorTorque;
+ this.m_motorSpeed = def.motorSpeed;
+ this.m_enableLimit = def.enableLimit;
+ this.m_enableMotor = def.enableMotor;
+ this.m_limitState = b2Joint.e_inactiveLimit;
+ this.m_indexA = 0;
+ this.m_indexB = 0;
+ this.m_rA = new b2Vec2();
+ this.m_rB = new b2Vec2();
+ this.m_localCenterA = new b2Vec2();
+ this.m_localCenterB = new b2Vec2();
+ this.m_invMassA = 0;
+ this.m_invMassB = 0;
+ this.m_invIA = 0;
+ this.m_invIB = 0;
+ this.m_mass = new b2Mat33();
+ this.m_motorMass = 0
+}
+b2RevoluteJoint.prototype = {
+ GetAnchorA: function () {
+ return this.m_bodyA.GetWorldPoint(this.m_localAnchorA)
+ },
+ GetAnchorB: function () {
+ return this.m_bodyB.GetWorldPoint(this.m_localAnchorB)
+ },
+ GetLocalAnchorA: function () {
+ return this.m_localAnchorA
+ },
+ GetLocalAnchorB: function () {
+ return this.m_localAnchorB
+ },
+ GetReferenceAngle: function () {
+ return this.m_referenceAngle
+ },
+ GetJointAngle: function () {
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ return bB.m_sweep.a - bA.m_sweep.a - this.m_referenceAngle
+ },
+ GetJointSpeed: function () {
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ return bB.m_angularVelocity - bA.m_angularVelocity
+ },
+ IsLimitEnabled: function () {
+ return this.m_enableLimit
+ },
+ EnableLimit: function (flag) {
+ if (flag != this.m_enableLimit) {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_enableLimit = flag;
+ this.m_impulse.z = 0
+ }
+ },
+ GetLowerLimit: function () {
+ return this.m_lowerAngle
+ },
+ GetUpperLimit: function () {
+ return this.m_upperAngle
+ },
+ SetLimits: function (lower, upper) {
+ if (lower != this.m_lowerAngle || upper != this.m_upperAngle) {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_impulse.z = 0;
+ this.m_lowerAngle = lower;
+ this.m_upperAngle = upper
+ }
+ },
+ IsMotorEnabled: function () {
+ return this.m_enableMotor
+ },
+ EnableMotor: function (flag) {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_enableMotor = flag
+ },
+ SetMotorSpeed: function (speed) {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_motorSpeed = speed
+ },
+ GetMotorSpeed: function () {
+ return this.m_motorSpeed
+ },
+ SetMaxMotorTorque: function (torque) {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_maxMotorTorque = torque
+ },
+ GetMaxMotorTorque: function () {
+ return this.m_maxMotorTorque
+ },
+ GetReactionForce: function (inv_dt) {
+ var P = new b2Vec2(this.m_impulse.x, this.m_impulse.y);
+ return b2Vec2.Multiply(inv_dt, P)
+ },
+ GetReactionTorque: function (inv_dt) {
+ return inv_dt * this.m_impulse.z
+ },
+ GetMotorTorque: function (inv_dt) {
+ return inv_dt * this.m_motorImpulse
+ },
+ InitVelocityConstraints: function (data) {
+ this.m_indexA = this.m_bodyA.m_islandIndex;
+ this.m_indexB = this.m_bodyB.m_islandIndex;
+ this.m_localCenterA = this.m_bodyA.m_sweep.localCenter;
+ this.m_localCenterB = this.m_bodyB.m_sweep.localCenter;
+ this.m_invMassA = this.m_bodyA.m_invMass;
+ this.m_invMassB = this.m_bodyB.m_invMass;
+ this.m_invIA = this.m_bodyA.m_invI;
+ this.m_invIB = this.m_bodyB.m_invI;
+ var aA = data.positions[this.m_indexA].a;
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var aB = data.positions[this.m_indexB].a;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var qA = new b2Rot(aA),
+ qB = new b2Rot(aB);
+ this.m_rA = b2Mul_r_v2(qA, b2Vec2.Subtract(this.m_localAnchorA, this.m_localCenterA));
+ this.m_rB = b2Mul_r_v2(qB, b2Vec2.Subtract(this.m_localAnchorB, this.m_localCenterB));
+ var mA = this.m_invMassA,
+ mB = this.m_invMassB;
+ var iA = this.m_invIA,
+ iB = this.m_invIB;
+ var fixedRotation = (iA + iB == 0);
+ this.m_mass.ex.x = mA + mB + this.m_rA.y * this.m_rA.y * iA + this.m_rB.y * this.m_rB.y * iB;
+ this.m_mass.ey.x = -this.m_rA.y * this.m_rA.x * iA - this.m_rB.y * this.m_rB.x * iB;
+ this.m_mass.ez.x = -this.m_rA.y * iA - this.m_rB.y * iB;
+ this.m_mass.ex.y = this.m_mass.ey.x;
+ this.m_mass.ey.y = mA + mB + this.m_rA.x * this.m_rA.x * iA + this.m_rB.x * this.m_rB.x * iB;
+ this.m_mass.ez.y = this.m_rA.x * iA + this.m_rB.x * iB;
+ this.m_mass.ex.z = this.m_mass.ez.x;
+ this.m_mass.ey.z = this.m_mass.ez.y;
+ this.m_mass.ez.z = iA + iB;
+ this.m_motorMass = iA + iB;
+ if (this.m_motorMass > 0) {
+ this.m_motorMass = 1 / this.m_motorMass
+ }
+ if (this.m_enableMotor == false || fixedRotation) {
+ this.m_motorImpulse = 0
+ }
+ if (this.m_enableLimit && fixedRotation == false) {
+ var jointAngle = aB - aA - this.m_referenceAngle;
+ if (b2Abs(this.m_upperAngle - this.m_lowerAngle) < 2 * b2_angularSlop) {
+ this.m_limitState = b2Joint.e_equalLimits
+ } else {
+ if (jointAngle <= this.m_lowerAngle) {
+ if (this.m_limitState != b2Joint.e_atLowerLimit) {
+ this.m_impulse.z = 0
+ }
+ this.m_limitState = b2Joint.e_atLowerLimit
+ } else {
+ if (jointAngle >= this.m_upperAngle) {
+ if (this.m_limitState != b2Joint.e_atUpperLimit) {
+ this.m_impulse.z = 0
+ }
+ this.m_limitState = b2Joint.e_atUpperLimit
+ } else {
+ this.m_limitState = b2Joint.e_inactiveLimit;
+ this.m_impulse.z = 0
+ }
+ }
+ }
+ } else {
+ this.m_limitState = b2Joint.e_inactiveLimit
+ }
+ if (data.step.warmStarting) {
+ this.m_impulse.Multiply(data.step.dtRatio);
+ this.m_motorImpulse *= data.step.dtRatio;
+ var P = new b2Vec2(this.m_impulse.x, this.m_impulse.y);
+ vA.Subtract(b2Vec2.Multiply(mA, P));
+ wA -= iA * (b2Cross_v2_v2(this.m_rA, P) + this.m_motorImpulse + this.m_impulse.z);
+ vB.Add(b2Vec2.Multiply(mB, P));
+ wB += iB * (b2Cross_v2_v2(this.m_rB, P) + this.m_motorImpulse + this.m_impulse.z)
+ } else {
+ this.m_impulse.SetZero();
+ this.m_motorImpulse = 0
+ }
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolveVelocityConstraints: function (data) {
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var mA = this.m_invMassA,
+ mB = this.m_invMassB;
+ var iA = this.m_invIA,
+ iB = this.m_invIB;
+ var fixedRotation = (iA + iB == 0);
+ if (this.m_enableMotor && this.m_limitState != b2Joint.e_equalLimits && fixedRotation == false) {
+ var Cdot = wB - wA - this.m_motorSpeed;
+ var impulse = -this.m_motorMass * Cdot;
+ var oldImpulse = this.m_motorImpulse;
+ var maxImpulse = data.step.dt * this.m_maxMotorTorque;
+ this.m_motorImpulse = b2Clamp(this.m_motorImpulse + impulse, -maxImpulse, maxImpulse);
+ impulse = this.m_motorImpulse - oldImpulse;
+ wA -= iA * impulse;
+ wB += iB * impulse
+ }
+ if (this.m_enableLimit && this.m_limitState != b2Joint.e_inactiveLimit && fixedRotation == false) {
+ var Cdot1 = b2Vec2.Subtract(b2Vec2.Subtract(b2Vec2.Add(vB, b2Cross_f_v2(wB, this.m_rB)), vA), b2Cross_f_v2(wA, this.m_rA));
+ var Cdot2 = wB - wA;
+ var Cdot = new b2Vec3(Cdot1.x, Cdot1.y, Cdot2);
+ var impulse = this.m_mass.Solve33(Cdot).Negate();
+ if (this.m_limitState == b2Joint.e_equalLimits) {
+ this.m_impulse.Add(impulse)
+ } else {
+ if (this.m_limitState == b2Joint.e_atLowerLimit) {
+ var newImpulse = this.m_impulse.z + impulse.z;
+ if (newImpulse < 0) {
+ var rhs = b2Vec2.Add(Cdot1.Negate(), b2Vec2.Multiply(this.m_impulse.z, new b2Vec2(this.m_mass.ez.x, this.m_mass.ez.y)));
+ var reduced = this.m_mass.Solve22(rhs);
+ impulse.x = reduced.x;
+ impulse.y = reduced.y;
+ impulse.z = -this.m_impulse.z;
+ this.m_impulse.x += reduced.x;
+ this.m_impulse.y += reduced.y;
+ this.m_impulse.z = 0
+ } else {
+ this.m_impulse.Add(impulse)
+ }
+ } else {
+ if (this.m_limitState == b2Joint.e_atUpperLimit) {
+ var newImpulse = this.m_impulse.z + impulse.z;
+ if (newImpulse > 0) {
+ var rhs = b2Vec2.Add(Cdot1.Negate(), b2Vec2.Multiply(this.m_impulse.z, new b2Vec2(this.m_mass.ez.x, this.m_mass.ez.y)));
+ var reduced = this.m_mass.Solve22(rhs);
+ impulse.x = reduced.x;
+ impulse.y = reduced.y;
+ impulse.z = -this.m_impulse.z;
+ this.m_impulse.x += reduced.x;
+ this.m_impulse.y += reduced.y;
+ this.m_impulse.z = 0
+ } else {
+ this.m_impulse.Add(impulse)
+ }
+ }
+ }
+ }
+ var P = new b2Vec2(impulse.x, impulse.y);
+ vA.Subtract(b2Vec2.Multiply(mA, P));
+ wA -= iA * (b2Cross_v2_v2(this.m_rA, P) + impulse.z);
+ vB.Add(b2Vec2.Multiply(mB, P));
+ wB += iB * (b2Cross_v2_v2(this.m_rB, P) + impulse.z)
+ } else {
+ var Cdot = b2Vec2.Subtract(b2Vec2.Subtract(b2Vec2.Add(vB, b2Cross_f_v2(wB, this.m_rB)), vA), b2Cross_f_v2(wA, this.m_rA));
+ var impulse = this.m_mass.Solve22(Cdot.Negate());
+ this.m_impulse.x += impulse.x;
+ this.m_impulse.y += impulse.y;
+ vA.Subtract(b2Vec2.Multiply(mA, impulse));
+ wA -= iA * b2Cross_v2_v2(this.m_rA, impulse);
+ vB.Add(b2Vec2.Multiply(mB, impulse));
+ wB += iB * b2Cross_v2_v2(this.m_rB, impulse)
+ }
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolvePositionConstraints: function (data) {
+ var cA = data.positions[this.m_indexA].c.Clone();
+ var aA = data.positions[this.m_indexA].a;
+ var cB = data.positions[this.m_indexB].c.Clone();
+ var aB = data.positions[this.m_indexB].a;
+ var qA = new b2Rot(aA),
+ qB = new b2Rot(aB);
+ var angularError = 0;
+ var positionError = 0;
+ var fixedRotation = (this.m_invIA + this.m_invIB == 0);
+ if (this.m_enableLimit && this.m_limitState != b2Joint.e_inactiveLimit && fixedRotation == false) {
+ var angle = aB - aA - this.m_referenceAngle;
+ var limitImpulse = 0;
+ if (this.m_limitState == b2Joint.e_equalLimits) {
+ var C = b2Clamp(angle - this.m_lowerAngle, -b2_maxAngularCorrection, b2_maxAngularCorrection);
+ limitImpulse = -this.m_motorMass * C;
+ angularError = b2Abs(C)
+ } else {
+ if (this.m_limitState == b2Joint.e_atLowerLimit) {
+ var C = angle - this.m_lowerAngle;
+ angularError = -C;
+ C = b2Clamp(C + b2_angularSlop, -b2_maxAngularCorrection, 0);
+ limitImpulse = -this.m_motorMass * C
+ } else {
+ if (this.m_limitState == b2Joint.e_atUpperLimit) {
+ var C = angle - this.m_upperAngle;
+ angularError = C;
+ C = b2Clamp(C - b2_angularSlop, 0, b2_maxAngularCorrection);
+ limitImpulse = -this.m_motorMass * C
+ }
+ }
+ }
+ aA -= this.m_invIA * limitImpulse;
+ aB += this.m_invIB * limitImpulse
+ }
+ qA.Set(aA);
+ qB.Set(aB);
+ var rA = b2Mul_r_v2(qA, b2Vec2.Subtract(this.m_localAnchorA, this.m_localCenterA));
+ var rB = b2Mul_r_v2(qB, b2Vec2.Subtract(this.m_localAnchorB, this.m_localCenterB));
+ var C = b2Vec2.Subtract(b2Vec2.Subtract(b2Vec2.Add(cB, rB), cA), rA);
+ positionError = C.Length();
+ var mA = this.m_invMassA,
+ mB = this.m_invMassB;
+ var iA = this.m_invIA,
+ iB = this.m_invIB;
+ var K = new b2Mat22();
+ K.ex.x = mA + mB + iA * rA.y * rA.y + iB * rB.y * rB.y;
+ K.ex.y = -iA * rA.x * rA.y - iB * rB.x * rB.y;
+ K.ey.x = K.ex.y;
+ K.ey.y = mA + mB + iA * rA.x * rA.x + iB * rB.x * rB.x;
+ var impulse = K.Solve(C).Negate();
+ cA.Subtract(b2Vec2.Multiply(mA, impulse));
+ aA -= iA * b2Cross_v2_v2(rA, impulse);
+ cB.Add(b2Vec2.Multiply(mB, impulse));
+ aB += iB * b2Cross_v2_v2(rB, impulse);
+ data.positions[this.m_indexA].c.Assign(cA);
+ data.positions[this.m_indexA].a = aA;
+ data.positions[this.m_indexB].c.Assign(cB);
+ data.positions[this.m_indexB].a = aB;
+ return positionError <= b2_linearSlop && angularError <= b2_angularSlop
+ },
+ _serialize: function (out) {
+ var obj = out || {};
+ this.parent.prototype._serialize.call(this, obj);
+ obj.localAnchorA = this.m_localAnchorA._serialize();
+ obj.localAnchorB = this.m_localAnchorB._serialize();
+ obj.referenceAngle = this.m_referenceAngle;
+ obj.lowerAngle = this.m_lowerAngle;
+ obj.upperAngle = this.m_upperAngle;
+ obj.maxMotorTorque = this.m_maxMotorTorque;
+ obj.motorSpeed = this.m_motorSpeed;
+ obj.enableLimit = this.m_enableLimit;
+ obj.enableMotor = this.m_enableMotor;
+ return obj
+ }
+};
+b2RevoluteJoint._extend(b2Joint);
+
+function b2MouseJointDef() {
+ this.parent.call(this);
+ this.type = b2Joint.e_mouseJoint;
+ this.target = new b2Vec2(0, 0);
+ this.maxForce = 0;
+ this.frequencyHz = 5;
+ this.dampingRatio = 0.7;
+ Object.seal(this)
+}
+b2MouseJointDef._extend(b2JointDef);
+
+function b2MouseJoint(def) {
+ this.parent.call(this, def);
+ this.m_targetA = def.target.Clone();
+ this.m_localAnchorB = b2MulT_t_v2(this.m_bodyB.GetTransform(), this.m_targetA);
+ this.m_maxForce = def.maxForce;
+ this.m_impulse = new b2Vec2();
+ this.m_frequencyHz = def.frequencyHz;
+ this.m_dampingRatio = def.dampingRatio;
+ this.m_beta = 0;
+ this.m_gamma = 0;
+ this.m_indexA = 0;
+ this.m_indexB = 0;
+ this.m_rB = new b2Vec2();
+ this.m_localCenterB = new b2Vec2();
+ this.m_invMassB = 0;
+ this.m_invIB = 0;
+ this.m_mass = new b2Mat22();
+ this.m_C = new b2Vec2()
+}
+b2MouseJoint.prototype = {
+ GetAnchorA: function () {
+ return this.m_targetA
+ },
+ GetAnchorB: function () {
+ return this.m_bodyB.GetWorldPoint(this.m_localAnchorB)
+ },
+ GetReactionForce: function (inv_dt) {
+ return b2Vec2.Multiply(inv_dt, this.m_impulse)
+ },
+ GetReactionTorque: function (inv_dt) {
+ return inv_dt * 0
+ },
+ SetTarget: function (target) {
+ if (this.m_bodyB.IsAwake() == false) {
+ this.m_bodyB.SetAwake(true)
+ }
+ this.m_targetA.Assign(target)
+ },
+ GetTarget: function () {
+ return this.m_targetA
+ },
+ SetMaxForce: function (force) {
+ this.m_maxForce = force
+ },
+ GetMaxForce: function () {
+ return this.m_maxForce
+ },
+ SetFrequency: function (hz) {
+ this.m_frequencyHz = hz
+ },
+ GetFrequency: function () {
+ return this.m_frequencyHz
+ },
+ SetDampingRatio: function (ratio) {
+ this.m_dampingRatio = ratio
+ },
+ GetDampingRatio: function () {
+ return this.m_dampingRatio
+ },
+ ShiftOrigin: function (newOrigin) {
+ this.m_targetA.Subtract(newOrigin)
+ },
+ InitVelocityConstraints: function (data) {
+ this.m_indexB = this.m_bodyB.m_islandIndex;
+ this.m_localCenterB.Assign(this.m_bodyB.m_sweep.localCenter);
+ this.m_invMassB = this.m_bodyB.m_invMass;
+ this.m_invIB = this.m_bodyB.m_invI;
+ var cB = data.positions[this.m_indexB].c.Clone();
+ var aB = data.positions[this.m_indexB].a;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var qB = new b2Rot(aB);
+ var mass = this.m_bodyB.GetMass();
+ var omega = 2 * b2_pi * this.m_frequencyHz;
+ var d = 2 * mass * this.m_dampingRatio * omega;
+ var k = mass * (omega * omega);
+ var h = data.step.dt;
+ this.m_gamma = h * (d + h * k);
+ if (this.m_gamma != 0) {
+ this.m_gamma = 1 / this.m_gamma
+ }
+ this.m_beta = h * k * this.m_gamma;
+ this.m_rB.Assign(b2Mul_r_v2(qB, b2Vec2.Subtract(this.m_localAnchorB, this.m_localCenterB)));
+ var K = new b2Mat22();
+ K.ex.x = this.m_invMassB + this.m_invIB * this.m_rB.y * this.m_rB.y + this.m_gamma;
+ K.ex.y = -this.m_invIB * this.m_rB.x * this.m_rB.y;
+ K.ey.x = K.ex.y;
+ K.ey.y = this.m_invMassB + this.m_invIB * this.m_rB.x * this.m_rB.x + this.m_gamma;
+ this.m_mass.Assign(K.GetInverse());
+ this.m_C.Assign(b2Vec2.Subtract(b2Vec2.Add(cB, this.m_rB), this.m_targetA));
+ this.m_C.Multiply(this.m_beta);
+ wB *= 0.98;
+ if (data.step.warmStarting) {
+ this.m_impulse.Multiply(data.step.dtRatio);
+ vB.Add(b2Vec2.Multiply(this.m_invMassB, this.m_impulse));
+ wB += this.m_invIB * b2Cross_v2_v2(this.m_rB, this.m_impulse)
+ } else {
+ this.m_impulse.SetZero()
+ }
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolveVelocityConstraints: function (data) {
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var Cdot = b2Vec2.Add(vB, b2Cross_f_v2(wB, this.m_rB));
+ var impulse = b2Mul_m22_v2(this.m_mass, (b2Vec2.Add(b2Vec2.Add(Cdot, this.m_C), b2Vec2.Multiply(this.m_gamma, this.m_impulse))).Negate());
+ var oldImpulse = this.m_impulse.Clone();
+ this.m_impulse.Add(impulse);
+ var maxImpulse = data.step.dt * this.m_maxForce;
+ if (this.m_impulse.LengthSquared() > maxImpulse * maxImpulse) {
+ this.m_impulse.Multiply(maxImpulse / this.m_impulse.Length())
+ }
+ impulse.Assign(b2Vec2.Subtract(this.m_impulse, oldImpulse));
+ vB.Add(b2Vec2.Multiply(this.m_invMassB, impulse));
+ wB += this.m_invIB * b2Cross_v2_v2(this.m_rB, impulse);
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolvePositionConstraints: function (data) {
+ return true
+ }
+};
+b2MouseJoint._extend(b2Joint);
+
+function b2DistanceJointDef() {
+ this.parent.call(this);
+ this.type = b2Joint.e_distanceJoint;
+ this.localAnchorA = new b2Vec2(0, 0);
+ this.localAnchorB = new b2Vec2(0, 0);
+ this.length = 1;
+ this.frequencyHz = 0;
+ this.dampingRatio = 0;
+ Object.seal(this)
+}
+b2DistanceJointDef.prototype = {
+ Initialize: function (b1, b2, anchor1, anchor2) {
+ this.bodyA = b1;
+ this.bodyB = b2;
+ this.localAnchorA = this.bodyA.GetLocalPoint(anchor1);
+ this.localAnchorB = this.bodyB.GetLocalPoint(anchor2);
+ var d = b2Vec2.Subtract(anchor2, anchor1);
+ this.length = d.Length()
+ },
+ _deserialize: function (data, bodies, joints) {
+ this.parent.prototype._deserialize.call(this, data, bodies, joints);
+ this.localAnchorA._deserialize(data.localAnchorA);
+ this.localAnchorB._deserialize(data.localAnchorB);
+ this.length = data.length;
+ this.frequencyHz = data.frequencyHz;
+ this.dampingRatio = data.dampingRatio
+ }
+};
+b2DistanceJointDef._extend(b2JointDef);
+
+function b2DistanceJoint(def) {
+ this.parent.call(this, def);
+ this.m_localAnchorA = def.localAnchorA.Clone();
+ this.m_localAnchorB = def.localAnchorB.Clone();
+ this.m_length = def.length;
+ this.m_frequencyHz = def.frequencyHz;
+ this.m_dampingRatio = def.dampingRatio;
+ this.m_impulse = 0;
+ this.m_gamma = 0;
+ this.m_bias = 0;
+ this.m_indexA = 0;
+ this.m_indexB = 0;
+ this.m_u = new b2Vec2();
+ this.m_rA = new b2Vec2();
+ this.m_rB = new b2Vec2();
+ this.m_localCenterA = new b2Vec2();
+ this.m_localCenterB = new b2Vec2();
+ this.m_invMassA = 0;
+ this.m_invMassB = 0;
+ this.m_invIA = 0;
+ this.m_invIB = 0;
+ this.m_mass = 0
+}
+b2DistanceJoint.prototype = {
+ GetAnchorA: function () {
+ return this.m_bodyA.GetWorldPoint(this.m_localAnchorA)
+ },
+ GetAnchorB: function () {
+ return this.m_bodyB.GetWorldPoint(this.m_localAnchorB)
+ },
+ GetReactionForce: function (inv_dt) {
+ var F = b2Vec2.Multiply((inv_dt * this.m_impulse), this.m_u);
+ return F
+ },
+ GetReactionTorque: function (inv_dt) {
+ return 0
+ },
+ GetLocalAnchorA: function () {
+ return this.m_localAnchorA
+ },
+ GetLocalAnchorB: function () {
+ return this.m_localAnchorB
+ },
+ SetLength: function (length) {
+ this.m_length = length
+ },
+ GetLength: function () {
+ return this.m_length
+ },
+ SetFrequency: function (hz) {
+ this.m_frequencyHz = hz
+ },
+ GetFrequency: function () {
+ return this.m_frequencyHz
+ },
+ SetDampingRatio: function (ratio) {
+ this.m_dampingRatio = ratio
+ },
+ GetDampingRatio: function () {
+ return this.m_dampingRatio
+ },
+ InitVelocityConstraints: function (data) {
+ this.m_indexA = this.m_bodyA.m_islandIndex;
+ this.m_indexB = this.m_bodyB.m_islandIndex;
+ this.m_localCenterA.Assign(this.m_bodyA.m_sweep.localCenter);
+ this.m_localCenterB.Assign(this.m_bodyB.m_sweep.localCenter);
+ this.m_invMassA = this.m_bodyA.m_invMass;
+ this.m_invMassB = this.m_bodyB.m_invMass;
+ this.m_invIA = this.m_bodyA.m_invI;
+ this.m_invIB = this.m_bodyB.m_invI;
+ var cA = data.positions[this.m_indexA].c.Clone();
+ var aA = data.positions[this.m_indexA].a;
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var cB = data.positions[this.m_indexB].c.Clone();
+ var aB = data.positions[this.m_indexB].a;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var qA = new b2Rot(aA),
+ qB = new b2Rot(aB);
+ this.m_rA = b2Mul_r_v2(qA, b2Vec2.Subtract(this.m_localAnchorA, this.m_localCenterA));
+ this.m_rB = b2Mul_r_v2(qB, b2Vec2.Subtract(this.m_localAnchorB, this.m_localCenterB));
+ this.m_u = b2Vec2.Subtract(b2Vec2.Subtract(b2Vec2.Add(cB, this.m_rB), cA), this.m_rA);
+ var length = this.m_u.Length();
+ if (length > b2_linearSlop) {
+ this.m_u.Multiply(1 / length)
+ } else {
+ this.m_u.Set(0, 0)
+ }
+ var crAu = b2Cross_v2_v2(this.m_rA, this.m_u);
+ var crBu = b2Cross_v2_v2(this.m_rB, this.m_u);
+ var invMass = this.m_invMassA + this.m_invIA * crAu * crAu + this.m_invMassB + this.m_invIB * crBu * crBu;
+ this.m_mass = invMass != 0 ? 1 / invMass : 0;
+ if (this.m_frequencyHz > 0) {
+ var C = length - this.m_length;
+ var omega = 2 * b2_pi * this.m_frequencyHz;
+ var d = 2 * this.m_mass * this.m_dampingRatio * omega;
+ var k = this.m_mass * omega * omega;
+ var h = data.step.dt;
+ this.m_gamma = h * (d + h * k);
+ this.m_gamma = this.m_gamma != 0 ? 1 / this.m_gamma : 0;
+ this.m_bias = C * h * k * this.m_gamma;
+ invMass += this.m_gamma;
+ this.m_mass = invMass != 0 ? 1 / invMass : 0
+ } else {
+ this.m_gamma = 0;
+ this.m_bias = 0
+ }
+ if (data.step.warmStarting) {
+ this.m_impulse *= data.step.dtRatio;
+ var P = b2Vec2.Multiply(this.m_impulse, this.m_u);
+ vA.Subtract(b2Vec2.Multiply(this.m_invMassA, P));
+ wA -= this.m_invIA * b2Cross_v2_v2(this.m_rA, P);
+ vB.Add(b2Vec2.Multiply(this.m_invMassB, P));
+ wB += this.m_invIB * b2Cross_v2_v2(this.m_rB, P)
+ } else {
+ this.m_impulse = 0
+ }
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolveVelocityConstraints: function (data) {
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var vpA = b2Vec2.Add(vA, b2Cross_f_v2(wA, this.m_rA));
+ var vpB = b2Vec2.Add(vB, b2Cross_f_v2(wB, this.m_rB));
+ var Cdot = b2Dot_v2_v2(this.m_u, b2Vec2.Subtract(vpB, vpA));
+ var impulse = -this.m_mass * (Cdot + this.m_bias + this.m_gamma * this.m_impulse);
+ this.m_impulse += impulse;
+ var P = b2Vec2.Multiply(impulse, this.m_u);
+ vA.Subtract(b2Vec2.Multiply(this.m_invMassA, P));
+ wA -= this.m_invIA * b2Cross_v2_v2(this.m_rA, P);
+ vB.Add(b2Vec2.Multiply(this.m_invMassB, P));
+ wB += this.m_invIB * b2Cross_v2_v2(this.m_rB, P);
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolvePositionConstraints: function (data) {
+ if (this.m_frequencyHz > 0) {
+ return true
+ }
+ var cA = data.positions[this.m_indexA].c.Clone();
+ var aA = data.positions[this.m_indexA].a;
+ var cB = data.positions[this.m_indexB].c.Clone();
+ var aB = data.positions[this.m_indexB].a;
+ var qA = new b2Rot(aA),
+ qB = new b2Rot(aB);
+ var rA = b2Mul_r_v2(qA, b2Vec2.Subtract(this.m_localAnchorA, this.m_localCenterA));
+ var rB = b2Mul_r_v2(qB, b2Vec2.Subtract(this.m_localAnchorB, this.m_localCenterB));
+ var u = b2Vec2.Subtract(b2Vec2.Subtract(b2Vec2.Add(cB, rB), cA), rA);
+ var length = u.Normalize();
+ var C = length - this.m_length;
+ C = b2Clamp(C, -b2_maxLinearCorrection, b2_maxLinearCorrection);
+ var impulse = -this.m_mass * C;
+ var P = b2Vec2.Multiply(impulse, u);
+ cA.Subtract(b2Vec2.Multiply(this.m_invMassA, P));
+ aA -= this.m_invIA * b2Cross_v2_v2(rA, P);
+ cB.Add(b2Vec2.Multiply(this.m_invMassB, P));
+ aB += this.m_invIB * b2Cross_v2_v2(rB, P);
+ data.positions[this.m_indexA].c.Assign(cA);
+ data.positions[this.m_indexA].a = aA;
+ data.positions[this.m_indexB].c.Assign(cB);
+ data.positions[this.m_indexB].a = aB;
+ return b2Abs(C) < b2_linearSlop
+ },
+ _serialize: function (out) {
+ var obj = out || {};
+ this.parent.prototype._serialize.call(this, obj);
+ obj.localAnchorA = this.m_localAnchorA._serialize();
+ obj.localAnchorB = this.m_localAnchorB._serialize();
+ obj.length = this.m_length;
+ obj.frequencyHz = this.m_frequencyHz;
+ obj.dampingRatio = this.m_dampingRatio;
+ return obj
+ }
+};
+b2DistanceJoint._extend(b2Joint);
+
+function b2PrismaticJointDef() {
+ this.parent.call(this);
+ this.type = b2Joint.e_prismaticJoint;
+ this.localAnchorA = new b2Vec2();
+ this.localAnchorB = new b2Vec2();
+ this.localAxisA = new b2Vec2(1, 0);
+ this.referenceAngle = 0;
+ this.enableLimit = false;
+ this.lowerTranslation = 0;
+ this.upperTranslation = 0;
+ this.enableMotor = false;
+ this.maxMotorForce = 0;
+ this.motorSpeed = 0;
+ Object.seal(this)
+}
+b2PrismaticJointDef.prototype = {
+ Initialize: function (bA, bB, anchor, axis) {
+ this.bodyA = bA;
+ this.bodyB = bB;
+ this.localAnchorA = this.bodyA.GetLocalPoint(anchor);
+ this.localAnchorB = this.bodyB.GetLocalPoint(anchor);
+ this.localAxisA = this.bodyA.GetLocalVector(axis);
+ this.referenceAngle = this.bodyB.GetAngle() - this.bodyA.GetAngle()
+ },
+ _deserialize: function (data, bodies, joints) {
+ this.parent.prototype._deserialize.call(this, data, bodies, joints);
+ this.localAnchorA._deserialize(data.localAnchorA);
+ this.localAnchorB._deserialize(data.localAnchorB);
+ this.localAxisA._deserialize(data.localAxisA);
+ this.referenceAngle = data.referenceAngle;
+ this.enableLimit = data.enableLimit;
+ this.lowerTranslation = data.lowerTranslation;
+ this.upperTranslation = data.upperTranslation;
+ this.enableMotor = data.enableMotor;
+ this.maxMotorForce = data.maxMotorForce;
+ this.motorSpeed = data.motorSpeed
+ }
+};
+b2PrismaticJointDef._extend(b2JointDef);
+
+function b2PrismaticJoint(def) {
+ this.parent.call(this, def);
+ this.m_localAnchorA = def.localAnchorA.Clone();
+ this.m_localAnchorB = def.localAnchorB.Clone();
+ this.m_localXAxisA = def.localAxisA.Clone();
+ this.m_localXAxisA.Normalize();
+ this.m_localYAxisA = b2Cross_f_v2(1, this.m_localXAxisA);
+ this.m_referenceAngle = def.referenceAngle;
+ this.m_impulse = new b2Vec3();
+ this.m_motorMass = 0;
+ this.m_motorImpulse = 0;
+ this.m_lowerTranslation = def.lowerTranslation;
+ this.m_upperTranslation = def.upperTranslation;
+ this.m_maxMotorForce = def.maxMotorForce;
+ this.m_motorSpeed = def.motorSpeed;
+ this.m_enableLimit = def.enableLimit;
+ this.m_enableMotor = def.enableMotor;
+ this.m_limitState = b2Joint.e_inactiveLimit;
+ this.m_axis = new b2Vec2();
+ this.m_perp = new b2Vec2();
+ this.m_indexA = 0;
+ this.m_indexB = 0;
+ this.m_localCenterA = new b2Vec2();
+ this.m_localCenterB = new b2Vec2();
+ this.m_invMassA = 0;
+ this.m_invMassB = 0;
+ this.m_invIA = 0;
+ this.m_invIB = 0;
+ this.m_s1 = 0, this.m_s2 = 0;
+ this.m_a1 = 0, this.m_a2 = 0;
+ this.m_K = new b2Mat33();
+ this.m_motorMass = 0
+}
+b2PrismaticJoint.prototype = {
+ GetAnchorA: function () {
+ return this.m_bodyA.GetWorldPoint(this.m_localAnchorA)
+ },
+ GetAnchorB: function () {
+ return this.m_bodyB.GetWorldPoint(this.m_localAnchorB)
+ },
+ GetReactionForce: function (inv_dt) {
+ return b2Vec2.Multiply(inv_dt, b2Vec2.Add(b2Vec2.Multiply(this.m_impulse.x, this.m_perp), b2Vec2.Multiply((this.m_motorImpulse + this.m_impulse.z), this.m_axis)))
+ },
+ GetReactionTorque: function (inv_dt) {
+ return inv_dt * this.m_impulse.y
+ },
+ GetLocalAnchorA: function () {
+ return this.m_localAnchorA
+ },
+ GetLocalAnchorB: function () {
+ return this.m_localAnchorB
+ },
+ GetLocalAxisA: function () {
+ return this.m_localXAxisA
+ },
+ GetReferenceAngle: function () {
+ return this.m_referenceAngle
+ },
+ GetJointTranslation: function () {
+ var pA = this.m_bodyA.GetWorldPoint(this.m_localAnchorA);
+ var pB = this.m_bodyB.GetWorldPoint(this.m_localAnchorB);
+ var d = b2Vec2.Subtract(pB, pA);
+ var axis = this.m_bodyA.GetWorldVector(this.m_localXAxisA);
+ var translation = b2Dot_v2_v2(d, axis);
+ return translation
+ },
+ GetJointSpeed: function () {
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var rA = b2Mul_r_v2(bA.m_xf.q, b2Vec2.Subtract(this.m_localAnchorA, bA.m_sweep.localCenter));
+ var rB = b2Mul_r_v2(bB.m_xf.q, b2Vec2.Subtract(this.m_localAnchorB, bB.m_sweep.localCenter));
+ var p1 = b2Vec2.Add(bA.m_sweep.c, rA);
+ var p2 = b2Vec2.Add(bB.m_sweep.c, rB);
+ var d = b2Vec2.Subtract(p2, p1);
+ var axis = b2Mul_r_v2(bA.m_xf.q, this.m_localXAxisA);
+ var vA = bA.m_linearVelocity;
+ var vB = bB.m_linearVelocity;
+ var wA = bA.m_angularVelocity;
+ var wB = bB.m_angularVelocity;
+ var speed = b2Dot_v2_v2(d, b2Cross_f_v2(wA, axis)) + b2Dot_v2_v2(axis, b2Vec2.Subtract(b2Vec2.Subtract(b2Vec2.Add(vB, b2Cross_f_v2(wB, rB)), vA), b2Cross_f_v2(wA, rA)));
+ return speed
+ },
+ IsLimitEnabled: function () {
+ return this.m_enableLimit
+ },
+ EnableLimit: function (flag) {
+ if (flag != this.m_enableLimit) {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_enableLimit = flag;
+ this.m_impulse.z = 0
+ }
+ },
+ GetLowerLimit: function () {
+ return this.m_lowerTranslation
+ },
+ GetUpperLimit: function () {
+ return this.m_upperTranslation
+ },
+ SetLimits: function (lower, upper) {
+ if (lower != this.m_lowerTranslation || upper != this.m_upperTranslation) {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_lowerTranslation = lower;
+ this.m_upperTranslation = upper;
+ this.m_impulse.z = 0
+ }
+ },
+ IsMotorEnabled: function () {
+ return this.m_enableMotor
+ },
+ EnableMotor: function (flag) {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_enableMotor = flag
+ },
+ SetMotorSpeed: function (speed) {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_motorSpeed = speed
+ },
+ GetMotorSpeed: function () {
+ return this.m_motorSpeed
+ },
+ SetMaxMotorForce: function (force) {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_maxMotorForce = force
+ },
+ GetMaxMotorForce: function () {
+ return this.m_maxMotorForce
+ },
+ GetMotorForce: function (inv_dt) {
+ return inv_dt * this.m_motorImpulse
+ },
+ InitVelocityConstraints: function (data) {
+ this.m_indexA = this.m_bodyA.m_islandIndex;
+ this.m_indexB = this.m_bodyB.m_islandIndex;
+ this.m_localCenterA = this.m_bodyA.m_sweep.localCenter;
+ this.m_localCenterB = this.m_bodyB.m_sweep.localCenter;
+ this.m_invMassA = this.m_bodyA.m_invMass;
+ this.m_invMassB = this.m_bodyB.m_invMass;
+ this.m_invIA = this.m_bodyA.m_invI;
+ this.m_invIB = this.m_bodyB.m_invI;
+ var cA = data.positions[this.m_indexA].c.Clone();
+ var aA = data.positions[this.m_indexA].a;
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var cB = data.positions[this.m_indexB].c.Clone();
+ var aB = data.positions[this.m_indexB].a;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var qA = new b2Rot(aA),
+ qB = new b2Rot(aB);
+ var rA = b2Mul_r_v2(qA, b2Vec2.Subtract(this.m_localAnchorA, this.m_localCenterA));
+ var rB = b2Mul_r_v2(qB, b2Vec2.Subtract(this.m_localAnchorB, this.m_localCenterB));
+ var d = b2Vec2.Add(b2Vec2.Subtract(cB, cA), b2Vec2.Subtract(rB, rA));
+ var mA = this.m_invMassA,
+ mB = this.m_invMassB;
+ var iA = this.m_invIA,
+ iB = this.m_invIB;
+ this.m_axis = b2Mul_r_v2(qA, this.m_localXAxisA);
+ this.m_a1 = b2Cross_v2_v2(b2Vec2.Add(d, rA), this.m_axis);
+ this.m_a2 = b2Cross_v2_v2(rB, this.m_axis);
+ this.m_motorMass = mA + mB + iA * this.m_a1 * this.m_a1 + iB * this.m_a2 * this.m_a2;
+ if (this.m_motorMass > 0) {
+ this.m_motorMass = 1 / this.m_motorMass
+ }
+ this.m_perp = b2Mul_r_v2(qA, this.m_localYAxisA);
+ this.m_s1 = b2Cross_v2_v2(b2Vec2.Add(d, rA), this.m_perp);
+ this.m_s2 = b2Cross_v2_v2(rB, this.m_perp);
+ var k11 = mA + mB + iA * this.m_s1 * this.m_s1 + iB * this.m_s2 * this.m_s2;
+ var k12 = iA * this.m_s1 + iB * this.m_s2;
+ var k13 = iA * this.m_s1 * this.m_a1 + iB * this.m_s2 * this.m_a2;
+ var k22 = iA + iB;
+ if (k22 == 0) {
+ k22 = 1
+ }
+ var k23 = iA * this.m_a1 + iB * this.m_a2;
+ var k33 = mA + mB + iA * this.m_a1 * this.m_a1 + iB * this.m_a2 * this.m_a2;
+ this.m_K.ex.Set(k11, k12, k13);
+ this.m_K.ey.Set(k12, k22, k23);
+ this.m_K.ez.Set(k13, k23, k33);
+ if (this.m_enableLimit) {
+ var jointTranslation = b2Dot_v2_v2(this.m_axis, d);
+ if (b2Abs(this.m_upperTranslation - this.m_lowerTranslation) < 2 * b2_linearSlop) {
+ this.m_limitState = b2Joint.e_equalLimits
+ } else {
+ if (jointTranslation <= this.m_lowerTranslation) {
+ if (this.m_limitState != b2Joint.e_atLowerLimit) {
+ this.m_limitState = b2Joint.e_atLowerLimit;
+ this.m_impulse.z = 0
+ }
+ } else {
+ if (jointTranslation >= this.m_upperTranslation) {
+ if (this.m_limitState != b2Joint.e_atUpperLimit) {
+ this.m_limitState = b2Joint.e_atUpperLimit;
+ this.m_impulse.z = 0
+ }
+ } else {
+ this.m_limitState = b2Joint.e_inactiveLimit;
+ this.m_impulse.z = 0
+ }
+ }
+ }
+ } else {
+ this.m_limitState = b2Joint.e_inactiveLimit;
+ this.m_impulse.z = 0
+ }
+ if (this.m_enableMotor == false) {
+ this.m_motorImpulse = 0
+ }
+ if (data.step.warmStarting) {
+ this.m_impulse.Multiply(data.step.dtRatio);
+ this.m_motorImpulse *= data.step.dtRatio;
+ var P = b2Vec2.Add(b2Vec2.Multiply(this.m_impulse.x, this.m_perp), b2Vec2.Multiply((this.m_motorImpulse + this.m_impulse.z), this.m_axis));
+ var LA = this.m_impulse.x * this.m_s1 + this.m_impulse.y + (this.m_motorImpulse + this.m_impulse.z) * this.m_a1;
+ var LB = this.m_impulse.x * this.m_s2 + this.m_impulse.y + (this.m_motorImpulse + this.m_impulse.z) * this.m_a2;
+ vA.Subtract(b2Vec2.Multiply(mA, P));
+ wA -= iA * LA;
+ vB.Add(b2Vec2.Multiply(mB, P));
+ wB += iB * LB
+ } else {
+ this.m_impulse.SetZero();
+ this.m_motorImpulse = 0
+ }
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolveVelocityConstraints: function (data) {
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var mA = this.m_invMassA,
+ mB = this.m_invMassB;
+ var iA = this.m_invIA,
+ iB = this.m_invIB;
+ if (this.m_enableMotor && this.m_limitState != b2Joint.e_equalLimits) {
+ var Cdot = b2Dot_v2_v2(this.m_axis, b2Vec2.Subtract(vB, vA)) + this.m_a2 * wB - this.m_a1 * wA;
+ var impulse = this.m_motorMass * (this.m_motorSpeed - Cdot);
+ var oldImpulse = this.m_motorImpulse;
+ var maxImpulse = data.step.dt * this.m_maxMotorForce;
+ this.m_motorImpulse = b2Clamp(this.m_motorImpulse + impulse, -maxImpulse, maxImpulse);
+ impulse = this.m_motorImpulse - oldImpulse;
+ var P = b2Vec2.Multiply(impulse, this.m_axis);
+ var LA = impulse * this.m_a1;
+ var LB = impulse * this.m_a2;
+ vA.Subtract(b2Vec2.Multiply(mA, P));
+ wA -= iA * LA;
+ vB.Add(b2Vec2.Multiply(mB, P));
+ wB += iB * LB
+ }
+ var Cdot1 = new b2Vec2();
+ Cdot1.x = b2Dot_v2_v2(this.m_perp, b2Vec2.Subtract(vB, vA)) + this.m_s2 * wB - this.m_s1 * wA;
+ Cdot1.y = wB - wA;
+ if (this.m_enableLimit && this.m_limitState != b2Joint.e_inactiveLimit) {
+ var Cdot2;
+ Cdot2 = b2Dot_v2_v2(this.m_axis, b2Vec2.Subtract(vB, vA)) + this.m_a2 * wB - this.m_a1 * wA;
+ var Cdot = new b2Vec3(Cdot1.x, Cdot1.y, Cdot2);
+ var f1 = this.m_impulse.Clone();
+ var df = this.m_K.Solve33(Cdot.Negate());
+ this.m_impulse.Add(df);
+ if (this.m_limitState == b2Joint.e_atLowerLimit) {
+ this.m_impulse.z = b2Max(this.m_impulse.z, 0)
+ } else {
+ if (this.m_limitState == b2Joint.e_atUpperLimit) {
+ this.m_impulse.z = b2Min(this.m_impulse.z, 0)
+ }
+ }
+ var b = b2Vec2.Subtract(Cdot1.Negate(), b2Vec2.Multiply((this.m_impulse.z - f1.z), new b2Vec2(this.m_K.ez.x, this.m_K.ez.y)));
+ var f2r = b2Vec2.Add(this.m_K.Solve22(b), new b2Vec2(f1.x, f1.y));
+ this.m_impulse.x = f2r.x;
+ this.m_impulse.y = f2r.y;
+ df = b2Vec3.Subtract(this.m_impulse, f1);
+ var P = b2Vec2.Add(b2Vec2.Multiply(df.x, this.m_perp), b2Vec2.Multiply(df.z, this.m_axis));
+ var LA = df.x * this.m_s1 + df.y + df.z * this.m_a1;
+ var LB = df.x * this.m_s2 + df.y + df.z * this.m_a2;
+ vA.Subtract(b2Vec2.Multiply(mA, P));
+ wA -= iA * LA;
+ vB.Add(b2Vec2.Multiply(mB, P));
+ wB += iB * LB
+ } else {
+ var df = this.m_K.Solve22(Cdot1.Negate());
+ this.m_impulse.x += df.x;
+ this.m_impulse.y += df.y;
+ var P = b2Vec2.Multiply(df.x, this.m_perp);
+ var LA = df.x * this.m_s1 + df.y;
+ var LB = df.x * this.m_s2 + df.y;
+ vA.Subtract(b2Vec2.Multiply(mA, P));
+ wA -= iA * LA;
+ vB.Add(b2Vec2.Multiply(mB, P));
+ wB += iB * LB
+ }
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolvePositionConstraints: function (data) {
+ var cA = data.positions[this.m_indexA].c.Clone();
+ var aA = data.positions[this.m_indexA].a;
+ var cB = data.positions[this.m_indexB].c.Clone();
+ var aB = data.positions[this.m_indexB].a;
+ var qA = new b2Rot(aA),
+ qB = new b2Rot(aB);
+ var mA = this.m_invMassA,
+ mB = this.m_invMassB;
+ var iA = this.m_invIA,
+ iB = this.m_invIB;
+ var rA = b2Mul_r_v2(qA, b2Vec2.Subtract(this.m_localAnchorA, this.m_localCenterA));
+ var rB = b2Mul_r_v2(qB, b2Vec2.Subtract(this.m_localAnchorB, this.m_localCenterB));
+ var d = b2Vec2.Subtract(b2Vec2.Subtract(b2Vec2.Add(cB, rB), cA), rA);
+ var axis = b2Mul_r_v2(qA, this.m_localXAxisA);
+ var a1 = b2Cross_v2_v2(b2Vec2.Add(d, rA), axis);
+ var a2 = b2Cross_v2_v2(rB, axis);
+ var perp = b2Mul_r_v2(qA, this.m_localYAxisA);
+ var s1 = b2Cross_v2_v2(b2Vec2.Add(d, rA), perp);
+ var s2 = b2Cross_v2_v2(rB, perp);
+ var impulse = new b2Vec3();
+ var C1 = new b2Vec2();
+ C1.x = b2Dot_v2_v2(perp, d);
+ C1.y = aB - aA - this.m_referenceAngle;
+ var linearError = b2Abs(C1.x);
+ var angularError = b2Abs(C1.y);
+ var active = false;
+ var C2 = 0;
+ if (this.m_enableLimit) {
+ var translation = b2Dot_v2_v2(axis, d);
+ if (b2Abs(this.m_upperTranslation - this.m_lowerTranslation) < 2 * b2_linearSlop) {
+ C2 = b2Clamp(translation, -b2_maxLinearCorrection, b2_maxLinearCorrection);
+ linearError = b2Max(linearError, b2Abs(translation));
+ active = true
+ } else {
+ if (translation <= this.m_lowerTranslation) {
+ C2 = b2Clamp(translation - this.m_lowerTranslation + b2_linearSlop, -b2_maxLinearCorrection, 0);
+ linearError = b2Max(linearError, this.m_lowerTranslation - translation);
+ active = true
+ } else {
+ if (translation >= this.m_upperTranslation) {
+ C2 = b2Clamp(translation - this.m_upperTranslation - b2_linearSlop, 0, b2_maxLinearCorrection);
+ linearError = b2Max(linearError, translation - this.m_upperTranslation);
+ active = true
+ }
+ }
+ }
+ }
+ if (active) {
+ var k11 = mA + mB + iA * s1 * s1 + iB * s2 * s2;
+ var k12 = iA * s1 + iB * s2;
+ var k13 = iA * s1 * a1 + iB * s2 * a2;
+ var k22 = iA + iB;
+ if (k22 == 0) {
+ k22 = 1
+ }
+ var k23 = iA * a1 + iB * a2;
+ var k33 = mA + mB + iA * a1 * a1 + iB * a2 * a2;
+ var K = new b2Mat33();
+ K.ex.Set(k11, k12, k13);
+ K.ey.Set(k12, k22, k23);
+ K.ez.Set(k13, k23, k33);
+ var C = new b2Vec3();
+ C.x = C1.x;
+ C.y = C1.y;
+ C.z = C2;
+ impulse = K.Solve33(C.Negate())
+ } else {
+ var k11 = mA + mB + iA * s1 * s1 + iB * s2 * s2;
+ var k12 = iA * s1 + iB * s2;
+ var k22 = iA + iB;
+ if (k22 == 0) {
+ k22 = 1
+ }
+ var K = new b2Mat22();
+ K.ex.Set(k11, k12);
+ K.ey.Set(k12, k22);
+ var impulse1 = K.Solve(C1.Negate());
+ impulse.x = impulse1.x;
+ impulse.y = impulse1.y;
+ impulse.z = 0
+ }
+ var P = b2Vec2.Add(b2Vec2.Multiply(impulse.x, perp), b2Vec2.Multiply(impulse.z, axis));
+ var LA = impulse.x * s1 + impulse.y + impulse.z * a1;
+ var LB = impulse.x * s2 + impulse.y + impulse.z * a2;
+ cA.Subtract(b2Vec2.Multiply(mA, P));
+ aA -= iA * LA;
+ cB.Add(b2Vec2.Multiply(mB, P));
+ aB += iB * LB;
+ data.positions[this.m_indexA].c.Assign(cA);
+ data.positions[this.m_indexA].a = aA;
+ data.positions[this.m_indexB].c.Assign(cB);
+ data.positions[this.m_indexB].a = aB;
+ return linearError <= b2_linearSlop && angularError <= b2_angularSlop
+ },
+ _serialize: function (out) {
+ var obj = out || {};
+ this.parent.prototype._serialize.call(this, obj);
+ obj.localAnchorA = this.m_localAnchorA._serialize();
+ obj.localAnchorB = this.m_localAnchorB._serialize();
+ obj.localAxisA = this.m_localXAxisA._serialize();
+ obj.referenceAngle = this.m_referenceAngle;
+ obj.enableLimit = this.m_enableLimit;
+ obj.lowerTranslation = this.m_lowerTranslation;
+ obj.upperTranslation = this.m_upperTranslation;
+ obj.enableMotor = this.m_enableMotor;
+ obj.maxMotorForce = this.m_maxMotorForce;
+ obj.motorSpeed = this.m_motorSpeed;
+ return obj
+ }
+};
+b2PrismaticJoint._extend(b2Joint);
+
+function b2FrictionJointDef() {
+ this.parent.call(this);
+ this.type = b2Joint.e_frictionJoint;
+ this.localAnchorA = new b2Vec2();
+ this.localAnchorB = new b2Vec2();
+ this.maxForce = 0;
+ this.maxTorque = 0;
+ Object.seal(this)
+}
+b2FrictionJointDef.prototype = {
+ Initialize: function (bA, bB, anchor) {
+ this.bodyA = bA;
+ this.bodyB = bB;
+ this.localAnchorA.Assign(this.bodyA.GetLocalPoint(anchor));
+ this.localAnchorB.Assign(this.bodyB.GetLocalPoint(anchor))
+ },
+ _deserialize: function (data, bodies, joints) {
+ this.parent.prototype._deserialize.call(this, data, bodies, joints);
+ this.localAnchorA._deserialize(data.localAnchorA);
+ this.localAnchorB._deserialize(data.localAnchorB);
+ this.maxForce = data.maxForce;
+ this.maxTorque = data.maxTorque
+ }
+};
+b2FrictionJointDef._extend(b2JointDef);
+
+function b2FrictionJoint(def) {
+ this.parent.call(this, def);
+ this.m_localAnchorA = def.localAnchorA.Clone();
+ this.m_localAnchorB = def.localAnchorB.Clone();
+ this.m_linearImpulse = new b2Vec2();
+ this.m_angularImpulse = 0;
+ this.m_maxForce = def.maxForce;
+ this.m_maxTorque = def.maxTorque;
+ this.m_indexA = 0;
+ this.m_indexB = 0;
+ this.m_rA = new b2Vec2();
+ this.m_rB = new b2Vec2();
+ this.m_localCenterA = new b2Vec2();
+ this.m_localCenterB = new b2Vec2();
+ this.m_invMassA = 0;
+ this.m_invMassB = 0;
+ this.m_invIA = 0;
+ this.m_invIB = 0;
+ this.m_linearMass = new b2Mat22();
+ this.m_angularMass = 0
+}
+b2FrictionJoint.prototype = {
+ GetAnchorA: function () {
+ return this.m_bodyA.GetWorldPoint(this.m_localAnchorA)
+ },
+ GetAnchorB: function () {
+ return this.m_bodyB.GetWorldPoint(this.m_localAnchorB)
+ },
+ GetReactionForce: function (inv_dt) {
+ return b2Vec2.Multiply(inv_dt, this.m_linearImpulse)
+ },
+ GetReactionTorque: function (inv_dt) {
+ return inv_dt * this.m_angularImpulse
+ },
+ GetLocalAnchorA: function () {
+ return this.m_localAnchorA
+ },
+ GetLocalAnchorB: function () {
+ return this.m_localAnchorB
+ },
+ SetMaxForce: function (force) {
+ this.m_maxForce = force
+ },
+ GetMaxForce: function () {
+ return this.m_maxForce
+ },
+ SetMaxTorque: function (torque) {
+ this.m_maxTorque = torque
+ },
+ GetMaxTorque: function () {
+ return this.m_maxTorque
+ },
+ InitVelocityConstraints: function (data) {
+ this.m_indexA = this.m_bodyA.m_islandIndex;
+ this.m_indexB = this.m_bodyB.m_islandIndex;
+ this.m_localCenterA.Assign(this.m_bodyA.m_sweep.localCenter);
+ this.m_localCenterB.Assign(this.m_bodyB.m_sweep.localCenter);
+ this.m_invMassA = this.m_bodyA.m_invMass;
+ this.m_invMassB = this.m_bodyB.m_invMass;
+ this.m_invIA = this.m_bodyA.m_invI;
+ this.m_invIB = this.m_bodyB.m_invI;
+ var aA = data.positions[this.m_indexA].a;
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var aB = data.positions[this.m_indexB].a;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var qA = new b2Rot(aA),
+ qB = new b2Rot(aB);
+ this.m_rA = b2Mul_r_v2(qA, b2Vec2.Subtract(this.m_localAnchorA, this.m_localCenterA));
+ this.m_rB = b2Mul_r_v2(qB, b2Vec2.Subtract(this.m_localAnchorB, this.m_localCenterB));
+ var mA = this.m_invMassA,
+ mB = this.m_invMassB;
+ var iA = this.m_invIA,
+ iB = this.m_invIB;
+ var K = new b2Mat22();
+ K.ex.x = mA + mB + iA * this.m_rA.y * this.m_rA.y + iB * this.m_rB.y * this.m_rB.y;
+ K.ex.y = -iA * this.m_rA.x * this.m_rA.y - iB * this.m_rB.x * this.m_rB.y;
+ K.ey.x = K.ex.y;
+ K.ey.y = mA + mB + iA * this.m_rA.x * this.m_rA.x + iB * this.m_rB.x * this.m_rB.x;
+ this.m_linearMass = K.GetInverse();
+ this.m_angularMass = iA + iB;
+ if (this.m_angularMass > 0) {
+ this.m_angularMass = 1 / this.m_angularMass
+ }
+ if (data.step.warmStarting) {
+ this.m_linearImpulse.Multiply(data.step.dtRatio);
+ this.m_angularImpulse *= data.step.dtRatio;
+ var P = new b2Vec2(this.m_linearImpulse.x, this.m_linearImpulse.y);
+ vA.Subtract(b2Vec2.Multiply(mA, P));
+ wA -= iA * (b2Cross_v2_v2(this.m_rA, P) + this.m_angularImpulse);
+ vB.Add(b2Vec2.Multiply(mB, P));
+ wB += iB * (b2Cross_v2_v2(this.m_rB, P) + this.m_angularImpulse)
+ } else {
+ this.m_linearImpulse.SetZero();
+ this.m_angularImpulse = 0
+ }
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolveVelocityConstraints: function (data) {
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var mA = this.m_invMassA,
+ mB = this.m_invMassB;
+ var iA = this.m_invIA,
+ iB = this.m_invIB;
+ var h = data.step.dt;
+ var Cdot = wB - wA;
+ var impulse = -this.m_angularMass * Cdot;
+ var oldImpulse = this.m_angularImpulse;
+ var maxImpulse = h * this.m_maxTorque;
+ this.m_angularImpulse = b2Clamp(this.m_angularImpulse + impulse, -maxImpulse, maxImpulse);
+ impulse = this.m_angularImpulse - oldImpulse;
+ wA -= iA * impulse;
+ wB += iB * impulse;
+ var Cdot = b2Vec2.Add(vB, b2Vec2.Subtract(b2Cross_f_v2(wB, this.m_rB), b2Vec2.Subtract(vA, b2Cross_f_v2(wA, this.m_rA))));
+ var impulse = b2Mul_m22_v2(this.m_linearMass, Cdot).Negate();
+ var oldImpulse = this.m_linearImpulse.Clone();
+ this.m_linearImpulse.Add(impulse);
+ var maxImpulse = h * this.m_maxForce;
+ if (this.m_linearImpulse.LengthSquared() > maxImpulse * maxImpulse) {
+ this.m_linearImpulse.Normalize();
+ this.m_linearImpulse.Multiply(maxImpulse)
+ }
+ impulse = b2Vec2.Subtract(this.m_linearImpulse, oldImpulse);
+ vA.Subtract(b2Vec2.Multiply(mA, impulse));
+ wA -= iA * b2Cross_v2_v2(this.m_rA, impulse);
+ vB.Add(b2Vec2.Multiply(mB, impulse));
+ wB += iB * b2Cross_v2_v2(this.m_rB, impulse);
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolvePositionConstraints: function (data) {
+ return true
+ },
+ _serialize: function (out) {
+ var obj = out || {};
+ this.parent.prototype._serialize.call(this, obj);
+ obj.localAnchorA = this.m_localAnchorA._serialize();
+ obj.localAnchorB = this.m_localAnchorB._serialize();
+ obj.maxForce = this.m_maxForce;
+ obj.maxTorque = this.m_maxTorque;
+ return obj
+ }
+};
+b2FrictionJoint._extend(b2Joint);
+
+function b2WeldJointDef() {
+ this.parent.call(this);
+ this.type = b2Joint.e_weldJoint;
+ this.localAnchorA = new b2Vec2(0, 0);
+ this.localAnchorB = new b2Vec2(0, 0);
+ this.referenceAngle = 0;
+ this.frequencyHz = 0;
+ this.dampingRatio = 0;
+ Object.seal(this)
+}
+b2WeldJointDef.prototype = {
+ Initialize: function (bA, bB, anchor) {
+ this.bodyA = bA;
+ this.bodyB = bB;
+ this.localAnchorA.Assign(this.bodyA.GetLocalPoint(anchor));
+ this.localAnchorB.Assign(this.bodyB.GetLocalPoint(anchor));
+ this.referenceAngle = this.bodyB.GetAngle() - this.bodyA.GetAngle()
+ },
+ _deserialize: function (data, bodies, joints) {
+ this.parent.prototype._deserialize.call(this, data, bodies, joints);
+ this.localAnchorA._deserialize(data.localAnchorA);
+ this.localAnchorB._deserialize(data.localAnchorB);
+ this.referenceAngle = data.referenceAngle;
+ this.frequencyHz = data.frequencyHz;
+ this.dampingRatio = data.dampingRatio
+ }
+};
+b2WeldJointDef._extend(b2JointDef);
+
+function b2WeldJoint(def) {
+ this.parent.call(this, def);
+ this.m_bias = 0;
+ this.m_gamma = 0;
+ this.m_indexA = 0;
+ this.m_indexB = 0;
+ this.m_rA = new b2Vec2();
+ this.m_rB = new b2Vec2();
+ this.m_localCenterA = new b2Vec2();
+ this.m_localCenterB = new b2Vec2();
+ this.m_invMassA = 0;
+ this.m_invMassB = 0;
+ this.m_invIA = 0;
+ this.m_invIB = 0;
+ this.m_mass = new b2Mat33();
+ this.m_localAnchorA = def.localAnchorA.Clone();
+ this.m_localAnchorB = def.localAnchorB.Clone();
+ this.m_referenceAngle = def.referenceAngle;
+ this.m_frequencyHz = def.frequencyHz;
+ this.m_dampingRatio = def.dampingRatio;
+ this.m_impulse = new b2Vec3()
+}
+b2WeldJoint.prototype = {
+ GetAnchorA: function () {
+ return this.m_bodyA.GetWorldPoint(this.m_localAnchorA)
+ },
+ GetAnchorB: function () {
+ return this.m_bodyB.GetWorldPoint(this.m_localAnchorB)
+ },
+ GetReactionForce: function (inv_dt) {
+ var P = new b2Vec2(this.m_impulse.x, this.m_impulse.y);
+ return b2Vec2.Multiply(inv_dt, P)
+ },
+ GetReactionTorque: function (inv_dt) {
+ return inv_dt * this.m_impulse.z
+ },
+ GetLocalAnchorA: function () {
+ return this.m_localAnchorA
+ },
+ GetLocalAnchorB: function () {
+ return this.m_localAnchorB
+ },
+ GetReferenceAngle: function () {
+ return this.m_referenceAngle
+ },
+ SetFrequency: function (hz) {
+ this.m_frequencyHz = hz
+ },
+ GetFrequency: function () {
+ return this.m_frequencyHz
+ },
+ SetDampingRatio: function (ratio) {
+ this.m_dampingRatio = ratio
+ },
+ GetDampingRatio: function () {
+ return this.m_dampingRatio
+ },
+ InitVelocityConstraints: function (data) {
+ this.m_indexA = this.m_bodyA.m_islandIndex;
+ this.m_indexB = this.m_bodyB.m_islandIndex;
+ this.m_localCenterA.Assign(this.m_bodyA.m_sweep.localCenter);
+ this.m_localCenterB.Assign(this.m_bodyB.m_sweep.localCenter);
+ this.m_invMassA = this.m_bodyA.m_invMass;
+ this.m_invMassB = this.m_bodyB.m_invMass;
+ this.m_invIA = this.m_bodyA.m_invI;
+ this.m_invIB = this.m_bodyB.m_invI;
+ var aA = data.positions[this.m_indexA].a;
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var aB = data.positions[this.m_indexB].a;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var qA = new b2Rot(aA),
+ qB = new b2Rot(aB);
+ this.m_rA.Assign(b2Mul_r_v2(qA, b2Vec2.Subtract(this.m_localAnchorA, this.m_localCenterA)));
+ this.m_rB.Assign(b2Mul_r_v2(qB, b2Vec2.Subtract(this.m_localAnchorB, this.m_localCenterB)));
+ var mA = this.m_invMassA,
+ mB = this.m_invMassB;
+ var iA = this.m_invIA,
+ iB = this.m_invIB;
+ var K = new b2Mat33();
+ K.ex.x = mA + mB + this.m_rA.y * this.m_rA.y * iA + this.m_rB.y * this.m_rB.y * iB;
+ K.ey.x = -this.m_rA.y * this.m_rA.x * iA - this.m_rB.y * this.m_rB.x * iB;
+ K.ez.x = -this.m_rA.y * iA - this.m_rB.y * iB;
+ K.ex.y = K.ey.x;
+ K.ey.y = mA + mB + this.m_rA.x * this.m_rA.x * iA + this.m_rB.x * this.m_rB.x * iB;
+ K.ez.y = this.m_rA.x * iA + this.m_rB.x * iB;
+ K.ex.z = K.ez.x;
+ K.ey.z = K.ez.y;
+ K.ez.z = iA + iB;
+ if (this.m_frequencyHz > 0) {
+ K.GetInverse22(this.m_mass);
+ var invM = iA + iB;
+ var m = invM > 0 ? 1 / invM : 0;
+ var C = aB - aA - this.m_referenceAngle;
+ var omega = 2 * b2_pi * this.m_frequencyHz;
+ var d = 2 * m * this.m_dampingRatio * omega;
+ var k = m * omega * omega;
+ var h = data.step.dt;
+ this.m_gamma = h * (d + h * k);
+ this.m_gamma = this.m_gamma != 0 ? 1 / this.m_gamma : 0;
+ this.m_bias = C * h * k * this.m_gamma;
+ invM += this.m_gamma;
+ this.m_mass.ez.z = invM != 0 ? 1 / invM : 0
+ } else {
+ K.GetSymInverse33(this.m_mass);
+ this.m_gamma = 0;
+ this.m_bias = 0
+ }
+ if (data.step.warmStarting) {
+ this.m_impulse.Multiply(data.step.dtRatio);
+ var P = new b2Vec2(this.m_impulse.x, this.m_impulse.y);
+ vA.Subtract(b2Vec2.Multiply(mA, P));
+ wA -= iA * (b2Cross_v2_v2(this.m_rA, P) + this.m_impulse.z);
+ vB.Add(b2Vec2.Multiply(mB, P));
+ wB += iB * (b2Cross_v2_v2(this.m_rB, P) + this.m_impulse.z)
+ } else {
+ this.m_impulse.SetZero()
+ }
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolveVelocityConstraints: function (data) {
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var mA = this.m_invMassA,
+ mB = this.m_invMassB;
+ var iA = this.m_invIA,
+ iB = this.m_invIB;
+ if (this.m_frequencyHz > 0) {
+ var Cdot2 = wB - wA;
+ var impulse2 = -this.m_mass.ez.z * (Cdot2 + this.m_bias + this.m_gamma * this.m_impulse.z);
+ this.m_impulse.z += impulse2;
+ wA -= iA * impulse2;
+ wB += iB * impulse2;
+ var Cdot1 = b2Vec2.Subtract(b2Vec2.Subtract(b2Vec2.Add(vB, b2Cross_f_v2(wB, this.m_rB)), vA), b2Cross_f_v2(wA, this.m_rA));
+ var impulse1 = b2Mul22_m33_v2(this.m_mass, Cdot1).Negate();
+ this.m_impulse.x += impulse1.x;
+ this.m_impulse.y += impulse1.y;
+ var P = impulse1.Clone();
+ vA.Subtract(b2Vec2.Multiply(mA, P));
+ wA -= iA * b2Cross_v2_v2(this.m_rA, P);
+ vB.Add(b2Vec2.Multiply(mB, P));
+ wB += iB * b2Cross_v2_v2(this.m_rB, P)
+ } else {
+ var Cdot1 = b2Vec2.Subtract(b2Vec2.Subtract(b2Vec2.Add(vB, b2Cross_f_v2(wB, this.m_rB)), vA), b2Cross_f_v2(wA, this.m_rA));
+ var Cdot2 = wB - wA;
+ var Cdot = new b2Vec3(Cdot1.x, Cdot1.y, Cdot2);
+ var impulse = b2Mul_m33_v3(this.m_mass, Cdot).Negate();
+ this.m_impulse.Add(impulse);
+ var P = new b2Vec2(impulse.x, impulse.y);
+ vA.Subtract(b2Vec2.Multiply(mA, P));
+ wA -= iA * (b2Cross_v2_v2(this.m_rA, P) + impulse.z);
+ vB.Add(b2Vec2.Multiply(mB, P));
+ wB += iB * (b2Cross_v2_v2(this.m_rB, P) + impulse.z)
+ }
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolvePositionConstraints: function (data) {
+ var cA = data.positions[this.m_indexA].c.Clone();
+ var aA = data.positions[this.m_indexA].a;
+ var cB = data.positions[this.m_indexB].c.Clone();
+ var aB = data.positions[this.m_indexB].a;
+ var qA = new b2Rot(aA),
+ qB = new b2Rot(aB);
+ var mA = this.m_invMassA,
+ mB = this.m_invMassB;
+ var iA = this.m_invIA,
+ iB = this.m_invIB;
+ var rA = b2Mul_r_v2(qA, b2Vec2.Subtract(this.m_localAnchorA, this.m_localCenterA));
+ var rB = b2Mul_r_v2(qB, b2Vec2.Subtract(this.m_localAnchorB, this.m_localCenterB));
+ var positionError, angularError;
+ var K = new b2Mat33();
+ K.ex.x = mA + mB + rA.y * rA.y * iA + rB.y * rB.y * iB;
+ K.ey.x = -rA.y * rA.x * iA - rB.y * rB.x * iB;
+ K.ez.x = -rA.y * iA - rB.y * iB;
+ K.ex.y = K.ey.x;
+ K.ey.y = mA + mB + rA.x * rA.x * iA + rB.x * rB.x * iB;
+ K.ez.y = rA.x * iA + rB.x * iB;
+ K.ex.z = K.ez.x;
+ K.ey.z = K.ez.y;
+ K.ez.z = iA + iB;
+ if (this.m_frequencyHz > 0) {
+ var C1 = b2Vec2.Subtract(b2Vec2.Subtract(b2Vec2.Add(cB, rB), cA), rA);
+ positionError = C1.Length();
+ angularError = 0;
+ var P = K.Solve22(C1).Negate();
+ cA.Subtract(b2Vec2.Multiply(mA, P));
+ aA -= iA * b2Cross_v2_v2(rA, P);
+ cB.Add(b2Vec2.Multiply(mB, P));
+ aB += iB * b2Cross_v2_v2(rB, P)
+ } else {
+ var C1 = b2Vec2.Subtract(b2Vec2.Subtract(b2Vec2.Add(cB, rB), cA), rA);
+ var C2 = aB - aA - this.m_referenceAngle;
+ positionError = C1.Length();
+ angularError = b2Abs(C2);
+ var C = new b2Vec3(C1.x, C1.y, C2);
+ var impulse = K.Solve33(C).Negate();
+ var P = new b2Vec2(impulse.x, impulse.y);
+ cA.Subtract(b2Vec2.Multiply(mA, P));
+ aA -= iA * (b2Cross_v2_v2(rA, P) + impulse.z);
+ cB.Add(b2Vec2.Multiply(mB, P));
+ aB += iB * (b2Cross_v2_v2(rB, P) + impulse.z)
+ }
+ data.positions[this.m_indexA].c.Assign(cA);
+ data.positions[this.m_indexA].a = aA;
+ data.positions[this.m_indexB].c.Assign(cB);
+ data.positions[this.m_indexB].a = aB;
+ return positionError <= b2_linearSlop && angularError <= b2_angularSlop
+ },
+ _serialize: function (out) {
+ var obj = out || {};
+ this.parent.prototype._serialize.call(this, obj);
+ obj.localAnchorA = this.m_localAnchorA._serialize();
+ obj.localAnchorB = this.m_localAnchorB._serialize();
+ obj.referenceAngle = this.m_referenceAngle;
+ obj.frequencyHz = this.m_frequencyHz;
+ obj.dampingRatio = this.m_dampingRatio;
+ return obj
+ }
+};
+b2WeldJoint._extend(b2Joint);
+
+function b2WheelJointDef() {
+ this.parent.call(this);
+ this.type = b2Joint.e_wheelJoint;
+ this.localAnchorA = new b2Vec2();
+ this.localAnchorB = new b2Vec2();
+ this.localAxisA = new b2Vec2(1, 0);
+ this.enableMotor = false;
+ this.maxMotorTorque = 0;
+ this.motorSpeed = 0;
+ this.frequencyHz = 2;
+ this.dampingRatio = 0.7;
+ Object.seal(this)
+}
+b2WheelJointDef.prototype = {
+ Initialize: function (bA, bB, anchor, axis) {
+ this.bodyA = bA;
+ this.bodyB = bB;
+ this.localAnchorA.Assign(this.bodyA.GetLocalPoint(anchor));
+ this.localAnchorB.Assign(this.bodyB.GetLocalPoint(anchor));
+ this.localAxisA.Assign(this.bodyA.GetLocalVector(axis))
+ },
+ _deserialize: function (data, bodies, joints) {
+ this.parent.prototype._deserialize.call(this, data, bodies, joints);
+ this.localAnchorA._deserialize(data.localAnchorA);
+ this.localAnchorB._deserialize(data.localAnchorB);
+ this.localAxisA._deserialize(data.localAxisA);
+ this.enableMotor = data.enableMotor;
+ this.maxMotorTorque = data.maxMotorTorque;
+ this.motorSpeed = data.motorSpeed;
+ this.frequencyHz = data.frequencyHz;
+ this.dampingRatio = data.dampingRatio
+ }
+};
+b2WheelJointDef._extend(b2JointDef);
+
+function b2WheelJoint(def) {
+ this.parent.call(this, def);
+ this.m_indexA = 0;
+ this.m_indexB = 0;
+ this.m_localCenterA = new b2Vec2();
+ this.m_localCenterB = new b2Vec2();
+ this.m_invMassA = 0;
+ this.m_invMassB = 0;
+ this.m_invIA = 0;
+ this.m_invIB = 0;
+ this.m_localAnchorA = def.localAnchorA.Clone();
+ this.m_localAnchorB = def.localAnchorB.Clone();
+ this.m_localXAxisA = def.localAxisA.Clone();
+ this.m_localYAxisA = b2Cross_f_v2(1, this.m_localXAxisA);
+ this.m_mass = 0;
+ this.m_impulse = 0;
+ this.m_motorMass = 0;
+ this.m_motorImpulse = 0;
+ this.m_springMass = 0;
+ this.m_springImpulse = 0;
+ this.m_maxMotorTorque = def.maxMotorTorque;
+ this.m_motorSpeed = def.motorSpeed;
+ this.m_enableMotor = def.enableMotor;
+ this.m_frequencyHz = def.frequencyHz;
+ this.m_dampingRatio = def.dampingRatio;
+ this.m_bias = 0;
+ this.m_gamma = 0;
+ this.m_ax = new b2Vec2();
+ this.m_ay = new b2Vec2();
+ this.m_sAx = this.m_sBx = 0;
+ this.m_sAy = this.m_sBy = 0
+}
+b2WheelJoint.prototype = {
+ GetAnchorA: function () {
+ return this.m_bodyA.GetWorldPoint(this.m_localAnchorA)
+ },
+ GetAnchorB: function () {
+ return this.m_bodyB.GetWorldPoint(this.m_localAnchorB)
+ },
+ GetReactionForce: function (inv_dt) {
+ return b2Vec2.Multiply(inv_dt, b2Vec2.Add(b2Vec2.Multiply(this.m_impulse, this.m_ay), b2Vec2.Multiply(this.m_springImpulse, this.m_ax)))
+ },
+ GetReactionTorque: function (inv_dt) {
+ return inv_dt * this.m_motorImpulse
+ },
+ GetLocalAnchorA: function () {
+ return this.m_localAnchorA
+ },
+ GetLocalAnchorB: function () {
+ return this.m_localAnchorB
+ },
+ GetLocalAxisA: function () {
+ return this.m_localXAxisA
+ },
+ GetJointTranslation: function () {
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var pA = bA.GetWorldPoint(this.m_localAnchorA);
+ var pB = bB.GetWorldPoint(this.m_localAnchorB);
+ var d = b2Vec2.Subtract(pB, pA);
+ var axis = bA.GetWorldVector(this.m_localXAxisA);
+ var translation = b2Dot_v2_v2(d, axis);
+ return translation
+ },
+ GetJointSpeed: function () {
+ var wA = this.m_bodyA.m_angularVelocity;
+ var wB = this.m_bodyB.m_angularVelocity;
+ return wB - wA
+ },
+ IsMotorEnabled: function () {
+ return this.m_enableMotor
+ },
+ EnableMotor: function (flag) {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_enableMotor = flag
+ },
+ SetMotorSpeed: function (speed) {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_motorSpeed = speed
+ },
+ GetMotorSpeed: function () {
+ return this.m_motorSpeed
+ },
+ SetMaxMotorTorque: function (torque) {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_maxMotorTorque = torque
+ },
+ GetMaxMotorTorque: function () {
+ return this.m_maxMotorTorque
+ },
+ GetMotorTorque: function (inv_dt) {
+ return inv_dt * this.m_motorImpulse
+ },
+ SetSpringFrequencyHz: function (hz) {
+ this.m_frequencyHz = hz
+ },
+ GetSpringFrequencyHz: function () {
+ return this.m_frequencyHz
+ },
+ SetSpringDampingRatio: function (ratio) {
+ this.m_dampingRatio = ratio
+ },
+ GetSpringDampingRatio: function () {
+ return this.m_dampingRatio
+ },
+ InitVelocityConstraints: function (data) {
+ this.m_indexA = this.m_bodyA.m_islandIndex;
+ this.m_indexB = this.m_bodyB.m_islandIndex;
+ this.m_localCenterA.Assign(this.m_bodyA.m_sweep.localCenter);
+ this.m_localCenterB.Assign(this.m_bodyB.m_sweep.localCenter);
+ this.m_invMassA = this.m_bodyA.m_invMass;
+ this.m_invMassB = this.m_bodyB.m_invMass;
+ this.m_invIA = this.m_bodyA.m_invI;
+ this.m_invIB = this.m_bodyB.m_invI;
+ var mA = this.m_invMassA,
+ mB = this.m_invMassB;
+ var iA = this.m_invIA,
+ iB = this.m_invIB;
+ var cA = data.positions[this.m_indexA].c.Clone();
+ var aA = data.positions[this.m_indexA].a;
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var cB = data.positions[this.m_indexB].c.Clone();
+ var aB = data.positions[this.m_indexB].a;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var qA = new b2Rot(aA),
+ qB = new b2Rot(aB);
+ var rA = b2Mul_r_v2(qA, b2Vec2.Subtract(this.m_localAnchorA, this.m_localCenterA));
+ var rB = b2Mul_r_v2(qB, b2Vec2.Subtract(this.m_localAnchorB, this.m_localCenterB));
+ var d = b2Vec2.Subtract(b2Vec2.Subtract(b2Vec2.Add(cB, rB), cA), rA);
+ this.m_ay.Assign(b2Mul_r_v2(qA, this.m_localYAxisA));
+ this.m_sAy = b2Cross_v2_v2(b2Vec2.Add(d, rA), this.m_ay);
+ this.m_sBy = b2Cross_v2_v2(rB, this.m_ay);
+ this.m_mass = mA + mB + iA * this.m_sAy * this.m_sAy + iB * this.m_sBy * this.m_sBy;
+ if (this.m_mass > 0) {
+ this.m_mass = 1 / this.m_mass
+ }
+ this.m_springMass = 0;
+ this.m_bias = 0;
+ this.m_gamma = 0;
+ if (this.m_frequencyHz > 0) {
+ this.m_ax.Assign(b2Mul_r_v2(qA, this.m_localXAxisA));
+ this.m_sAx = b2Cross_v2_v2(b2Vec2.Add(d, rA), this.m_ax);
+ this.m_sBx = b2Cross_v2_v2(rB, this.m_ax);
+ var invMass = mA + mB + iA * this.m_sAx * this.m_sAx + iB * this.m_sBx * this.m_sBx;
+ if (invMass > 0) {
+ this.m_springMass = 1 / invMass;
+ var C = b2Dot_v2_v2(d, this.m_ax);
+ var omega = 2 * b2_pi * this.m_frequencyHz;
+ var d = 2 * this.m_springMass * this.m_dampingRatio * omega;
+ var k = this.m_springMass * omega * omega;
+ var h = data.step.dt;
+ this.m_gamma = h * (d + h * k);
+ if (this.m_gamma > 0) {
+ this.m_gamma = 1 / this.m_gamma
+ }
+ this.m_bias = C * h * k * this.m_gamma;
+ this.m_springMass = invMass + this.m_gamma;
+ if (this.m_springMass > 0) {
+ this.m_springMass = 1 / this.m_springMass
+ }
+ }
+ } else {
+ this.m_springImpulse = 0
+ }
+ if (this.m_enableMotor) {
+ this.m_motorMass = iA + iB;
+ if (this.m_motorMass > 0) {
+ this.m_motorMass = 1 / this.m_motorMass
+ }
+ } else {
+ this.m_motorMass = 0;
+ this.m_motorImpulse = 0
+ }
+ if (data.step.warmStarting) {
+ this.m_impulse *= data.step.dtRatio;
+ this.m_springImpulse *= data.step.dtRatio;
+ this.m_motorImpulse *= data.step.dtRatio;
+ var P = b2Vec2.Add(b2Vec2.Multiply(this.m_impulse, this.m_ay), b2Vec2.Multiply(this.m_springImpulse, this.m_ax));
+ var LA = this.m_impulse * this.m_sAy + this.m_springImpulse * this.m_sAx + this.m_motorImpulse;
+ var LB = this.m_impulse * this.m_sBy + this.m_springImpulse * this.m_sBx + this.m_motorImpulse;
+ vA.Subtract(b2Vec2.Multiply(this.m_invMassA, P));
+ wA -= this.m_invIA * LA;
+ vB.Add(b2Vec2.Multiply(this.m_invMassB, P));
+ wB += this.m_invIB * LB
+ } else {
+ this.m_impulse = 0;
+ this.m_springImpulse = 0;
+ this.m_motorImpulse = 0
+ }
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolveVelocityConstraints: function (data) {
+ var mA = this.m_invMassA,
+ mB = this.m_invMassB;
+ var iA = this.m_invIA,
+ iB = this.m_invIB;
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var Cdot = b2Dot_v2_v2(this.m_ax, b2Vec2.Subtract(vB, vA)) + this.m_sBx * wB - this.m_sAx * wA;
+ var impulse = -this.m_springMass * (Cdot + this.m_bias + this.m_gamma * this.m_springImpulse);
+ this.m_springImpulse += impulse;
+ var P = b2Vec2.Multiply(impulse, this.m_ax);
+ var LA = impulse * this.m_sAx;
+ var LB = impulse * this.m_sBx;
+ vA.Subtract(b2Vec2.Multiply(mA, P));
+ wA -= iA * LA;
+ vB.Add(b2Vec2.Multiply(mB, P));
+ wB += iB * LB;
+ var Cdot = wB - wA - this.m_motorSpeed;
+ var impulse = -this.m_motorMass * Cdot;
+ var oldImpulse = this.m_motorImpulse;
+ var maxImpulse = data.step.dt * this.m_maxMotorTorque;
+ this.m_motorImpulse = b2Clamp(this.m_motorImpulse + impulse, -maxImpulse, maxImpulse);
+ impulse = this.m_motorImpulse - oldImpulse;
+ wA -= iA * impulse;
+ wB += iB * impulse;
+ var Cdot = b2Dot_v2_v2(this.m_ay, b2Vec2.Subtract(vB, vA)) + this.m_sBy * wB - this.m_sAy * wA;
+ var impulse = -this.m_mass * Cdot;
+ this.m_impulse += impulse;
+ var P = b2Vec2.Multiply(impulse, this.m_ay);
+ var LA = impulse * this.m_sAy;
+ var LB = impulse * this.m_sBy;
+ vA.Subtract(b2Vec2.Multiply(mA, P));
+ wA -= iA * LA;
+ vB.Add(b2Vec2.Multiply(mB, P));
+ wB += iB * LB;
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolvePositionConstraints: function (data) {
+ var cA = data.positions[this.m_indexA].c.Clone();
+ var aA = data.positions[this.m_indexA].a;
+ var cB = data.positions[this.m_indexB].c.Clone();
+ var aB = data.positions[this.m_indexB].a;
+ var qA = new b2Rot(aA),
+ qB = new b2Rot(aB);
+ var rA = b2Mul_r_v2(qA, b2Vec2.Subtract(this.m_localAnchorA, this.m_localCenterA));
+ var rB = b2Mul_r_v2(qB, b2Vec2.Subtract(this.m_localAnchorB, this.m_localCenterB));
+ var d = b2Vec2.Add(b2Vec2.Subtract(cB, cA), b2Vec2.Subtract(rB, rA));
+ var ay = b2Mul_r_v2(qA, this.m_localYAxisA);
+ var sAy = b2Cross_v2_v2(b2Vec2.Add(d, rA), ay);
+ var sBy = b2Cross_v2_v2(rB, ay);
+ var C = b2Dot_v2_v2(d, ay);
+ var k = this.m_invMassA + this.m_invMassB + this.m_invIA * this.m_sAy * this.m_sAy + this.m_invIB * this.m_sBy * this.m_sBy;
+ var impulse;
+ if (k != 0) {
+ impulse = -C / k
+ } else {
+ impulse = 0
+ }
+ var P = b2Vec2.Multiply(impulse, ay);
+ var LA = impulse * sAy;
+ var LB = impulse * sBy;
+ cA.Subtract(b2Vec2.Multiply(this.m_invMassA, P));
+ aA -= this.m_invIA * LA;
+ cB.Add(b2Vec2.Multiply(this.m_invMassB, P));
+ aB += this.m_invIB * LB;
+ data.positions[this.m_indexA].c.Assign(cA);
+ data.positions[this.m_indexA].a = aA;
+ data.positions[this.m_indexB].c.Assign(cB);
+ data.positions[this.m_indexB].a = aB;
+ return b2Abs(C) <= b2_linearSlop
+ },
+ _serialize: function (out) {
+ var obj = out || {};
+ this.parent.prototype._serialize.call(this, obj);
+ obj.localAnchorA = this.m_localAnchorA._serialize();
+ obj.localAnchorB = this.m_localAnchorB._serialize();
+ obj.localAxisA = this.m_localAxisA._serialize();
+ obj.enableMotor = this.m_enableMotor;
+ obj.maxMotorTorque = this.m_maxMotorTorque;
+ obj.motorSpeed = this.m_motorSpeed;
+ obj.frequencyHz = this.m_frequencyHz;
+ obj.dampingRatio = this.m_dampingRatio;
+ return obj
+ }
+};
+b2WheelJoint._extend(b2Joint);
+
+function b2GearJointDef() {
+ this.parent.call(this);
+ this.type = b2Joint.e_gearJoint;
+ this.joint1 = null;
+ this.joint2 = null;
+ this.ratio = 1;
+ Object.seal(this)
+}
+b2GearJointDef.prototype = {
+ _deserialize: function (data, bodies, joints) {
+ this.parent.prototype._deserialize.call(this, data, bodies, joints);
+ this.joint1 = data.joint1;
+ this.joint2 = data.joint2;
+ this.ratio = data.ratio
+ }
+};
+b2GearJointDef._extend(b2JointDef);
+
+function b2GearJoint(def) {
+ this.parent.call(this, def);
+ this.m_joint1 = def.joint1;
+ this.m_joint2 = def.joint2;
+ this.m_typeA = this.m_joint1.GetType();
+ this.m_typeB = this.m_joint2.GetType();
+ var coordinateA, coordinateB;
+ this.m_bodyC = this.m_joint1.GetBodyA();
+ this.m_bodyA = this.m_joint1.GetBodyB();
+ var xfA = this.m_bodyA.m_xf;
+ var aA = this.m_bodyA.m_sweep.a;
+ var xfC = this.m_bodyC.m_xf;
+ var aC = this.m_bodyC.m_sweep.a;
+ this.m_localAnchorA = new b2Vec2();
+ this.m_localAnchorB = new b2Vec2();
+ this.m_localAnchorC = new b2Vec2();
+ this.m_localAnchorD = new b2Vec2();
+ this.m_localAxisC = new b2Vec2();
+ this.m_localAxisD = new b2Vec2();
+ if (this.m_typeA == b2Joint.e_revoluteJoint) {
+ var revolute = def.joint1;
+ this.m_localAnchorC.Assign(revolute.m_localAnchorA);
+ this.m_localAnchorA.Assign(revolute.m_localAnchorB);
+ this.m_referenceAngleA = revolute.m_referenceAngle;
+ this.m_localAxisC.SetZero();
+ coordinateA = aA - aC - this.m_referenceAngleA
+ } else {
+ var prismatic = def.joint1;
+ this.m_localAnchorC.Assign(prismatic.m_localAnchorA);
+ this.m_localAnchorA.Assign(prismatic.m_localAnchorB);
+ this.m_referenceAngleA = prismatic.m_referenceAngle;
+ this.m_localAxisC.Assign(prismatic.m_localXAxisA);
+ var pC = this.m_localAnchorC;
+ var pA = b2MulT_r_v2(xfC.q, b2Vec2.Add(b2Mul_r_v2(xfA.q, this.m_localAnchorA), b2Vec2.Subtract(xfA.p, xfC.p)));
+ coordinateA = b2Dot_v2_v2(b2Vec2.Subtract(pA, pC), this.m_localAxisC)
+ }
+ this.m_bodyD = this.m_joint2.GetBodyA();
+ this.m_bodyB = this.m_joint2.GetBodyB();
+ var xfB = this.m_bodyB.m_xf;
+ var aB = this.m_bodyB.m_sweep.a;
+ var xfD = this.m_bodyD.m_xf;
+ var aD = this.m_bodyD.m_sweep.a;
+ if (this.m_typeB == b2Joint.e_revoluteJoint) {
+ var revolute = def.joint2;
+ this.m_localAnchorD.Assign(revolute.m_localAnchorA);
+ this.m_localAnchorB.Assign(revolute.m_localAnchorB);
+ this.m_referenceAngleB = revolute.m_referenceAngle;
+ this.m_localAxisD.SetZero();
+ coordinateB = aB - aD - this.m_referenceAngleB
+ } else {
+ var prismatic = def.joint2;
+ this.m_localAnchorD.Assign(prismatic.m_localAnchorA);
+ this.m_localAnchorB.Assign(prismatic.m_localAnchorB);
+ this.m_referenceAngleB = prismatic.m_referenceAngle;
+ this.m_localAxisD.Assign(prismatic.m_localXAxisA);
+ var pD = this.m_localAnchorD;
+ var pB = b2MulT_r_v2(xfD.q, b2Vec2.Add(b2Mul_r_v2(xfB.q, this.m_localAnchorB), b2Vec2.Subtract(xfB.p, xfD.p)));
+ coordinateB = b2Dot_v2_v2(b2Vec2.Subtract(pB, pD), this.m_localAxisD)
+ }
+ this.m_ratio = def.ratio;
+ this.m_constant = coordinateA + this.m_ratio * coordinateB;
+ this.m_impulse = 0;
+ this.m_indexA = this.m_indexB = this.m_indexC = this.m_indexD = 0;
+ this.m_lcA = new b2Vec2();
+ this.m_lcB = new b2Vec2();
+ this.m_lcC = new b2Vec2();
+ this.m_lcD = new b2Vec2();
+ this.m_mA = this.m_mB = this.m_mC = this.m_mD = 0;
+ this.m_iA = this.m_iB = this.m_iC = this.m_iD = 0;
+ this.m_JvAC = new b2Vec2(), this.m_JvBD = new b2Vec2();
+ this.m_JwA = this.m_JwB = this.m_JwC = this.m_JwD = 0;
+ this.m_mass = 0
+}
+b2GearJoint.prototype = {
+ GetAnchorA: function () {
+ return this.m_bodyA.GetWorldPoint(this.m_localAnchorA)
+ },
+ GetAnchorB: function () {
+ return this.m_bodyB.GetWorldPoint(this.m_localAnchorB)
+ },
+ GetReactionForce: function (inv_dt) {
+ var P = b2Vec2.Multiply(this.m_impulse, this.m_JvAC);
+ return b2Vec2.Multiply(inv_dt, P)
+ },
+ GetReactionTorque: function (inv_dt) {
+ var L = this.m_impulse * this.m_JwA;
+ return inv_dt * L
+ },
+ GetJoint1: function () {
+ return this.m_joint1
+ },
+ GetJoint2: function () {
+ return this.m_joint2
+ },
+ SetRatio: function (ratio) {
+ this.m_ratio = ratio
+ },
+ GetRatio: function () {
+ return this.m_ratio
+ },
+ InitVelocityConstraints: function (data) {
+ this.m_indexA = this.m_bodyA.m_islandIndex;
+ this.m_indexB = this.m_bodyB.m_islandIndex;
+ this.m_indexC = this.m_bodyC.m_islandIndex;
+ this.m_indexD = this.m_bodyD.m_islandIndex;
+ this.m_lcA.Assign(this.m_bodyA.m_sweep.localCenter);
+ this.m_lcB.Assign(this.m_bodyB.m_sweep.localCenter);
+ this.m_lcC.Assign(this.m_bodyC.m_sweep.localCenter);
+ this.m_lcD.Assign(this.m_bodyD.m_sweep.localCenter);
+ this.m_mA = this.m_bodyA.m_invMass;
+ this.m_mB = this.m_bodyB.m_invMass;
+ this.m_mC = this.m_bodyC.m_invMass;
+ this.m_mD = this.m_bodyD.m_invMass;
+ this.m_iA = this.m_bodyA.m_invI;
+ this.m_iB = this.m_bodyB.m_invI;
+ this.m_iC = this.m_bodyC.m_invI;
+ this.m_iD = this.m_bodyD.m_invI;
+ var aA = data.positions[this.m_indexA].a;
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var aB = data.positions[this.m_indexB].a;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var aC = data.positions[this.m_indexC].a;
+ var vC = data.velocities[this.m_indexC].v.Clone();
+ var wC = data.velocities[this.m_indexC].w;
+ var aD = data.positions[this.m_indexD].a;
+ var vD = data.velocities[this.m_indexD].v.Clone();
+ var wD = data.velocities[this.m_indexD].w;
+ var qA = new b2Rot(aA),
+ qB = new b2Rot(aB),
+ qC = new b2Rot(aC),
+ qD = new b2Rot(aD);
+ this.m_mass = 0;
+ if (this.m_typeA == b2Joint.e_revoluteJoint) {
+ this.m_JvAC.SetZero();
+ this.m_JwA = 1;
+ this.m_JwC = 1;
+ this.m_mass += this.m_iA + this.m_iC
+ } else {
+ var u = b2Mul_r_v2(qC, this.m_localAxisC);
+ var rC = b2Mul_r_v2(qC, b2Vec2.Subtract(this.m_localAnchorC, this.m_lcC));
+ var rA = b2Mul_r_v2(qA, b2Vec2.Subtract(this.m_localAnchorA, this.m_lcA));
+ this.m_JvAC.Assign(u);
+ this.m_JwC = b2Cross_v2_v2(rC, u);
+ this.m_JwA = b2Cross_v2_v2(rA, u);
+ this.m_mass += this.m_mC + this.m_mA + this.m_iC * this.m_JwC * this.m_JwC + this.m_iA * this.m_JwA * this.m_JwA
+ }
+ if (this.m_typeB == b2Joint.e_revoluteJoint) {
+ this.m_JvBD.SetZero();
+ this.m_JwB = this.m_ratio;
+ this.m_JwD = this.m_ratio;
+ this.m_mass += this.m_ratio * this.m_ratio * (this.m_iB + this.m_iD)
+ } else {
+ var u = b2Mul_r_v2(qD, this.m_localAxisD);
+ var rD = b2Mul_r_v2(qD, b2Vec2.Subtract(this.m_localAnchorD, this.m_lcD));
+ var rB = b2Mul_r_v2(qB, b2Vec2.Subtract(this.m_localAnchorB, this.m_lcB));
+ this.m_JvBD.Assign(b2Vec2.Multiply(this.m_ratio, u));
+ this.m_JwD = this.m_ratio * b2Cross_v2_v2(rD, u);
+ this.m_JwB = this.m_ratio * b2Cross_v2_v2(rB, u);
+ this.m_mass += this.m_ratio * this.m_ratio * (this.m_mD + this.m_mB) + this.m_iD * this.m_JwD * this.m_JwD + this.m_iB * this.m_JwB * this.m_JwB
+ }
+ this.m_mass = this.m_mass > 0 ? 1 / this.m_mass : 0;
+ if (data.step.warmStarting) {
+ vA.Add(b2Vec2.Multiply((this.m_mA * this.m_impulse), this.m_JvAC));
+ wA += this.m_iA * this.m_impulse * this.m_JwA;
+ vB.Add(b2Vec2.Multiply((this.m_mB * this.m_impulse), this.m_JvBD));
+ wB += this.m_iB * this.m_impulse * this.m_JwB;
+ vC.Subtract(b2Vec2.Multiply((this.m_mC * this.m_impulse), this.m_JvAC));
+ wC -= this.m_iC * this.m_impulse * this.m_JwC;
+ vD.Subtract(b2Vec2.Multiply((this.m_mD * this.m_impulse), this.m_JvBD));
+ wD -= this.m_iD * this.m_impulse * this.m_JwD
+ } else {
+ this.m_impulse = 0
+ }
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB;
+ data.velocities[this.m_indexC].v.Assign(vC);
+ data.velocities[this.m_indexC].w = wC;
+ data.velocities[this.m_indexD].v.Assign(vD);
+ data.velocities[this.m_indexD].w = wD
+ },
+ SolveVelocityConstraints: function (data) {
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var vC = data.velocities[this.m_indexC].v.Clone();
+ var wC = data.velocities[this.m_indexC].w;
+ var vD = data.velocities[this.m_indexD].v.Clone();
+ var wD = data.velocities[this.m_indexD].w;
+ var Cdot = b2Dot_v2_v2(this.m_JvAC, b2Vec2.Subtract(vA, vC)) + b2Dot_v2_v2(this.m_JvBD, b2Vec2.Subtract(vB, vD));
+ Cdot += (this.m_JwA * wA - this.m_JwC * wC) + (this.m_JwB * wB - this.m_JwD * wD);
+ var impulse = -this.m_mass * Cdot;
+ this.m_impulse += impulse;
+ vA.Add(b2Vec2.Multiply((this.m_mA * impulse), this.m_JvAC));
+ wA += this.m_iA * impulse * this.m_JwA;
+ vB.Add(b2Vec2.Multiply((this.m_mB * impulse), this.m_JvBD));
+ wB += this.m_iB * impulse * this.m_JwB;
+ vC.Subtract(b2Vec2.Multiply((this.m_mC * impulse), this.m_JvAC));
+ wC -= this.m_iC * impulse * this.m_JwC;
+ vD.Subtract(b2Vec2.Multiply((this.m_mD * impulse), this.m_JvBD));
+ wD -= this.m_iD * impulse * this.m_JwD;
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB;
+ data.velocities[this.m_indexC].v.Assign(vC);
+ data.velocities[this.m_indexC].w = wC;
+ data.velocities[this.m_indexD].v.Assign(vD);
+ data.velocities[this.m_indexD].w = wD
+ },
+ SolvePositionConstraints: function (data) {
+ var cA = data.positions[this.m_indexA].c.Clone();
+ var aA = data.positions[this.m_indexA].a;
+ var cB = data.positions[this.m_indexB].c.Clone();
+ var aB = data.positions[this.m_indexB].a;
+ var cC = data.positions[this.m_indexC].c.Clone();
+ var aC = data.positions[this.m_indexC].a;
+ var cD = data.positions[this.m_indexD].c.Clone();
+ var aD = data.positions[this.m_indexD].a;
+ var qA = new b2Rot(aA),
+ qB = new b2Rot(aB),
+ qC = new b2Rot(aC),
+ qD = new b2Rot(aD);
+ var linearError = 0;
+ var coordinateA, coordinateB;
+ var JvAC = new b2Vec2(),
+ JvBD = new b2Vec2();
+ var JwA, JwB, JwC, JwD;
+ var mass = 0;
+ if (this.m_typeA == b2Joint.e_revoluteJoint) {
+ JvAC.SetZero();
+ JwA = 1;
+ JwC = 1;
+ mass += this.m_iA + this.m_iC;
+ coordinateA = aA - aC - this.m_referenceAngleA
+ } else {
+ var u = b2Mul_r_v2(qC, this.m_localAxisC);
+ var rC = b2Mul_r_v2(qC, b2Vec2.Subtract(this.m_localAnchorC, this.m_lcC));
+ var rA = b2Mul_r_v2(qA, b2Vec2.Subtract(this.m_localAnchorA, this.m_lcA));
+ JvAC.Assign(u);
+ JwC = b2Cross_v2_v2(rC, u);
+ JwA = b2Cross_v2_v2(rA, u);
+ mass += this.m_mC + this.m_mA + this.m_iC * JwC * JwC + this.m_iA * JwA * JwA;
+ var pC = b2Vec2.Subtract(this.m_localAnchorC, this.m_lcC);
+ var pA = b2MulT_r_v2(qC, b2Vec2.Add(rA, b2Vec2.Subtract(cA, cC)));
+ coordinateA = b2Dot_v2_v2(b2Vec2.Subtract(pA, pC), this.m_localAxisC)
+ }
+ if (this.m_typeB == b2Joint.e_revoluteJoint) {
+ JvBD.SetZero();
+ JwB = this.m_ratio;
+ JwD = this.m_ratio;
+ mass += this.m_ratio * this.m_ratio * (this.m_iB + this.m_iD);
+ coordinateB = aB - aD - this.m_referenceAngleB
+ } else {
+ var u = b2Mul_r_v2(qD, this.m_localAxisD);
+ var rD = b2Mul_r_v2(qD, b2Vec2.Subtract(this.m_localAnchorD, this.m_lcD));
+ var rB = b2Mul_r_v2(qB, b2Vec2.Subtract(this.m_localAnchorB, this.m_lcB));
+ JvBD.Assign(b2Vec2.Multiply(this.m_ratio, u));
+ JwD = this.m_ratio * b2Cross_v2_v2(rD, u);
+ JwB = this.m_ratio * b2Cross_v2_v2(rB, u);
+ mass += this.m_ratio * this.m_ratio * (this.m_mD + this.m_mB) + this.m_iD * JwD * JwD + this.m_iB * JwB * JwB;
+ var pD = b2Vec2.Subtract(this.m_localAnchorD, this.m_lcD);
+ var pB = b2MulT_r_v2(qD, b2Vec2.Add(rB, b2Vec2.Subtract(cB, cD)));
+ coordinateB = b2Dot_v2_v2(b2Vec2.Subtract(pB, pD), this.m_localAxisD)
+ }
+ var C = (coordinateA + this.m_ratio * coordinateB) - this.m_constant;
+ var impulse = 0;
+ if (mass > 0) {
+ impulse = -C / mass
+ }
+ cA.Add(b2Vec2.Multiply(this.m_mA, b2Vec2.Multiply(impulse, JvAC)));
+ aA += this.m_iA * impulse * JwA;
+ cB.Add(b2Vec2.Multiply(this.m_mB, b2Vec2.Multiply(impulse, JvBD)));
+ aB += this.m_iB * impulse * JwB;
+ cC.Subtract(b2Vec2.Multiply(this.m_mC, b2Vec2.Multiply(impulse, JvAC)));
+ aC -= this.m_iC * impulse * JwC;
+ cD.Subtract(b2Vec2.Multiply(this.m_mD, b2Vec2.Multiply(impulse, JvBD)));
+ aD -= this.m_iD * impulse * JwD;
+ data.positions[this.m_indexA].c.Assign(cA);
+ data.positions[this.m_indexA].a = aA;
+ data.positions[this.m_indexB].c.Assign(cB);
+ data.positions[this.m_indexB].a = aB;
+ data.positions[this.m_indexC].c.Assign(cC);
+ data.positions[this.m_indexC].a = aC;
+ data.positions[this.m_indexD].c.Assign(cD);
+ data.positions[this.m_indexD].a = aD;
+ return linearError < b2_linearSlop
+ },
+ _serialize: function (out) {
+ var obj = out || {};
+ this.parent.prototype._serialize.call(this, obj);
+ obj.joint1 = this.m_joint1.__temp_joint_id;
+ obj.joint2 = this.m_joint2.__temp_joint_id;
+ obj.ratio = this.m_ratio;
+ return obj
+ }
+};
+b2GearJoint._extend(b2Joint);
+
+function b2MotorJointDef() {
+ this.parent.call(this);
+ this.type = b2Joint.e_motorJoint;
+ this.linearOffset = new b2Vec2();
+ this.angularOffset = 0;
+ this.maxForce = 1;
+ this.maxTorque = 1;
+ this.correctionFactor = 0.3;
+ Object.seal(this)
+}
+b2MotorJointDef.prototype = {
+ Initialize: function (bA, bB) {
+ this.bodyA = bA;
+ this.bodyB = bB;
+ var xB = this.bodyB.GetPosition();
+ this.linearOffset.Assign(this.bodyA.GetLocalPoint(xB));
+ var angleA = this.bodyA.GetAngle();
+ var angleB = this.bodyB.GetAngle();
+ this.angularOffset = angleB - angleA
+ },
+ _deserialize: function (data, bodies, joints) {
+ this.parent.prototype._deserialize.call(this, data, bodies, joints);
+ this.linearOffset._deserialize(data.linearOffset);
+ this.angularOffset = data.angularOffset;
+ this.maxForce = data.maxForce;
+ this.maxTorque = data.maxTorque;
+ this.correctionFactor = data.correctionFactor
+ }
+};
+b2MotorJointDef._extend(b2JointDef);
+
+function b2MotorJoint(def) {
+ this.parent.call(this, def);
+ this.m_linearOffset = def.linearOffset.Clone();
+ this.m_angularOffset = def.angularOffset;
+ this.m_linearImpulse = new b2Vec2();
+ this.m_angularImpulse = 0;
+ this.m_maxForce = def.maxForce;
+ this.m_maxTorque = def.maxTorque;
+ this.m_correctionFactor = def.correctionFactor;
+ this.m_indexA = 0;
+ this.m_indexB = 0;
+ this.m_rA = new b2Vec2();
+ this.m_rB = new b2Vec2();
+ this.m_localCenterA = new b2Vec2();
+ this.m_localCenterB = new b2Vec2();
+ this.m_linearError = new b2Vec2();
+ this.m_angularError = 0;
+ this.m_invMassA = 0;
+ this.m_invMassB = 0;
+ this.m_invIA = 0;
+ this.m_invIB = 0;
+ this.m_linearMass = new b2Mat22();
+ this.m_angularMass = 0
+}
+b2MotorJoint.prototype = {
+ GetAnchorA: function () {
+ return this.m_bodyA.GetPosition()
+ },
+ GetAnchorB: function () {
+ return this.m_bodyB.GetPosition()
+ },
+ GetReactionForce: function (inv_dt) {
+ return b2Vec2.Multiply(inv_dt, this.m_linearImpulse)
+ },
+ GetReactionTorque: function (inv_dt) {
+ return inv_dt * this.m_angularImpulse
+ },
+ SetLinearOffset: function (linearOffset) {
+ if (linearOffset.x != this.m_linearOffset.x || linearOffset.y != this.m_linearOffset.y) {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_linearOffset.Assign(linearOffset)
+ }
+ },
+ GetLinearOffset: function () {
+ return this.m_linearOffset
+ },
+ SetAngularOffset: function (angularOffset) {
+ if (angularOffset != this.m_angularOffset) {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_angularOffset = angularOffset
+ }
+ },
+ GetAngularOffset: function () {
+ return this.m_angularOffset
+ },
+ SetMaxForce: function (force) {
+ this.m_maxForce = force
+ },
+ GetMaxForce: function () {
+ return this.m_maxForce
+ },
+ SetMaxTorque: function (torque) {
+ this.m_maxTorque = torque
+ },
+ GetMaxTorque: function () {
+ return this.m_maxTorque
+ },
+ SetCorrectionFactor: function (factor) {
+ this.m_correctionFactor = factor
+ },
+ GetCorrectionFactor: function () {
+ return this.m_correctionFactor
+ },
+ InitVelocityConstraints: function (data) {
+ this.m_indexA = this.m_bodyA.m_islandIndex;
+ this.m_indexB = this.m_bodyB.m_islandIndex;
+ this.m_localCenterA.Assign(this.m_bodyA.m_sweep.localCenter);
+ this.m_localCenterB.Assign(this.m_bodyB.m_sweep.localCenter);
+ this.m_invMassA = this.m_bodyA.m_invMass;
+ this.m_invMassB = this.m_bodyB.m_invMass;
+ this.m_invIA = this.m_bodyA.m_invI;
+ this.m_invIB = this.m_bodyB.m_invI;
+ var cA = data.positions[this.m_indexA].c.Clone();
+ var aA = data.positions[this.m_indexA].a;
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var cB = data.positions[this.m_indexB].c.Clone();
+ var aB = data.positions[this.m_indexB].a;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var qA = new b2Rot(aA),
+ qB = new b2Rot(aB);
+ this.m_rA.Assign(b2Mul_r_v2(qA, this.m_localCenterA.Negate()));
+ this.m_rB.Assign(b2Mul_r_v2(qB, this.m_localCenterB.Negate()));
+ var mA = this.m_invMassA,
+ mB = this.m_invMassB;
+ var iA = this.m_invIA,
+ iB = this.m_invIB;
+ var K = new b2Mat22();
+ K.ex.x = mA + mB + iA * this.m_rA.y * this.m_rA.y + iB * this.m_rB.y * this.m_rB.y;
+ K.ex.y = -iA * this.m_rA.x * this.m_rA.y - iB * this.m_rB.x * this.m_rB.y;
+ K.ey.x = K.ex.y;
+ K.ey.y = mA + mB + iA * this.m_rA.x * this.m_rA.x + iB * this.m_rB.x * this.m_rB.x;
+ this.m_linearMass.Assign(K.GetInverse());
+ this.m_angularMass = iA + iB;
+ if (this.m_angularMass > 0) {
+ this.m_angularMass = 1 / this.m_angularMass
+ }
+ this.m_linearError.Assign(b2Vec2.Subtract(b2Vec2.Subtract(b2Vec2.Subtract(b2Vec2.Add(cB, this.m_rB), cA), this.m_rA), b2Mul_r_v2(qA, this.m_linearOffset)));
+ this.m_angularError = aB - aA - this.m_angularOffset;
+ if (data.step.warmStarting) {
+ this.m_linearImpulse.Multiply(data.step.dtRatio);
+ this.m_angularImpulse *= data.step.dtRatio;
+ var P = new b2Vec2(this.m_linearImpulse.x, this.m_linearImpulse.y);
+ vA.Subtract(b2Vec2.Multiply(mA, P));
+ wA -= iA * (b2Cross_v2_v2(this.m_rA, P) + this.m_angularImpulse);
+ vB.Add(b2Vec2.Multiply(mB, P));
+ wB += iB * (b2Cross_v2_v2(this.m_rB, P) + this.m_angularImpulse)
+ } else {
+ this.m_linearImpulse.SetZero();
+ this.m_angularImpulse = 0
+ }
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolveVelocityConstraints: function (data) {
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var mA = this.m_invMassA,
+ mB = this.m_invMassB;
+ var iA = this.m_invIA,
+ iB = this.m_invIB;
+ var h = data.step.dt;
+ var inv_h = data.step.inv_dt;
+ var Cdot = wB - wA + inv_h * this.m_correctionFactor * this.m_angularError;
+ var impulse = -this.m_angularMass * Cdot;
+ var oldImpulse = this.m_angularImpulse;
+ var maxImpulse = h * this.m_maxTorque;
+ this.m_angularImpulse = b2Clamp(this.m_angularImpulse + impulse, -maxImpulse, maxImpulse);
+ impulse = this.m_angularImpulse - oldImpulse;
+ wA -= iA * impulse;
+ wB += iB * impulse;
+ var Cdot = b2Vec2.Add(b2Vec2.Subtract(b2Vec2.Subtract(b2Vec2.Add(vB, b2Cross_f_v2(wB, this.m_rB)), vA), b2Cross_f_v2(wA, this.m_rA)), b2Vec2.Multiply(inv_h, b2Vec2.Multiply(this.m_correctionFactor, this.m_linearError)));
+ var impulse = b2Mul_m22_v2(this.m_linearMass, Cdot).Negate();
+ var oldImpulse = this.m_linearImpulse;
+ this.m_linearImpulse.Add(impulse);
+ var maxImpulse = h * this.m_maxForce;
+ if (this.m_linearImpulse.LengthSquared() > maxImpulse * maxImpulse) {
+ this.m_linearImpulse.Normalize();
+ this.m_linearImpulse.Multiply(maxImpulse)
+ }
+ impulse.Assign(b2Vec2.Subtract(this.m_linearImpulse, oldImpulse));
+ vA.Subtract(b2Vec2.Multiply(mA, impulse));
+ wA -= iA * b2Cross_v2_v2(this.m_rA, impulse);
+ vB.Add(b2Vec2.Multiply(mB, impulse));
+ wB += iB * b2Cross_v2_v2(this.m_rB, impulse);
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolvePositionConstraints: function (data) {
+ return true
+ },
+ _serialize: function (out) {
+ var obj = out || {};
+ this.parent.prototype._serialize.call(this, obj);
+ obj.linearOffset = this.m_linearOffset._serialize();
+ obj.angularOffset = this.m_angularOffset;
+ obj.maxForce = this.m_maxForce;
+ obj.maxTorque = this.m_maxTorque;
+ obj.correctionFactor = this.m_correctionFactor;
+ return obj
+ }
+};
+b2MotorJoint._extend(b2Joint);
+var b2_minPulleyLength = 2;
+
+function b2PulleyJointDef() {
+ this.parent.call(this);
+ this.type = b2Joint.e_pulleyJoint;
+ this.groundAnchorA = new b2Vec2(-1, 1);
+ this.groundAnchorB = new b2Vec2(1, 1);
+ this.localAnchorA = new b2Vec2(-1, 0);
+ this.localAnchorB = new b2Vec2(1, 0);
+ this.lengthA = 0;
+ this.lengthB = 0;
+ this.ratio = 1;
+ this.collideConnected = true;
+ Object.seal(this)
+}
+b2PulleyJointDef.prototype = {
+ Initialize: function (bA, bB, groundA, groundB, anchorA, anchorB, r) {
+ this.bodyA = bA;
+ this.bodyB = bB;
+ this.groundAnchorA.Assign(groundA);
+ this.groundAnchorB.Assign(groundB);
+ this.localAnchorA.Assign(this.bodyA.GetLocalPoint(anchorA));
+ this.localAnchorB.Assign(this.bodyB.GetLocalPoint(anchorB));
+ var dA = b2Vec2.Subtract(anchorA, groundA);
+ this.lengthA = dA.Length();
+ var dB = b2Vec2.Subtract(anchorB, groundB);
+ this.lengthB = dB.Length();
+ this.ratio = r
+ },
+ _deserialize: function (data, bodies, joints) {
+ this.parent.prototype._deserialize.call(this, data, bodies, joints);
+ this.groundAnchorA._deserialize(data.groundAnchorA);
+ this.groundAnchorB._deserialize(data.groundAnchorB);
+ this.localAnchorA._deserialize(data.localAnchorA);
+ this.localAnchorB._deserialize(data.localAnchorB);
+ this.lengthA = data.lengthA;
+ this.lengthB = data.lengthB;
+ this.ratio = data.ratio
+ }
+};
+b2PulleyJointDef._extend(b2JointDef);
+
+function b2PulleyJoint(def) {
+ this.parent.call(this, def);
+ this.m_indexA = 0;
+ this.m_indexB = 0;
+ this.m_uA = new b2Vec2();
+ this.m_uB = new b2Vec2();
+ this.m_rA = new b2Vec2();
+ this.m_rB = new b2Vec2();
+ this.m_localCenterA = new b2Vec2();
+ this.m_localCenterB = new b2Vec2();
+ this.m_invMassA = 0;
+ this.m_invMassB = 0;
+ this.m_invIA = 0;
+ this.m_invIB = 0;
+ this.m_mass = 0;
+ this.m_groundAnchorA = def.groundAnchorA.Clone();
+ this.m_groundAnchorB = def.groundAnchorB.Clone();
+ this.m_localAnchorA = def.localAnchorA.Clone();
+ this.m_localAnchorB = def.localAnchorB.Clone();
+ this.m_lengthA = def.lengthA;
+ this.m_lengthB = def.lengthB;
+ this.m_ratio = def.ratio;
+ this.m_constant = def.lengthA + this.m_ratio * def.lengthB;
+ this.m_impulse = 0
+}
+b2PulleyJoint.prototype = {
+ GetAnchorA: function () {
+ return this.m_bodyA.GetWorldPoint(this.m_localAnchorA)
+ },
+ GetAnchorB: function () {
+ return this.m_bodyB.GetWorldPoint(this.m_localAnchorB)
+ },
+ GetReactionForce: function (inv_dt) {
+ var P = b2Vec2.Multiply(this.m_impulse, this.m_uB);
+ return b2Vec2.Multiply(inv_dt, P)
+ },
+ GetReactionTorque: function (inv_dt) {
+ return 0
+ },
+ GetGroundAnchorA: function () {
+ return this.m_groundAnchorA
+ },
+ GetGroundAnchorB: function () {
+ return this.m_groundAnchorB
+ },
+ GetLengthA: function () {
+ return this.m_lengthA
+ },
+ GetLengthB: function () {
+ return this.m_lengthB
+ },
+ GetRatio: function () {
+ return this.m_ratio
+ },
+ GetCurrentLengthA: function () {
+ var p = this.m_bodyA.GetWorldPoint(this.m_localAnchorA);
+ var s = this.m_groundAnchorA;
+ var d = b2Vec2.Subtract(p, s);
+ return d.Length()
+ },
+ GetCurrentLengthB: function () {
+ var p = this.m_bodyB.GetWorldPoint(this.m_localAnchorB);
+ var s = this.m_groundAnchorB;
+ var d = b2Vec2.Subtract(p, s);
+ return d.Length()
+ },
+ ShiftOrigin: function (newOrigin) {
+ this.m_groundAnchorA.Subtract(newOrigin);
+ this.m_groundAnchorB.Subtract(newOrigin)
+ },
+ InitVelocityConstraints: function (data) {
+ this.m_indexA = this.m_bodyA.m_islandIndex;
+ this.m_indexB = this.m_bodyB.m_islandIndex;
+ this.m_localCenterA.Assign(this.m_bodyA.m_sweep.localCenter);
+ this.m_localCenterB.Assign(this.m_bodyB.m_sweep.localCenter);
+ this.m_invMassA = this.m_bodyA.m_invMass;
+ this.m_invMassB = this.m_bodyB.m_invMass;
+ this.m_invIA = this.m_bodyA.m_invI;
+ this.m_invIB = this.m_bodyB.m_invI;
+ var cA = data.positions[this.m_indexA].c.Clone();
+ var aA = data.positions[this.m_indexA].a;
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var cB = data.positions[this.m_indexB].c.Clone();
+ var aB = data.positions[this.m_indexB].a;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var qA = new b2Rot(aA),
+ qB = new b2Rot(aB);
+ this.m_rA.Assign(b2Mul_r_v2(qA, b2Vec2.Subtract(this.m_localAnchorA, this.m_localCenterA)));
+ this.m_rB.Assign(b2Mul_r_v2(qB, b2Vec2.Subtract(this.m_localAnchorB, this.m_localCenterB)));
+ this.m_uA.Assign(b2Vec2.Add(cA, b2Vec2.Subtract(this.m_rA, this.m_groundAnchorA)));
+ this.m_uB.Assign(b2Vec2.Add(cB, b2Vec2.Subtract(this.m_rB, this.m_groundAnchorB)));
+ var lengthA = this.m_uA.Length();
+ var lengthB = this.m_uB.Length();
+ if (lengthA > 10 * b2_linearSlop) {
+ this.m_uA.Multiply(1 / lengthA)
+ } else {
+ this.m_uA.SetZero()
+ }
+ if (lengthB > 10 * b2_linearSlop) {
+ this.m_uB.Multiply(1 / lengthB)
+ } else {
+ this.m_uB.SetZero()
+ }
+ var ruA = b2Cross_v2_v2(this.m_rA, this.m_uA);
+ var ruB = b2Cross_v2_v2(this.m_rB, this.m_uB);
+ var mA = this.m_invMassA + this.m_invIA * ruA * ruA;
+ var mB = this.m_invMassB + this.m_invIB * ruB * ruB;
+ this.m_mass = mA + this.m_ratio * this.m_ratio * mB;
+ if (this.m_mass > 0) {
+ this.m_mass = 1 / this.m_mass
+ }
+ if (data.step.warmStarting) {
+ this.m_impulse *= data.step.dtRatio;
+ var PA = b2Vec2.Multiply(-(this.m_impulse), this.m_uA);
+ var PB = b2Vec2.Multiply((-this.m_ratio * this.m_impulse), this.m_uB);
+ vA.Add(b2Vec2.Multiply(this.m_invMassA, PA));
+ wA += this.m_invIA * b2Cross_v2_v2(this.m_rA, PA);
+ vB.Add(b2Vec2.Multiply(this.m_invMassB, PB));
+ wB += this.m_invIB * b2Cross_v2_v2(this.m_rB, PB)
+ } else {
+ this.m_impulse = 0
+ }
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolveVelocityConstraints: function (data) {
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var vpA = b2Vec2.Add(vA, b2Cross_f_v2(wA, this.m_rA));
+ var vpB = b2Vec2.Add(vB, b2Cross_f_v2(wB, this.m_rB));
+ var Cdot = -b2Dot_v2_v2(this.m_uA, vpA) - this.m_ratio * b2Dot_v2_v2(this.m_uB, vpB);
+ var impulse = -this.m_mass * Cdot;
+ this.m_impulse += impulse;
+ var PA = b2Vec2.Multiply(-impulse, this.m_uA);
+ var PB = b2Vec2.Multiply(-this.m_ratio, b2Vec2.Multiply(impulse, this.m_uB));
+ vA.Add(b2Vec2.Multiply(this.m_invMassA, PA));
+ wA += this.m_invIA * b2Cross_v2_v2(this.m_rA, PA);
+ vB.Add(b2Vec2.Multiply(this.m_invMassB, PB));
+ wB += this.m_invIB * b2Cross_v2_v2(this.m_rB, PB);
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolvePositionConstraints: function (data) {
+ var cA = data.positions[this.m_indexA].c.Clone();
+ var aA = data.positions[this.m_indexA].a;
+ var cB = data.positions[this.m_indexB].c.Clone();
+ var aB = data.positions[this.m_indexB].a;
+ var qA = new b2Rot(aA),
+ qB = new b2Rot(aB);
+ var rA = b2Mul_r_v2(qA, b2Vec2.Subtract(this.m_localAnchorA, this.m_localCenterA));
+ var rB = b2Mul_r_v2(qB, b2Vec2.Subtract(this.m_localAnchorB, this.m_localCenterB));
+ var uA = b2Vec2.Add(cA, b2Vec2.Subtract(rA, this.m_groundAnchorA));
+ var uB = b2Vec2.Add(cB, b2Vec2.Subtract(rB, this.m_groundAnchorB));
+ var lengthA = uA.Length();
+ var lengthB = uB.Length();
+ if (lengthA > 10 * b2_linearSlop) {
+ uA.Multiply(1 / lengthA)
+ } else {
+ uA.SetZero()
+ }
+ if (lengthB > 10 * b2_linearSlop) {
+ uB.Multiply(1 / lengthB)
+ } else {
+ uB.SetZero()
+ }
+ var ruA = b2Cross_v2_v2(rA, uA);
+ var ruB = b2Cross_v2_v2(rB, uB);
+ var mA = this.m_invMassA + this.m_invIA * ruA * ruA;
+ var mB = this.m_invMassB + this.m_invIB * ruB * ruB;
+ var mass = mA + this.m_ratio * this.m_ratio * mB;
+ if (mass > 0) {
+ mass = 1 / mass
+ }
+ var C = this.m_constant - lengthA - this.m_ratio * lengthB;
+ var linearError = b2Abs(C);
+ var impulse = -mass * C;
+ var PA = b2Vec2.Multiply(-impulse, uA);
+ var PB = b2Vec2.Multiply(-this.m_ratio, b2Vec2.Multiply(impulse, uB));
+ cA.Add(b2Vec2.Multiply(this.m_invMassA, PA));
+ aA += this.m_invIA * b2Cross_v2_v2(rA, PA);
+ cB.Add(b2Vec2.Multiply(this.m_invMassB, PB));
+ aB += this.m_invIB * b2Cross_v2_v2(rB, PB);
+ data.positions[this.m_indexA].c.Assign(cA);
+ data.positions[this.m_indexA].a = aA;
+ data.positions[this.m_indexB].c.Assign(cB);
+ data.positions[this.m_indexB].a = aB;
+ return linearError < b2_linearSlop
+ },
+ _serialize: function (out) {
+ var obj = out || {};
+ this.parent.prototype._serialize.call(this, obj);
+ obj.groundAnchorA = this.m_groundAnchorA._serialize();
+ obj.groundAnchorB = this.m_groundAnchorB._serialize();
+ obj.localAnchorA = this.m_localAnchorA._serialize();
+ obj.localAnchorB = this.m_localAnchorB._serialize();
+ obj.lengthA = this.m_lengthA;
+ obj.lengthB = this.m_lengthB;
+ obj.ratio = this.m_ratio;
+ return obj
+ }
+};
+b2PulleyJoint._extend(b2Joint);
+
+function b2RopeJointDef() {
+ this.parent.call(this);
+ this.type = b2Joint.e_ropeJoint;
+ this.localAnchorA = new b2Vec2(-1, 0);
+ this.localAnchorB = new b2Vec2(1, 0);
+ this.maxLength = 0;
+ Object.seal(this)
+}
+b2RopeJointDef.prototype = {
+ _deserialize: function (data, bodies, joints) {
+ this.parent.prototype._deserialize.call(this, data, bodies, joints);
+ this.localAnchorA._deserialize(data.localAnchorA);
+ this.localAnchorB._deserialize(data.localAnchorB);
+ this.maxLength = data.maxLength
+ }
+};
+b2RopeJointDef._extend(b2JointDef);
+
+function b2RopeJoint(def) {
+ this.parent.call(this, def);
+ this.m_localAnchorA = def.localAnchorA.Clone();
+ this.m_localAnchorB = def.localAnchorB.Clone();
+ this.m_maxLength = def.maxLength;
+ this.m_mass = 0;
+ this.m_impulse = 0;
+ this.m_state = b2Joint.e_inactiveLimit;
+ this.m_length = 0;
+ this.m_indexA = 0;
+ this.m_indexB = 0;
+ this.m_u = new b2Vec2();
+ this.m_rA = new b2Vec2();
+ this.m_rB = new b2Vec2();
+ this.m_localCenterA = new b2Vec2();
+ this.m_localCenterB = new b2Vec2();
+ this.m_invMassA = 0;
+ this.m_invMassB = 0;
+ this.m_invIA = 0;
+ this.m_invIB = 0
+}
+b2RopeJoint.prototype = {
+ GetAnchorA: function () {
+ return this.m_bodyA.GetWorldPoint(this.m_localAnchorA)
+ },
+ GetAnchorB: function () {
+ return this.m_bodyB.GetWorldPoint(this.m_localAnchorB)
+ },
+ GetReactionForce: function (inv_dt) {
+ var F = b2Vec2.Multiply((inv_dt * this.m_impulse), this.m_u);
+ return F
+ },
+ GetReactionTorque: function (inv_dt) {
+ return 0
+ },
+ GetLocalAnchorA: function () {
+ return this.m_localAnchorA
+ },
+ GetLocalAnchorB: function () {
+ return this.m_localAnchorB
+ },
+ SetMaxLength: function (length) {
+ this.m_maxLength = length
+ },
+ GetMaxLength: function () {
+ return this.m_maxLength
+ },
+ GetLimitState: function () {
+ return this.m_state
+ },
+ InitVelocityConstraints: function (data) {
+ this.m_indexA = this.m_bodyA.m_islandIndex;
+ this.m_indexB = this.m_bodyB.m_islandIndex;
+ this.m_localCenterA.Assign(this.m_bodyA.m_sweep.localCenter);
+ this.m_localCenterB.Assign(this.m_bodyB.m_sweep.localCenter);
+ this.m_invMassA = this.m_bodyA.m_invMass;
+ this.m_invMassB = this.m_bodyB.m_invMass;
+ this.m_invIA = this.m_bodyA.m_invI;
+ this.m_invIB = this.m_bodyB.m_invI;
+ var cA = data.positions[this.m_indexA].c.Clone();
+ var aA = data.positions[this.m_indexA].a;
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var cB = data.positions[this.m_indexB].c.Clone();
+ var aB = data.positions[this.m_indexB].a;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var qA = new b2Rot(aA),
+ qB = new b2Rot(aB);
+ this.m_rA.Assign(b2Mul_r_v2(qA, b2Vec2.Subtract(this.m_localAnchorA, this.m_localCenterA)));
+ this.m_rB.Assign(b2Mul_r_v2(qB, b2Vec2.Subtract(this.m_localAnchorB, this.m_localCenterB)));
+ this.m_u.Assign(b2Vec2.Subtract(b2Vec2.Subtract(b2Vec2.Add(cB, this.m_rB), cA), this.m_rA));
+ this.m_length = this.m_u.Length();
+ var C = this.m_length - this.m_maxLength;
+ if (C > 0) {
+ this.m_state = b2Joint.e_atUpperLimit
+ } else {
+ this.m_state = b2Joint.e_inactiveLimit
+ }
+ if (this.m_length > b2_linearSlop) {
+ this.m_u.Multiply(1 / this.m_length)
+ } else {
+ this.m_u.SetZero();
+ this.m_mass = 0;
+ this.m_impulse = 0;
+ return
+ }
+ var crA = b2Cross_v2_v2(this.m_rA, this.m_u);
+ var crB = b2Cross_v2_v2(this.m_rB, this.m_u);
+ var invMass = this.m_invMassA + this.m_invIA * crA * crA + this.m_invMassB + this.m_invIB * crB * crB;
+ this.m_mass = invMass != 0 ? 1 / invMass : 0;
+ if (data.step.warmStarting) {
+ this.m_impulse *= data.step.dtRatio;
+ var P = b2Vec2.Multiply(this.m_impulse, this.m_u);
+ vA.Subtract(b2Vec2.Multiply(this.m_invMassA, P));
+ wA -= this.m_invIA * b2Cross_v2_v2(this.m_rA, P);
+ vB.Add(b2Vec2.Multiply(this.m_invMassB, P));
+ wB += this.m_invIB * b2Cross_v2_v2(this.m_rB, P)
+ } else {
+ this.m_impulse = 0
+ }
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolveVelocityConstraints: function (data) {
+ var vA = data.velocities[this.m_indexA].v.Clone();
+ var wA = data.velocities[this.m_indexA].w;
+ var vB = data.velocities[this.m_indexB].v.Clone();
+ var wB = data.velocities[this.m_indexB].w;
+ var vpA = b2Vec2.Add(vA, b2Cross_f_v2(wA, this.m_rA));
+ var vpB = b2Vec2.Add(vB, b2Cross_f_v2(wB, this.m_rB));
+ var C = this.m_length - this.m_maxLength;
+ var Cdot = b2Dot_v2_v2(this.m_u, b2Vec2.Subtract(vpB, vpA));
+ if (C < 0) {
+ Cdot += data.step.inv_dt * C
+ }
+ var impulse = -this.m_mass * Cdot;
+ var oldImpulse = this.m_impulse;
+ this.m_impulse = b2Min(0, this.m_impulse + impulse);
+ impulse = this.m_impulse - oldImpulse;
+ var P = b2Vec2.Multiply(impulse, this.m_u);
+ vA.Subtract(b2Vec2.Multiply(this.m_invMassA, P));
+ wA -= this.m_invIA * b2Cross_v2_v2(this.m_rA, P);
+ vB.Add(b2Vec2.Multiply(this.m_invMassB, P));
+ wB += this.m_invIB * b2Cross_v2_v2(this.m_rB, P);
+ data.velocities[this.m_indexA].v.Assign(vA);
+ data.velocities[this.m_indexA].w = wA;
+ data.velocities[this.m_indexB].v.Assign(vB);
+ data.velocities[this.m_indexB].w = wB
+ },
+ SolvePositionConstraints: function (data) {
+ var cA = data.positions[this.m_indexA].c.Clone();
+ var aA = data.positions[this.m_indexA].a;
+ var cB = data.positions[this.m_indexB].c.Clone();
+ var aB = data.positions[this.m_indexB].a;
+ var qA = new b2Rot(aA),
+ qB = new b2Rot(aB);
+ var rA = b2Mul_r_v2(qA, b2Vec2.Subtract(this.m_localAnchorA, this.m_localCenterA));
+ var rB = b2Mul_r_v2(qB, b2Vec2.Subtract(this.m_localAnchorB, this.m_localCenterB));
+ var u = b2Vec2.Subtract(b2Vec2.Subtract(b2Vec2.Add(cB, rB), cA), rA);
+ var length = u.Normalize();
+ var C = length - this.m_maxLength;
+ C = b2Clamp(C, 0, b2_maxLinearCorrection);
+ var impulse = -this.m_mass * C;
+ var P = b2Vec2.Multiply(impulse, u);
+ cA.Subtract(b2Vec2.Multiply(this.m_invMassA, P));
+ aA -= this.m_invIA * b2Cross_v2_v2(rA, P);
+ cB.Add(b2Vec2.Multiply(this.m_invMassB, P));
+ aB += this.m_invIB * b2Cross_v2_v2(rB, P);
+ data.positions[this.m_indexA].c.Assign(cA);
+ data.positions[this.m_indexA].a = aA;
+ data.positions[this.m_indexB].c.Assign(cB);
+ data.positions[this.m_indexB].a = aB;
+ return length - this.m_maxLength < b2_linearSlop
+ },
+ _serialize: function (out) {
+ var obj = out || {};
+ this.parent.prototype._serialize.call(this, obj);
+ obj.localAnchorA = this.m_localAnchorA._serialize();
+ obj.localAnchorB = this.m_localAnchorB._serialize();
+ obj.maxLength = this.m_maxLength;
+ return obj
+ }
+};
+b2RopeJoint._extend(b2Joint);
+var expf = Math.exp;
+
+function b2RopeDef() {
+ this.vertices = null;
+ this.count = 0;
+ this.masses = null;
+ this.gravity = new b2Vec2();
+ this.damping = 0.1;
+ this.k2 = 0.9;
+ this.k3 = 0.1
+}
+
+function b2Rope() {
+ this.m_count = 0;
+ this.m_ps = null;
+ this.m_p0s = null;
+ this.m_vs = null;
+ this.m_ims = null;
+ this.m_Ls = null;
+ this.m_as = null;
+ this.m_damping = 0;
+ this.m_gravity = new b2Vec2();
+ this.m_k2 = 1;
+ this.m_k3 = 0.1
+}
+b2Rope.prototype = {
+ Initialize: function (def) {
+ this.m_count = def.count;
+ this.m_ps = new Array(this.m_count);
+ this.m_p0s = new Array(this.m_count);
+ this.m_vs = new Array(this.m_count);
+ this.m_ims = new Array(this.m_count);
+ for (var i = 0; i < this.m_count; ++i) {
+ this.m_ps[i] = def.vertices[i].Clone();
+ this.m_p0s[i] = def.vertices[i].Clone();
+ this.m_vs[i] = new b2Vec2();
+ var m = def.masses[i];
+ if (m > 0) {
+ this.m_ims[i] = 1 / m
+ } else {
+ this.m_ims[i] = 0
+ }
+ }
+ var count2 = this.m_count - 1;
+ var count3 = this.m_count - 2;
+ this.m_Ls = new Array(count2);
+ this.m_as = new Array(count3);
+ for (var i = 0; i < count2; ++i) {
+ var p1 = this.m_ps[i];
+ var p2 = this.m_ps[i + 1];
+ this.m_Ls[i] = b2Distance(p1, p2)
+ }
+ for (var i = 0; i < count3; ++i) {
+ var p1 = this.m_ps[i];
+ var p2 = this.m_ps[i + 1];
+ var p3 = this.m_ps[i + 2];
+ var d1 = b2Vec2.Subtract(p2, p1);
+ var d2 = b2Vec2.Subtract(p3, p2);
+ var a = b2Cross_v2_v2(d1, d2);
+ var b = b2Dot_v2_v2(d1, d2);
+ this.m_as[i] = b2Atan2(a, b)
+ }
+ this.m_gravity = def.gravity.Clone();
+ this.m_damping = def.damping;
+ this.m_k2 = def.k2;
+ this.m_k3 = def.k3
+ },
+ Step: function (h, iterations) {
+ if (h == 0) {
+ return
+ }
+ var d = expf(-h * this.m_damping);
+ for (var i = 0; i < this.m_count; ++i) {
+ this.m_p0s[i].Assign(this.m_ps[i]);
+ if (this.m_ims[i] > 0) {
+ this.m_vs[i].Add(b2Vec2.Multiply(h, this.m_gravity))
+ }
+ this.m_vs[i].Multiply(d);
+ this.m_ps[i].Add(b2Vec2.Multiply(h, this.m_vs[i]))
+ }
+ for (var i = 0; i < iterations; ++i) {
+ this.SolveC2();
+ this.SolveC3();
+ this.SolveC2()
+ }
+ var inv_h = 1 / h;
+ for (var i = 0; i < this.m_count; ++i) {
+ this.m_vs[i] = b2Vec2.Multiply(inv_h, b2Vec2.Subtract(this.m_ps[i], this.m_p0s[i]))
+ }
+ },
+ GetVertexCount: function () {
+ return this.m_count
+ },
+ GetVertices: function () {
+ return this.m_ps
+ },
+ Draw: function (draw) {
+ var c = new b2Color(0.4, 0.5, 0.7);
+ for (var i = 0; i < this.m_count - 1; ++i) {
+ draw.DrawSegment(this.m_ps[i], this.m_ps[i + 1], c)
+ }
+ },
+ SetAngle: function (angle) {
+ var count3 = this.m_count - 2;
+ for (var i = 0; i < count3; ++i) {
+ this.m_as[i] = angle
+ }
+ },
+ SolveC2: function () {
+ var count2 = this.m_count - 1;
+ for (var i = 0; i < count2; ++i) {
+ var p1 = this.m_ps[i];
+ var p2 = this.m_ps[i + 1];
+ var d = b2Vec2.Subtract(p2, p1);
+ var L = d.Normalize();
+ var im1 = this.m_ims[i];
+ var im2 = this.m_ims[i + 1];
+ if (im1 + im2 == 0) {
+ continue
+ }
+ var s1 = im1 / (im1 + im2);
+ var s2 = im2 / (im1 + im2);
+ p1.Subtract(b2Vec2.Multiply(this.m_k2 * s1 * (this.m_Ls[i] - L), d));
+ p2.Add(b2Vec2.Multiply(this.m_k2 * s2 * (this.m_Ls[i] - L), d))
+ }
+ },
+ SolveC3: function () {
+ var count3 = this.m_count - 2;
+ for (var i = 0; i < count3; ++i) {
+ var p1 = this.m_ps[i];
+ var p2 = this.m_ps[i + 1];
+ var p3 = this.m_ps[i + 2];
+ var m1 = this.m_ims[i];
+ var m2 = this.m_ims[i + 1];
+ var m3 = this.m_ims[i + 2];
+ var d1 = b2Vec2.Subtract(p2, p1);
+ var d2 = b2Vec2.Subtract(p3, p2);
+ var L1sqr = d1.LengthSquared();
+ var L2sqr = d2.LengthSquared();
+ if (L1sqr * L2sqr == 0) {
+ continue
+ }
+ var a = b2Cross_v2_v2(d1, d2);
+ var b = b2Dot_v2_v2(d1, d2);
+ var angle = b2Atan2(a, b);
+ var Jd1 = b2Vec2.Multiply((-1 / L1sqr), d1.Skew());
+ var Jd2 = b2Vec2.Multiply((1 / L2sqr), d2.Skew());
+ var J1 = b2Vec2.Negate(Jd1);
+ var J2 = b2Vec2.Subtract(Jd1, Jd2);
+ var J3 = Jd2;
+ var mass = m1 * b2Dot_v2_v2(J1, J1) + m2 * b2Dot_v2_v2(J2, J2) + m3 * b2Dot_v2_v2(J3, J3);
+ if (mass == 0) {
+ continue
+ }
+ mass = 1 / mass;
+ var C = angle - this.m_as[i];
+ while (C > b2_pi) {
+ angle -= 2 * b2_pi;
+ C = angle - this.m_as[i]
+ }
+ while (C < -b2_pi) {
+ angle += 2 * b2_pi;
+ C = angle - this.m_as[i]
+ }
+ var impulse = -this.m_k3 * mass * C;
+ p1.Add(b2Vec2.Multiply((m1 * impulse), J1));
+ p2.Add(b2Vec2.Multiply((m2 * impulse), J2));
+ p3.Add(b2Vec2.Multiply((m3 * impulse), J3))
+ }
+ }
+};
+var b2JsonSerializer = {
+ serialize: function (world) {
+ var shapes = [];
+ var i;
+ var serialized;
+ var b;
+ var f;
+ var shape;
+ for (b = world.GetBodyList(); b; b = b.GetNext()) {
+ for (f = b.GetFixtureList(); f; f = f.GetNext()) {
+ shape = f.GetShape();
+ f.__temp_shape_id = shapes.length;
+ shapes.push(shape._serialize())
+ }
+ }
+ var fixtures = [];
+ for (b = world.GetBodyList(); b; b = b.GetNext()) {
+ b.__temp_fixture_ids = [];
+ for (f = b.GetFixtureList(); f; f = f.GetNext()) {
+ serialized = f._serialize();
+ serialized.shape = f.__temp_shape_id;
+ delete f.__temp_shape_id;
+ b.__temp_fixture_ids.push(fixtures.length);
+ fixtures.push(serialized)
+ }
+ }
+ var bodies = [];
+ for (b = world.GetBodyList(); b; b = b.GetNext()) {
+ serialized = b._serialize();
+ serialized.fixtures = [];
+ for (i = 0; i < b.__temp_fixture_ids.length; ++i) {
+ serialized.fixtures.push(b.__temp_fixture_ids[i])
+ }
+ delete b.__temp_fixture_ids;
+ b.__temp_body_id = bodies.length;
+ bodies.push(serialized)
+ }
+ var joints = [];
+ var j;
+ for (j = world.GetJointList(), i = 0; j; j = j.GetNext(), ++i) {
+ j.__temp_joint_id = i
+ }
+ for (j = world.GetJointList(); j; j = j.GetNext()) {
+ if (j.GetType() === b2Joint.e_mouseJoint) {
+ continue
+ }
+ serialized = j._serialize();
+ serialized.bodyA = j.GetBodyA().__temp_body_id;
+ serialized.bodyB = j.GetBodyB().__temp_body_id;
+ joints.push(serialized)
+ }
+ for (j = world.GetJointList(); j; j = j.GetNext()) {
+ delete j.__temp_joint_id
+ }
+ for (b = world.GetBodyList(); b; b = b.GetNext()) {
+ delete b.__temp_body_id
+ }
+ return {
+ shapes: shapes,
+ fixtures: fixtures,
+ bodies: bodies,
+ joints: joints
+ }
+ },
+ deserialize: function (serialized, world, clear) {
+ var deserialized = JSON.parse(serialized);
+ if (clear) {
+ for (var b = world.GetBodyList(); b;) {
+ var next = b.GetNext();
+ world.DestroyBody(b);
+ b = next
+ }
+ for (var j = world.GetJointList(); j;) {
+ var next = j.GetNext();
+ world.DestroyJoint(j);
+ j = next
+ }
+ }
+ var shapes = [];
+ for (var i = 0; i < deserialized.shapes.length; ++i) {
+ var shapeData = deserialized.shapes[i];
+ var shape;
+ switch (shapeData.m_type) {
+ case b2Shape.e_circle:
+ shape = new b2CircleShape();
+ break;
+ case b2Shape.e_edge:
+ shape = new b2EdgeShape();
+ break;
+ case b2Shape.e_chain:
+ shape = new b2ChainShape();
+ break;
+ case b2Shape.e_polygon:
+ shape = new b2PolygonShape();
+ break
+ }
+ shape._deserialize(shapeData);
+ shapes.push(shape)
+ }
+ var fixtures = [];
+ for (i = 0; i < deserialized.fixtures.length; ++i) {
+ var fixtureData = deserialized.fixtures[i];
+ var fixture = new b2FixtureDef();
+ fixture._deserialize(fixtureData);
+ fixture.shape = shapes[fixtureData.shape];
+ fixtures.push(fixture)
+ }
+ var bodies = [];
+ for (i = 0; i < deserialized.bodies.length; ++i) {
+ var bodyData = deserialized.bodies[i];
+ var def = new b2BodyDef();
+ def._deserialize(bodyData);
+ var body = world.CreateBody(def);
+ for (var x = 0; x < bodyData.fixtures.length; ++x) {
+ body.CreateFixture(fixtures[bodyData.fixtures[x]])
+ }
+ bodies.push(body)
+ }
+ var joints = [];
+ var gears = [];
+ for (i = 0; i < deserialized.joints.length; ++i) {
+ var jointData = deserialized.joints[i];
+ var jointDef;
+ switch (jointData.type) {
+ case b2Joint.e_revoluteJoint:
+ jointDef = new b2RevoluteJointDef();
+ break;
+ case b2Joint.e_prismaticJoint:
+ jointDef = new b2PrismaticJointDef();
+ break;
+ case b2Joint.e_distanceJoint:
+ jointDef = new b2DistanceJointDef();
+ break;
+ case b2Joint.e_pulleyJoint:
+ jointDef = new b2PulleyJointDef();
+ break;
+ case b2Joint.e_gearJoint:
+ jointDef = new b2GearJointDef();
+ break;
+ case b2Joint.e_wheelJoint:
+ jointDef = new b2WheelJointDef();
+ break;
+ case b2Joint.e_weldJoint:
+ jointDef = new b2WeldJointDef();
+ break;
+ case b2Joint.e_frictionJoint:
+ jointDef = new b2FrictionJointDef();
+ break;
+ case b2Joint.e_ropeJoint:
+ jointDef = new b2RopeJointDef();
+ break;
+ case b2Joint.e_motorJoint:
+ jointDef = new b2MotorJointDef();
+ break;
+ default:
+ throw new Error("unknown joint")
+ }
+ jointDef._deserialize(jointData, bodies);
+ if (jointData.type === b2Joint.e_gearJoint) {
+ gears.push([jointDef, joints.length]);
+ joints.push(null)
+ } else {
+ var joint = world.CreateJoint(jointDef);
+ joints.push(joint)
+ }
+ }
+ for (i = 0; i < gears.length; ++i) {
+ gears[i][0].joint1 = joints[gears[i][0].joint1];
+ gears[i][0].joint2 = joints[gears[i][0].joint2];
+ joint = world.CreateJoint(gears[i][0]);
+ joints[gears[i][1]] = joint
+ }
+ }
+};
+var b2RUBELoader = (function () {
+ function parseVector(obj) {
+ return new b2Vec2(obj ? (obj.x || 0) : 0, obj ? (obj.y || 0) : 0)
+ }
+
+ function parseVectorArray(obj) {
+ var vals = new Array(obj.x.length);
+ for (var i = 0; i < vals.length; ++i) {
+ vals[i] = new b2Vec2(obj.x[i], obj.y[i])
+ }
+ return vals
+ }
+
+ function parseProperty(obj, instance) {
+ var name = obj.name;
+ var val;
+ if (typeof (obj["int"]) !== "undefined") {
+ val = obj["int"]
+ } else {
+ if (typeof (obj["float"]) !== "undefined") {
+ val = obj["float"]
+ } else {
+ if (typeof (obj.string) !== "undefined") {
+ val = obj.string
+ } else {
+ if (typeof (obj.bool) !== "undefined") {
+ val = obj.bool
+ } else {
+ if (typeof (obj.vec2) !== "undefined") {
+ val = parseVector(obj.vec2)
+ } else {
+ throw new Error("unknown property type")
+ }
+ }
+ }
+ }
+ }
+ if (instance.hasOwnProperty(name)) {
+ throw new Error("custom property possibly overwriting an existing one")
+ }
+ instance[name] = val
+ }
+
+ function parseFixture(obj, body) {
+ var def = new b2FixtureDef();
+ def.density = obj.density || 0;
+ def.filter.categoryBits = typeof (obj["filter-categoryBits"]) === "undefined" ? 1 : obj["filter-categoryBits"];
+ def.filter.maskBits = typeof (obj["filter-maskBits"]) === "undefined" ? 65535 : obj["filter-maskBits"];
+ def.filter.groupIndex = typeof (obj["filter-groupIndex"]) === "undefined" ? 0 : obj["filter-groupIndex"];
+ def.friction = obj.friction || 0;
+ def.restitution = obj.restitution || 0;
+ def.isSensor = obj.sensor || 0;
+ var shape;
+ if (typeof (obj.circle) !== "undefined") {
+ shape = new b2CircleShape();
+ shape.m_p = parseVector(obj.circle.center);
+ shape.m_radius = obj.circle.radius || 0
+ } else {
+ if (typeof (obj.polygon) !== "undefined") {
+ var vertices = parseVectorArray(obj.polygon.vertices);
+ shape = new b2PolygonShape();
+ shape.Set(vertices, vertices.length)
+ } else {
+ if (typeof (obj.chain) !== "undefined") {
+ var vertices = parseVectorArray(obj.chain.vertices);
+ shape = new b2ChainShape();
+ shape.m_count = vertices.length;
+ shape.m_vertices = vertices;
+ if (shape.m_hasNextVertex = obj.chain.hasNextVertex) {
+ shape.m_nextVertex = parseVector(obj.chain.nextVertex)
+ }
+ if (shape.m_hasPrevVertex = obj.chain.hasPrevVertex) {
+ shape.m_prevVertex = parseVector(obj.chain.prevVertex)
+ }
+ } else {
+ throw new Error("unknown shape type")
+ }
+ }
+ }
+ def.shape = shape;
+ var fixture = body.CreateFixture(def);
+ fixture.name = obj.name;
+ if (obj.customProperties) {
+ for (var i = 0; i < obj.customProperties.length; ++i) {
+ parseProperty(obj, fixture)
+ }
+ }
+ }
+
+ function parseBody(obj, world) {
+ var def = new b2BodyDef();
+ def.type = obj.type || b2Body.b2_staticBody;
+ def.angle = obj.angle || 0;
+ def.angularDamping = obj.angularDamping || 0;
+ def.angularVelocity = obj.angularVelocity || 0;
+ def.awake = obj.awake || false;
+ def.bullet = obj.bullet || false;
+ def.fixedRotation = obj.fixedRotation || false;
+ def.linearDamping = obj.linearDamping || false;
+ def.linearVelocity = parseVector(obj.linearVelocity);
+ def.gravityScale = typeof (obj.gravityScale) !== "undefined" ? obj.gravityScale : 1;
+ var md = new b2MassData();
+ md.mass = obj["massData-mass"] || 0;
+ md.center = parseVector(obj["massData-center"]);
+ md.I = obj["massData-I"] || 0;
+ def.position = parseVector(obj.position);
+ var body = world.CreateBody(def);
+ body.name = obj.name;
+ body.SetMassData(md);
+ if (obj.fixture) {
+ for (var i = 0; i < obj.fixture.length; ++i) {
+ parseFixture(obj.fixture[i], body)
+ }
+ }
+ if (obj.customProperties) {
+ for (i = 0; i < obj.customProperties.length; ++i) {
+ parseProperty(obj, body)
+ }
+ }
+ return body
+ }
+ var jointsList = {
+ revolute: b2RevoluteJointDef,
+ distance: b2DistanceJointDef,
+ prismatic: b2PrismaticJointDef,
+ wheel: b2WheelJointDef,
+ rope: b2RopeJointDef,
+ motor: b2MotorJointDef,
+ weld: b2WeldJointDef,
+ friction: b2FrictionJointDef
+ };
+
+ function parseJoint(obj, world, bodies) {
+ if (!jointsList[obj.type]) {
+ throw new Error("unknown joint type")
+ }
+ var jd = new jointsList[obj.type]();
+ switch (jd.type) {
+ case b2Joint.e_revoluteJoint:
+ jd.localAnchorA = parseVector(obj.anchorA);
+ jd.localAnchorB = parseVector(obj.anchorB);
+ jd.enableLimit = obj.enableLimit || false;
+ jd.enableMotor = obj.enableMotor || false;
+ jd.lowerAngle = obj.lowerLimit || 0;
+ jd.maxMotorTorque = obj.maxMotorTorque || 0;
+ jd.motorSpeed = obj.motorSpeed || 0;
+ jd.referenceAngle = obj.refAngle || 0;
+ jd.upperAngle = obj.upperLimit || 0;
+ break;
+ case b2Joint.e_distanceJoint:
+ jd.localAnchorA = parseVector(obj.anchorA);
+ jd.localAnchorB = parseVector(obj.anchorB);
+ jd.dampingRatio = obj.dampingRatio || 0;
+ jd.frequencyHz = obj.frequency || 0;
+ jd.length = obj.length || 0;
+ break;
+ case b2Joint.e_prismaticJoint:
+ jd.localAnchorA = parseVector(obj.anchorA);
+ jd.localAnchorB = parseVector(obj.anchorB);
+ jd.enableLimit = obj.enableLimit || false;
+ jd.enableMotor = obj.enableMotor || false;
+ jd.localAxisA = parseVector(obj.localAxisA);
+ jd.lowerTranslation = obj.lowerLimit || 0;
+ jd.maxMotorForce = obj.maxMotorForce || 0;
+ jd.motorSpeed = obj.motorSpeed || 0;
+ jd.referenceAngle = obj.refAngle || 0;
+ jd.upperTranslation = obj.upperLimit || 0;
+ break;
+ case b2Joint.e_wheelJoint:
+ jd.localAnchorA = parseVector(obj.anchorA);
+ jd.localAnchorB = parseVector(obj.anchorB);
+ jd.enableMotor = obj.enableMotor || false;
+ jd.localAxisA = parseVector(obj.localAxisA);
+ jd.maxMotorTorque = obj.maxMotorTorque || 0;
+ jd.motorSpeed = obj.motorSpeed || 0;
+ jd.dampingRatio = obj.springDampingRatio || 0;
+ jd.frequencyHz = obj.springFrequency || 0;
+ break;
+ case b2Joint.e_ropeJoint:
+ jd.localAnchorA = parseVector(obj.anchorA);
+ jd.localAnchorB = parseVector(obj.anchorB);
+ jd.maxLength = obj.maxLength || 0;
+ break;
+ case b2Joint.e_motorJoint:
+ jd.linearOffset = parseVector(obj.anchorA);
+ jd.angularOffset = obj.refAngle || 0;
+ jd.maxForce = obj.maxForce || 0;
+ jd.maxTorque = obj.maxTorque || 0;
+ jd.correctionFactor = obj.correctionFactor || 0;
+ break;
+ case b2Joint.e_weldJoint:
+ jd.localAnchorA = parseVector(obj.anchorA);
+ jd.localAnchorB = parseVector(obj.anchorB);
+ jd.referenceAngle = obj.refAngle || 0;
+ jd.dampingRatio = obj.dampingRatio || 0;
+ jd.frequencyHz = obj.frequencyHz || 0;
+ break;
+ case b2Joint.e_frictionJoint:
+ jd.localAnchorA = parseVector(obj.anchorA);
+ jd.localAnchorB = parseVector(obj.anchorB);
+ jd.maxForce = obj.maxForce || 0;
+ jd.maxTorque = obj.maxTorque || 0;
+ break;
+ default:
+ throw new Error("wat?")
+ }
+ jd.bodyA = bodies[obj.bodyA || 0];
+ jd.bodyB = bodies[obj.bodyB || 0];
+ jd.collideConnected = obj.collideConnected || false;
+ var joint = world.CreateJoint(jd);
+ joint.name = obj.name;
+ if (obj.customProperties) {
+ for (var i = 0; i < obj.customProperties.length; ++i) {
+ parseProperty(obj, joint)
+ }
+ }
+ return joint
+ }
+
+ function b2RubeParameters() {
+ this.world = null;
+ this.positionIterations = 0;
+ this.velocityIterations = 0;
+ this.stepsPerSecond = 0;
+ this.fixtures = {};
+ this.bodies = {};
+ this.joints = {};
+ Object.seal(this)
+ }
+
+ function parseWorld(obj, world) {
+ var params = new b2RubeParameters();
+ params.world = world = world || new b2World(new b2Vec2(0, 0));
+ params.positionIterations = obj.positionIterations || 0;
+ params.velocityIterations = obj.velocityIterations || 0;
+ params.stepsPerSecond = obj.stepsPerSecond || 0;
+ if (obj.gravity) {
+ world.SetGravity(parseVector(obj.gravity))
+ }
+ world.SetAllowSleeping(obj.allowSleep || false);
+ world.SetAutoClearForces(obj.autoClearForces || false);
+ world.SetWarmStarting(obj.warmStarting || false);
+ world.SetContinuousPhysics(obj.continuousPhysics || false);
+ world.SetSubStepping(obj.subStepping || false);
+ var bodies = [];
+ var bl = obj.body;
+ if (bl) {
+ for (var i = 0; i < bl.length; ++i) {
+ var body = parseBody(bl[i], world);
+ bodies.push(body);
+ for (var f = body.GetFixtureList(); f; f = f.GetNext()) {
+ if (!params.fixtures[f.name]) {
+ params.fixtures[f.name] = []
+ }
+ params.fixtures[f.name].push(f)
+ }
+ if (!params.bodies[body.name]) {
+ params.bodies[body.name] = []
+ }
+ params.bodies[body.name].push(body)
+ }
+ }
+ var joints = [];
+ var jl = obj.joint;
+ if (jl) {
+ for (i = 0; i < jl.length; ++i) {
+ var joint = parseJoint(jl[i], world, bodies);
+ joints.push(joint);
+ if (!params.joints[joint.name]) {
+ params.joints[joint.name] = []
+ }
+ params.joints[joint.name].push(joint)
+ }
+ }
+ return params
+ }
+ return {
+ parseWorld: parseWorld
+ }
+})();
+var mappings = [{
+ trimmed: "version",
+ name: "b2_version",
+ def: b2_version
+}, {
+ trimmed: "Vec2",
+ name: "b2Vec2",
+ def: b2Vec2
+}, {
+ trimmed: "Vec3",
+ name: "b2Vec3",
+ def: b2Vec3
+}, {
+ trimmed: "Mat22",
+ name: "b2Mat22",
+ def: b2Mat22
+}, {
+ trimmed: "Mat33",
+ name: "b2Mat33",
+ def: b2Mat33
+}, {
+ trimmed: "Rot",
+ name: "b2Rot",
+ def: b2Rot
+}, {
+ trimmed: "Transform",
+ name: "b2Transform",
+ def: b2Transform
+}, {
+ trimmed: "Sweep",
+ name: "b2Sweep",
+ def: b2Sweep
+}, {
+ trimmed: "Dot_v2_v2",
+ name: "b2Dot_v2_v2",
+ def: b2Dot_v2_v2
+}, {
+ trimmed: "Cross_v2_v2",
+ name: "b2Cross_v2_v2",
+ def: b2Cross_v2_v2
+}, {
+ trimmed: "Cross_v2_f",
+ name: "b2Cross_v2_f",
+ def: b2Cross_v2_f
+}, {
+ trimmed: "Cross_f_v2",
+ name: "b2Cross_f_v2",
+ def: b2Cross_f_v2
+}, {
+ trimmed: "Mul_m22_v2",
+ name: "b2Mul_m22_v2",
+ def: b2Mul_m22_v2
+}, {
+ trimmed: "MulT_m22_v2",
+ name: "b2MulT_m22_v2",
+ def: b2MulT_m22_v2
+}, {
+ trimmed: "Distance",
+ name: "b2Distance",
+ def: b2Distance
+}, {
+ trimmed: "DistanceSquared",
+ name: "b2DistanceSquared",
+ def: b2DistanceSquared
+}, {
+ trimmed: "Dot_v3_v3",
+ name: "b2Dot_v3_v3",
+ def: b2Dot_v3_v3
+}, {
+ trimmed: "Cross_v3_v3",
+ name: "b2Cross_v3_v3",
+ def: b2Cross_v3_v3
+}, {
+ trimmed: "Mul_m22_m22",
+ name: "b2Mul_m22_m22",
+ def: b2Mul_m22_m22
+}, {
+ trimmed: "MulT_m22_m22",
+ name: "b2MulT_m22_m22",
+ def: b2MulT_m22_m22
+}, {
+ trimmed: "Mul_m33_v3",
+ name: "b2Mul_m33_v3",
+ def: b2Mul_m33_v3
+}, {
+ trimmed: "Mul22_m33_v2",
+ name: "b2Mul22_m33_v2",
+ def: b2Mul22_m33_v2
+}, {
+ trimmed: "Mul_r_r",
+ name: "b2Mul_r_r",
+ def: b2Mul_r_r
+}, {
+ trimmed: "MulT_r_r",
+ name: "b2MulT_r_r",
+ def: b2MulT_r_r
+}, {
+ trimmed: "Mul_r_v2",
+ name: "b2Mul_r_v2",
+ def: b2Mul_r_v2
+}, {
+ trimmed: "MulT_r_v2",
+ name: "b2MulT_r_v2",
+ def: b2MulT_r_v2
+}, {
+ trimmed: "Mul_t_v2",
+ name: "b2Mul_t_v2",
+ def: b2Mul_t_v2
+}, {
+ trimmed: "Min_v2",
+ name: "b2Min_v2",
+ def: b2Min_v2
+}, {
+ trimmed: "Max_v2",
+ name: "b2Max_v2",
+ def: b2Max_v2
+}, {
+ trimmed: "Clamp",
+ name: "b2Clamp",
+ def: b2Clamp
+}, {
+ trimmed: "MulT_t_v2",
+ name: "b2MulT_t_v2",
+ def: b2MulT_t_v2
+}, {
+ trimmed: "Mul_t_t",
+ name: "b2Mul_t_t",
+ def: b2Mul_t_t
+}, {
+ trimmed: "MulT_t_t",
+ name: "b2MulT_t_t",
+ def: b2MulT_t_t
+}, {
+ trimmed: "Clamp_v2",
+ name: "b2Clamp_v2",
+ def: b2Clamp_v2
+}, {
+ trimmed: "NextPowerOfTwo",
+ name: "b2NextPowerOfTwo",
+ def: b2NextPowerOfTwo
+}, {
+ trimmed: "Abs_v2",
+ name: "b2Abs_v2",
+ def: b2Abs_v2
+}, {
+ trimmed: "Abs_m22",
+ name: "b2Abs_m22",
+ def: b2Abs_m22
+}, {
+ trimmed: "IsPowerOfTwo",
+ name: "b2IsPowerOfTwo",
+ def: b2IsPowerOfTwo
+}, {
+ trimmed: "RandomFloat",
+ name: "b2RandomFloat",
+ def: b2RandomFloat
+}, {
+ trimmed: "Timer",
+ name: "b2Timer",
+ def: b2Timer
+}, {
+ trimmed: "Color",
+ name: "b2Color",
+ def: b2Color
+}, {
+ trimmed: "Draw",
+ name: "b2Draw",
+ def: b2Draw
+}, {
+ trimmed: "ContactID",
+ name: "b2ContactID",
+ def: b2ContactID
+}, {
+ trimmed: "ManifoldPoint",
+ name: "b2ManifoldPoint",
+ def: b2ManifoldPoint
+}, {
+ trimmed: "Manifold",
+ name: "b2Manifold",
+ def: b2Manifold
+}, {
+ trimmed: "WorldManifold",
+ name: "b2WorldManifold",
+ def: b2WorldManifold
+}, {
+ trimmed: "GetPointStates",
+ name: "b2GetPointStates",
+ def: b2GetPointStates
+}, {
+ trimmed: "ClipVertex",
+ name: "b2ClipVertex",
+ def: b2ClipVertex
+}, {
+ trimmed: "RayCastInput",
+ name: "b2RayCastInput",
+ def: b2RayCastInput
+}, {
+ trimmed: "RayCastOutput",
+ name: "b2RayCastOutput",
+ def: b2RayCastOutput
+}, {
+ trimmed: "AABB",
+ name: "b2AABB",
+ def: b2AABB
+}, {
+ trimmed: "CollideCircles",
+ name: "b2CollideCircles",
+ def: b2CollideCircles
+}, {
+ trimmed: "CollidePolygonAndCircle",
+ name: "b2CollidePolygonAndCircle",
+ def: b2CollidePolygonAndCircle
+}, {
+ trimmed: "FindMaxSeparation",
+ name: "b2FindMaxSeparation",
+ def: b2FindMaxSeparation
+}, {
+ trimmed: "FindIncidentEdge",
+ name: "b2FindIncidentEdge",
+ def: b2FindIncidentEdge
+}, {
+ trimmed: "CollidePolygons",
+ name: "b2CollidePolygons",
+ def: b2CollidePolygons
+}, {
+ trimmed: "CollideEdgeAndCircle",
+ name: "b2CollideEdgeAndCircle",
+ def: b2CollideEdgeAndCircle
+}, {
+ trimmed: "EPAxis",
+ name: "b2EPAxis",
+ def: b2EPAxis
+}, {
+ trimmed: "TempPolygon",
+ name: "b2TempPolygon",
+ def: b2TempPolygon
+}, {
+ trimmed: "ReferenceFace",
+ name: "b2ReferenceFace",
+ def: b2ReferenceFace
+}, {
+ trimmed: "EPCollider",
+ name: "b2EPCollider",
+ def: b2EPCollider
+}, {
+ trimmed: "CollideEdgeAndPolygon",
+ name: "b2CollideEdgeAndPolygon",
+ def: b2CollideEdgeAndPolygon
+}, {
+ trimmed: "ClipSegmentToLine",
+ name: "b2ClipSegmentToLine",
+ def: b2ClipSegmentToLine
+}, {
+ trimmed: "TestShapeOverlap",
+ name: "b2TestShapeOverlap",
+ def: b2TestShapeOverlap
+}, {
+ trimmed: "TestOverlap",
+ name: "b2TestOverlap",
+ def: b2TestOverlap
+}, {
+ trimmed: "Shape",
+ name: "b2Shape",
+ def: b2Shape
+}, {
+ trimmed: "CircleShape",
+ name: "b2CircleShape",
+ def: b2CircleShape
+}, {
+ trimmed: "EdgeShape",
+ name: "b2EdgeShape",
+ def: b2EdgeShape
+}, {
+ trimmed: "ChainShape",
+ name: "b2ChainShape",
+ def: b2ChainShape
+}, {
+ trimmed: "PolygonShape",
+ name: "b2PolygonShape",
+ def: b2PolygonShape
+}, {
+ trimmed: "Pair",
+ name: "b2Pair",
+ def: b2Pair
+}, {
+ trimmed: "PairLessThan",
+ name: "b2PairLessThan",
+ def: b2PairLessThan
+}, {
+ trimmed: "BroadPhase",
+ name: "b2BroadPhase",
+ def: b2BroadPhase
+}, {
+ trimmed: "DistanceProxy",
+ name: "b2DistanceProxy",
+ def: b2DistanceProxy
+}, {
+ trimmed: "SimplexCache",
+ name: "b2SimplexCache",
+ def: b2SimplexCache
+}, {
+ trimmed: "DistanceInput",
+ name: "b2DistanceInput",
+ def: b2DistanceInput
+}, {
+ trimmed: "DistanceOutput",
+ name: "b2DistanceOutput",
+ def: b2DistanceOutput
+}, {
+ trimmed: "SimplexVertex",
+ name: "b2SimplexVertex",
+ def: b2SimplexVertex
+}, {
+ trimmed: "Simplex",
+ name: "b2Simplex",
+ def: b2Simplex
+}, {
+ trimmed: "DistanceFunc",
+ name: "b2DistanceFunc",
+ def: b2DistanceFunc
+}, {
+ trimmed: "TreeNode",
+ name: "b2TreeNode",
+ def: b2TreeNode
+}, {
+ trimmed: "DynamicTree",
+ name: "b2DynamicTree",
+ def: b2DynamicTree
+}, {
+ trimmed: "TOIInput",
+ name: "b2TOIInput",
+ def: b2TOIInput
+}, {
+ trimmed: "TOIOutput",
+ name: "b2TOIOutput",
+ def: b2TOIOutput
+}, {
+ trimmed: "SeparationFunction",
+ name: "b2SeparationFunction",
+ def: b2SeparationFunction
+}, {
+ trimmed: "TimeOfImpact",
+ name: "b2TimeOfImpact",
+ def: b2TimeOfImpact
+}, {
+ trimmed: "BodyDef",
+ name: "b2BodyDef",
+ def: b2BodyDef
+}, {
+ trimmed: "Body",
+ name: "b2Body",
+ def: b2Body
+}, {
+ trimmed: "Filter",
+ name: "b2Filter",
+ def: b2Filter
+}, {
+ trimmed: "FixtureDef",
+ name: "b2FixtureDef",
+ def: b2FixtureDef
+}, {
+ trimmed: "Fixture",
+ name: "b2Fixture",
+ def: b2Fixture
+}, {
+ trimmed: "DestructionListener",
+ name: "b2DestructionListener",
+ def: b2DestructionListener
+}, {
+ trimmed: "ContactFilter",
+ name: "b2ContactFilter",
+ def: b2ContactFilter
+}, {
+ trimmed: "ContactImpulse",
+ name: "b2ContactImpulse",
+ def: b2ContactImpulse
+}, {
+ trimmed: "ContactListener",
+ name: "b2ContactListener",
+ def: b2ContactListener
+}, {
+ trimmed: "QueryCallback",
+ name: "b2QueryCallback",
+ def: b2QueryCallback
+}, {
+ trimmed: "RayCastCallback",
+ name: "b2RayCastCallback",
+ def: b2RayCastCallback
+}, {
+ trimmed: "TimeStep",
+ name: "b2TimeStep",
+ def: b2TimeStep
+}, {
+ trimmed: "Position",
+ name: "b2Position",
+ def: b2Position
+}, {
+ trimmed: "Velocity",
+ name: "b2Velocity",
+ def: b2Velocity
+}, {
+ trimmed: "SolverData",
+ name: "b2SolverData",
+ def: b2SolverData
+}, {
+ trimmed: "World",
+ name: "b2World",
+ def: b2World
+}, {
+ trimmed: "MixFriction",
+ name: "b2MixFriction",
+ def: b2MixFriction
+}, {
+ trimmed: "MixRestitution",
+ name: "b2MixRestitution",
+ def: b2MixRestitution
+}, {
+ trimmed: "ContactRegister",
+ name: "b2ContactRegister",
+ def: b2ContactRegister
+}, {
+ trimmed: "ContactEdge",
+ name: "b2ContactEdge",
+ def: b2ContactEdge
+}, {
+ trimmed: "Contact",
+ name: "b2Contact",
+ def: b2Contact
+}, {
+ trimmed: "CircleContact",
+ name: "b2CircleContact",
+ def: b2CircleContact
+}, {
+ trimmed: "PolygonContact",
+ name: "b2PolygonContact",
+ def: b2PolygonContact
+}, {
+ trimmed: "ChainAndCircleContact",
+ name: "b2ChainAndCircleContact",
+ def: b2ChainAndCircleContact
+}, {
+ trimmed: "ChainAndPolygonContact",
+ name: "b2ChainAndPolygonContact",
+ def: b2ChainAndPolygonContact
+}, {
+ trimmed: "EdgeAndCircleContact",
+ name: "b2EdgeAndCircleContact",
+ def: b2EdgeAndCircleContact
+}, {
+ trimmed: "EdgeAndPolygonContact",
+ name: "b2EdgeAndPolygonContact",
+ def: b2EdgeAndPolygonContact
+}, {
+ trimmed: "PolygonAndCircleContact",
+ name: "b2PolygonAndCircleContact",
+ def: b2PolygonAndCircleContact
+}, {
+ trimmed: "defaultFilter",
+ name: "b2_defaultFilter",
+ def: b2_defaultFilter
+}, {
+ trimmed: "defaultListener",
+ name: "b2_defaultListener",
+ def: b2_defaultListener
+}, {
+ trimmed: "ContactManager",
+ name: "b2ContactManager",
+ def: b2ContactManager
+}, {
+ trimmed: "VelocityConstraintPoint",
+ name: "b2VelocityConstraintPoint",
+ def: b2VelocityConstraintPoint
+}, {
+ trimmed: "ContactPositionConstraint",
+ name: "b2ContactPositionConstraint",
+ def: b2ContactPositionConstraint
+}, {
+ trimmed: "ContactVelocityConstraint",
+ name: "b2ContactVelocityConstraint",
+ def: b2ContactVelocityConstraint
+}, {
+ trimmed: "PositionSolverManifold",
+ name: "b2PositionSolverManifold",
+ def: b2PositionSolverManifold
+}, {
+ trimmed: "ContactSolverDef",
+ name: "b2ContactSolverDef",
+ def: b2ContactSolverDef
+}, {
+ trimmed: "ContactSolver",
+ name: "b2ContactSolver",
+ def: b2ContactSolver
+}, {
+ trimmed: "Island",
+ name: "b2Island",
+ def: b2Island
+}, {
+ trimmed: "Jacobian",
+ name: "b2Jacobian",
+ def: b2Jacobian
+}, {
+ trimmed: "JointEdge",
+ name: "b2JointEdge",
+ def: b2JointEdge
+}, {
+ trimmed: "JointDef",
+ name: "b2JointDef",
+ def: b2JointDef
+}, {
+ trimmed: "Joint",
+ name: "b2Joint",
+ def: b2Joint
+}, {
+ trimmed: "RevoluteJointDef",
+ name: "b2RevoluteJointDef",
+ def: b2RevoluteJointDef
+}, {
+ trimmed: "RevoluteJoint",
+ name: "b2RevoluteJoint",
+ def: b2RevoluteJoint
+}, {
+ trimmed: "MouseJointDef",
+ name: "b2MouseJointDef",
+ def: b2MouseJointDef
+}, {
+ trimmed: "MouseJoint",
+ name: "b2MouseJoint",
+ def: b2MouseJoint
+}, {
+ trimmed: "DistanceJointDef",
+ name: "b2DistanceJointDef",
+ def: b2DistanceJointDef
+}, {
+ trimmed: "DistanceJoint",
+ name: "b2DistanceJoint",
+ def: b2DistanceJoint
+}, {
+ trimmed: "PrismaticJointDef",
+ name: "b2PrismaticJointDef",
+ def: b2PrismaticJointDef
+}, {
+ trimmed: "PrismaticJoint",
+ name: "b2PrismaticJoint",
+ def: b2PrismaticJoint
+}, {
+ trimmed: "FrictionJointDef",
+ name: "b2FrictionJointDef",
+ def: b2FrictionJointDef
+}, {
+ trimmed: "FrictionJoint",
+ name: "b2FrictionJoint",
+ def: b2FrictionJoint
+}, {
+ trimmed: "WeldJointDef",
+ name: "b2WeldJointDef",
+ def: b2WeldJointDef
+}, {
+ trimmed: "WeldJoint",
+ name: "b2WeldJoint",
+ def: b2WeldJoint
+}, {
+ trimmed: "WheelJointDef",
+ name: "b2WheelJointDef",
+ def: b2WheelJointDef
+}, {
+ trimmed: "WheelJoint",
+ name: "b2WheelJoint",
+ def: b2WheelJoint
+}, {
+ trimmed: "GearJointDef",
+ name: "b2GearJointDef",
+ def: b2GearJointDef
+}, {
+ trimmed: "GearJoint",
+ name: "b2GearJoint",
+ def: b2GearJoint
+}, {
+ trimmed: "MotorJointDef",
+ name: "b2MotorJointDef",
+ def: b2MotorJointDef
+}, {
+ trimmed: "MotorJoint",
+ name: "b2MotorJoint",
+ def: b2MotorJoint
+}, {
+ trimmed: "PulleyJointDef",
+ name: "b2PulleyJointDef",
+ def: b2PulleyJointDef
+}, {
+ trimmed: "PulleyJoint",
+ name: "b2PulleyJoint",
+ def: b2PulleyJoint
+}, {
+ trimmed: "RopeJointDef",
+ name: "b2RopeJointDef",
+ def: b2RopeJointDef
+}, {
+ trimmed: "RopeJoint",
+ name: "b2RopeJoint",
+ def: b2RopeJoint
+}, {
+ trimmed: "RopeDef",
+ name: "b2RopeDef",
+ def: b2RopeDef
+}, {
+ trimmed: "Rope",
+ name: "b2Rope",
+ def: b2Rope
+}, {
+ trimmed: "maxManifoldPoints",
+ name: "b2_maxManifoldPoints",
+ def: b2_maxManifoldPoints
+}, {
+ trimmed: "maxPolygonVertices",
+ name: "b2_maxPolygonVertices",
+ def: b2_maxPolygonVertices
+}, {
+ trimmed: "aabbExtension",
+ name: "b2_aabbExtension",
+ def: b2_aabbExtension
+}, {
+ trimmed: "aabbMultiplier",
+ name: "b2_aabbMultiplier",
+ def: b2_aabbMultiplier
+}, {
+ trimmed: "linearSlop",
+ name: "b2_linearSlop",
+ def: b2_linearSlop
+}, {
+ trimmed: "angularSlop",
+ name: "b2_angularSlop",
+ def: b2_angularSlop
+}, {
+ trimmed: "polygonRadius",
+ name: "b2_polygonRadius",
+ def: b2_polygonRadius
+}, {
+ trimmed: "maxSubSteps",
+ name: "b2_maxSubSteps",
+ def: b2_maxSubSteps
+}, {
+ trimmed: "maxTOIContacts",
+ name: "b2_maxTOIContacts",
+ def: b2_maxTOIContacts
+}, {
+ trimmed: "velocityThreshold",
+ name: "b2_velocityThreshold",
+ def: b2_velocityThreshold
+}, {
+ trimmed: "maxLinearCorrection",
+ name: "b2_maxLinearCorrection",
+ def: b2_maxLinearCorrection
+}, {
+ trimmed: "maxAngularCorrection",
+ name: "b2_maxAngularCorrection",
+ def: b2_maxAngularCorrection
+}, {
+ trimmed: "maxTranslation",
+ name: "b2_maxTranslation",
+ def: b2_maxTranslation
+}, {
+ trimmed: "maxTranslationSquared",
+ name: "b2_maxTranslationSquared",
+ def: b2_maxTranslationSquared
+}, {
+ trimmed: "maxRotation",
+ name: "b2_maxRotation",
+ def: b2_maxRotation
+}, {
+ trimmed: "maxRotationSquared",
+ name: "b2_maxRotationSquared",
+ def: b2_maxRotationSquared
+}, {
+ trimmed: "baumgarte",
+ name: "b2_baumgarte",
+ def: b2_baumgarte
+}, {
+ trimmed: "toiBaugarte",
+ name: "b2_toiBaugarte",
+ def: b2_toiBaugarte
+}, {
+ trimmed: "timeToSleep",
+ name: "b2_timeToSleep",
+ def: b2_timeToSleep
+}, {
+ trimmed: "linearSleepTolerance",
+ name: "b2_linearSleepTolerance",
+ def: b2_linearSleepTolerance
+}, {
+ trimmed: "angularSleepTolerance",
+ name: "b2_angularSleepTolerance",
+ def: b2_angularSleepTolerance
+}, {
+ trimmed: "epsilon",
+ name: "b2_epsilon",
+ def: b2_epsilon
+}, {
+ trimmed: "JsonSerializer",
+ name: "b2JsonSerializer",
+ def: b2JsonSerializer
+}, {
+ trimmed: "RUBELoader",
+ name: "b2RUBELoader",
+ def: b2RUBELoader
+}, {
+ trimmed: "Profiler",
+ name: "b2Profiler",
+ def: b2Profiler
+}];
+
+
+if (typeof (b2_compatibility) !== "undefined" && typeof (window) !== "undefined") {
+ for (var i = 0; i < mappings.length; ++i) {
+ window[mappings[i].name] = mappings[i].def
+ }
+} else {
+ var b2 = {};
+ for (var i = 0; i < mappings.length; ++i) {
+ b2[mappings[i].trimmed] = mappings[i].def
+ }
+ if (typeof (module) !== "undefined") {
+ module.exports = b2
+ } else {
+ window.b2 = b2
+ }
+}
\ No newline at end of file
diff --git a/js/draw_p5js.js b/js/draw_p5js.js
new file mode 100644
index 0000000000000000000000000000000000000000..aa25c107ecd4a1240c003897a33ab57668e4a61b
--- /dev/null
+++ b/js/draw_p5js.js
@@ -0,0 +1,592 @@
+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;
+ }
+ let age = agent.age == "adult" ? "" : " (" + window.lang_dict[window.get_language()]['morphologies'][agent.age] + ")";
+ text(agent.name + age, 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]);
+}
\ No newline at end of file
diff --git a/js/envs/multi_agents_continuous_parkour.js b/js/envs/multi_agents_continuous_parkour.js
new file mode 100644
index 0000000000000000000000000000000000000000..1bbb4e7dbb8e7ca9c6912641570589d0e4cc0c6a
--- /dev/null
+++ b/js/envs/multi_agents_continuous_parkour.js
@@ -0,0 +1,1264 @@
+//region Constants
+
+const FPS = 50;
+const SCALE = 30; // affects how fast-paced the game is, forces should be adjusted as well
+const VIEWPORT_W = 600;
+const VIEWPORT_H = 400;
+
+let RENDERING_VIEWER_W = VIEWPORT_W;
+let RENDERING_VIEWER_H = VIEWPORT_H
+
+const NB_LIDAR = 10;
+const LIDAR_RANGE = 160/SCALE;
+
+const TERRAIN_STEP = 14/SCALE;
+const TERRAIN_LENGTH = 200; // in steps
+const TERRAIN_HEIGHT = VIEWPORT_H/SCALE/4;
+const TERRAIN_END = 5;
+const INITIAL_TERRAIN_STARTPAD = 20; // in steps
+const FRICTION = 2.5;
+const WATER_DENSITY = 1.0;
+const CREEPER_UNIT = 1;
+
+const SCROLL_X_MAX = 500;
+const SCROLL_Y_MAX = 300;
+const INIT_SCROLL_X = -0.035 * RENDERING_VIEWER_W;
+const THUMBNAIL_SCROLL_X = 0;
+let THUMBNAIL_ZOOM = 0.27;
+let INIT_ZOOM = 0.27;
+
+//endregion
+
+/**
+ * @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.
+ */
+class MultiAgentsContinuousParkour {
+
+ /**
+ * @constructor
+ * @param agents {{morphologies: Array, policies: Array, positions: Array}} - Morphologies, policies and positions of the agents
+ * @param input_CPPN_dim {number} - Dimension of the array that encodes the CPPN
+ * @param [terrain_cppn_scale=10] {number} - Smoothing
+ * @param [ceiling_offset=200] {number}
+ * @param [ceiling_clip_offset=0] {number}
+ * @param [water_clip=20] {number}
+ * @param [movable_creepers=false] {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_terrain {Object}
+ */
+ constructor(agents, input_CPPN_dim=3, terrain_cppn_scale=10,
+ ceiling_offset=200, ceiling_clip_offset=0, water_clip=20,
+ movable_creepers=false, ground, ceiling, align_terrain){
+
+ // Initializes class attributes
+ this.scale = SCALE;
+ this.zoom = INIT_ZOOM;
+ this.movable_creepers = movable_creepers;
+ this.terrain_bodies = [];
+ this.background_polys = [];
+ this.creepers_joints = [];
+ this.ground = ground;
+ this.ceiling = ceiling;
+ this.align_terrain = align_terrain;
+
+ // Initializes Box2D
+ this.contact_listener = new ContactDetector(this);
+ let gravity = new b2.Vec2(0, -10);
+ this.world = new b2.World(gravity);
+ this.world.SetContactListener(this.contact_listener);
+
+ // Creates the agents
+ this.agents = [];
+ console.assert(agents.morphologies.length == agents.policies.length && agents.morphologies.length == agents.positions.length);
+ for(let i = 0; i < agents.morphologies.length; i++){
+ this.create_agent(agents.morphologies[i], agents.policies[i], agents.positions[i]);
+ //this.create_agent("spider", {name: "random", path: null}, null);
+ }
+
+ // Initializes dynamics
+ this.water_dynamics = new WaterDynamics(this.world.m_gravity);
+ this.climbing_dynamics = new ClimbingDynamics();
+
+ // Creates the Box2D fixtures
+ this.create_terrain_fixtures();
+
+ // Initializes the CPPN and scales the terrain
+ this.terrain_CPPN = new CPPN(TERRAIN_LENGTH, input_CPPN_dim);
+ this.set_terrain_cppn_scale(terrain_cppn_scale, ceiling_offset, ceiling_clip_offset);
+ }
+
+ /**
+ * Creates an agent with the given parameters.
+ * @param morphology {string} - Name of the morphology
+ * @param policy {{name: string, age: string, path: string}} - Name and path of the policy model
+ * @param init_pos {{x: number, y: number}} - Initial position of the agent
+ */
+ create_agent(morphology, policy, init_pos){
+
+ let agent = {
+ id: this.agents.length,
+ name: policy.name,
+ age: policy.age,
+ is_selected: false,
+ morphology: morphology,
+ policy: policy,
+ init_pos: init_pos,
+ prev_shaping: null,
+ episodic_reward: 0,
+ };
+
+ // Initializes the agent's body and lidars according to the morphology
+ if(morphology == "bipedal") {
+ agent.agent_body = new ClassicBipedalBody(SCALE);
+ agent.lidars_config = this.set_lidars_type("down");
+ }
+ else if(morphology == "chimpanzee") {
+ agent.agent_body = new ClimbingProfileChimpanzee(SCALE);
+ agent.lidars_config = this.set_lidars_type("up");
+ }
+ else if(morphology == "fish"){
+ agent.agent_body = new FishBody(SCALE, 80, WATER_DENSITY, 600);
+ agent.lidars_config = this.set_lidars_type("full");
+ }
+ else if(morphology == "spider"){
+ agent.agent_body = new SpiderBody(SCALE);
+ agent.lidars_config = this.set_lidars_type("down");
+ }
+ else {
+ agent.agent_body = new ClassicBipedalBody(SCALE);
+ agent.lidars_config = this.set_lidars_type("down");
+ }
+
+ // Adds the new agent to the list of agents
+ this.agents.push(agent);
+ }
+
+ /**
+ * Seeds the random generator according to the terrain parameters.
+ */
+ seed(){
+ // Creates a string with all the terrain parameters
+ let seed = "";
+ for(let dim of this.CPPN_input_vector){
+ seed += dim;
+ }
+ seed += this.water_level;
+ seed += this.TERRAIN_CPPN_SCALE;
+ seed += this.creepers_width;
+ seed += this.creepers_height;
+ seed += this.creepers_spacing;
+ seed += this.movable_creepers;;
+
+ Math.seedrandom(seed);
+ }
+
+ /**
+ * Returns a lidars configuration according to the given type.
+ * @param lidars_type {string} - 'down', 'up', or 'full'
+ * @returns {{lidar_angle: number, lidar_y_offset: number}}
+ */
+ set_lidars_type(lidars_type){
+ // Use 'down' for walkers, 'up' for climbers and 'full' for swimmers.
+ let lidar_config = {};
+ if(lidars_type == "down") {
+ lidar_config.lidar_angle = 1.5;
+ lidar_config.lidar_y_offset = 0;
+ }
+ else if(lidars_type == "up") {
+ lidar_config.lidar_angle = 2.3;
+ lidar_config.lidar_y_offset = 1.5;
+ }
+ else if(lidars_type == "full") {
+ lidar_config.lidar_angle = Math.PI;
+ lidar_config.lidar_y_offset = 0;
+ }
+ return lidar_config;
+ }
+
+ /**
+ * Scales the terrain according to the smoothing.
+ * @param terrain_cppn_scale {number} - Smoothing
+ * @param ceiling_offset {number}
+ * @param ceiling_clip_offset {number}
+ */
+ set_terrain_cppn_scale(terrain_cppn_scale, ceiling_offset, ceiling_clip_offset){
+ console.assert(terrain_cppn_scale > 1);
+ this.TERRAIN_CPPN_SCALE = terrain_cppn_scale;
+ this.CEILING_LIMIT = 1000 / this.TERRAIN_CPPN_SCALE;
+ this.GROUND_LIMIT = -1000 / INITIAL_TERRAIN_STARTPAD;
+ this.ceiling_offset = ceiling_offset / this.TERRAIN_CPPN_SCALE;
+ this.ceiling_clip_offset = ceiling_clip_offset / this.TERRAIN_CPPN_SCALE;
+ }
+
+ /**
+ * Sets the parameters for terrain generation.
+ * Must be called before `reset()`.
+ * @param 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 terrain_cppn_scale {number} - Smoothing
+ * @param movable_creepers {boolean}
+ */
+ set_environment(input_vector, water_level, creepers_width=null,
+ creepers_height=null, creepers_spacing=0.1, terrain_cppn_scale=10, movable_creepers){
+
+ this.CPPN_input_vector = input_vector;
+ this.water_level = water_level > 0 ? water_level : - 0.01;
+ this.creepers_width = creepers_width;
+ this.creepers_height = creepers_height;
+ this.creepers_spacing = Math.max(0.01, creepers_spacing);
+ this.movable_creepers = movable_creepers;
+ this.set_terrain_cppn_scale(terrain_cppn_scale,
+ this.ceiling_offset * this.TERRAIN_CPPN_SCALE,
+ this.ceiling_clip_offset * this.TERRAIN_CPPN_SCALE);
+ this.seed();
+ }
+
+ /**
+ * Destroys all the bodies composing the terrain and the agents
+ */
+ _destroy(){
+ this.world.SetContactListener(null);
+ for(let t of this.terrain_bodies){
+ this.world.DestroyBody(t.body);
+ }
+ this.terrain_bodies = [];
+ this.creepers_joints = [];
+ for(let agent of this.agents){
+ agent.agent_body.destroy(this.world);
+ }
+ }
+
+ /**
+ * Resets the environment.
+ * @returns {*[]} - Array that contains the observation state and reward of each agent.
+ */
+ reset(){
+ this._destroy();
+ this.contact_listener = new ContactDetector(this);
+ this.world.SetContactListener(this.contact_listener);
+ this.scroll = [0, 0];
+ this.water_y = this.GROUND_LIMIT;
+ this.assets_bodies = [];
+
+ for(let agent of this.agents){
+ agent.nb_steps_outside_water = 0;
+ agent.nb_steps_under_water = 0;
+ agent.critical_contact = false;
+ }
+
+ // Generates the terrain and the agents
+ this.generate_game();
+
+ // Initializes all the agents
+ for(let agent of this.agents) {
+ this.init_agent(agent);
+ }
+
+ // Runs a simulation step and returns the results
+ return this.step();
+ }
+
+ /**
+ * Initializes the given agent.
+ * @param agent {Object}
+ */
+ init_agent(agent){
+ // Creates the lidars of the agent
+ agent.lidars = [];
+ for(let i = 0; i < NB_LIDAR; i++){
+ agent.lidars.push(new LidarCallback(agent.agent_body.reference_head_object.GetFixtureList().GetFilterData().maskBits));
+ }
+
+ // Initializes the agent with motionless actions
+ agent.actions = Array.from({length: agent.agent_body.get_action_size()}, () => 0);
+
+ agent.nb_steps_outside_water = 0;
+ agent.nb_steps_under_water = 0;
+ agent.episodic_reward = 0;
+ }
+
+ /**
+ * Initializes the position of the given climber agent so that it hangs from the ceiling.
+ * @param agent {Object}
+ */
+ init_climber_pos(agent){
+ let y_diff = 0;
+ for(let i = 0; i < agent.agent_body.sensors.length; i++){
+
+ // Tells this sensor to grasp
+ agent.actions[agent.actions.length - i - 1] = 1;
+
+ let sensor = agent.agent_body.sensors[agent.agent_body.sensors.length - i - 1];
+ let sensor_position = sensor.GetPosition();
+
+ // Finds the best y-coordinate of the ceiling according to the x-coordinate of the sensor
+ let ceiling_y = find_best_y(sensor_position.x, this.terrain_ceiling);
+
+ // Computes the vertical offset
+ if(y_diff == 0){
+ y_diff = ceiling_y - sensor_position.y;
+ }
+
+ // Sets the position of the sensor
+ sensor.SetTransform(new b2.Vec2(sensor_position.x, ceiling_y),
+ sensor.GetAngle());
+ }
+
+ // Shifts the position of each body part by the vertical offset
+ for(let body_part of agent.agent_body.body_parts){
+ let body_part_pos = body_part.GetPosition();
+ body_part.SetTransform(new b2Vec2(body_part_pos.x, body_part_pos.y + y_diff),
+ body_part.GetAngle());
+ }
+ }
+
+ /**
+ * Runs one step and updates the observation of the agents
+ * @returns {Array} - List of return of each agent : [state, reward, done, {success: boolean}]
+ */
+ step(){
+ // Checks if agents are dead according to their morphology
+ for(let agent of this.agents){
+ if(agent.agent_body.body_type == BodyTypesEnum.SWIMMER){
+ if(agent.nb_steps_outside_water > agent.agent_body.nb_steps_can_survive_outside_water){
+ agent.is_dead = true;
+ agent.actions = Array.from({length: agent.agent_body.get_action_size()}, () => 0);
+ }
+ else{
+ agent.is_dead = false;
+ }
+ }
+ else{
+ if(agent.nb_steps_under_water > agent.agent_body.nb_steps_can_survive_under_water){
+ agent.is_dead = true;
+ agent.actions = Array.from({length: agent.agent_body.get_action_size()}, () => 0);
+ }
+ else{
+ agent.is_dead = false;
+ }
+ }
+
+ // Makes the agent moves according to its actions
+ agent.agent_body.activate_motors(agent.actions);
+
+ // Prepares climbing dynamics according to the grasping actions (i.e. readies sensor to grasp or release sensor grip by destroying joint)
+ if(agent.agent_body.body_type == BodyTypesEnum.CLIMBER){
+ this.climbing_dynamics.before_step_climbing_dynamics(agent.actions, agent.agent_body, this.world);
+ }
+ }
+
+ // Updates Box2D world
+ //this.world.Step(1.0 / FPS, 45, 15);
+ this.world.Step(1.0 / FPS, 3 * 30, 1 * 30);
+
+ for(let agent of this.agents) {
+ // Creates joints between sensors ready to grasp if collision with graspable area was detected
+ if(agent.agent_body.body_type == BodyTypesEnum.CLIMBER){
+ this.climbing_dynamics.after_step_climbing_dynamics(this.world.m_contactManager.m_contactListener.climbing_contact_detector, this.world);
+ }
+ }
+
+ // Filters null fixture pairs to avoid errors with water collisions
+ 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){
+ return fp[0].GetShape() != null && fp[1].GetShape() != null;
+ });
+ // Calculates water physics
+ this.water_dynamics.calculate_forces(this.world.m_contactManager.m_contactListener.water_contact_detector.fixture_pairs);
+
+ let ret = [];
+
+ // Observation state for each agent
+ for(let agent of this.agents) {
+ let head = agent.agent_body.reference_head_object;
+ let pos = head.GetPosition();
+ let vel = head.GetLinearVelocity();
+
+ this.update_lidars(agent);
+
+ let is_under_water = pos.y <= this.water_y;
+ if(!agent.is_dead){
+ if(is_under_water){
+ agent.nb_steps_under_water += 1;
+ agent.nb_steps_outside_water = 0;
+ }
+ else{
+ agent.nb_steps_under_water = 0;
+ agent.nb_steps_outside_water += 1;
+ }
+ }
+
+ let state = [
+ head.GetAngle(), // Normal angles up to 0.5 here, but sure more is possible.
+ 2.0 * head.GetAngularVelocity() / FPS,
+ 0.3 * vel.x * (VIEWPORT_W / SCALE) / FPS, // Normalized to get [-1, 1] range
+ 0.3 * vel.y * (VIEWPORT_H / SCALE) / FPS,
+ is_under_water ? 1.0 : 0.0,
+ agent.is_dead ? 1.0 : 0.0
+ ];
+
+ // Adds motor-related state
+ state = state.concat(agent.agent_body.get_motors_state());
+
+ // Adds sensor-related state for climbers
+ if(agent.agent_body.body_type == BodyTypesEnum.CLIMBER){
+ state = state.concat(agent.agent_body.get_sensors_state());
+ }
+
+ // Adds lidar-related state with distance and surface detected
+ let nb_of_water_detected = 0;
+ let surface_detected = [];
+ for(let lidar of agent.lidars){
+ state.push(lidar.fraction);
+ if(lidar.is_water_detected){
+ surface_detected.push(-1);
+ nb_of_water_detected += 1;
+ }
+ else if(lidar.is_creeper_detected){
+ surface_detected.push(1)
+ }
+ else{
+ surface_detected.push(0);
+ }
+ }
+ state = state.concat(surface_detected)
+
+ let shaping = 130 * pos.x / SCALE; // moving forward is a way to receive reward (normalized to get 300 on completion)
+ if(agent.agent_body.remove_reward_on_head_angle){
+ shaping -= 5.0 * Math.abs(state[0]); // keep head straight, other than that and falling, any behavior is unpunished
+ }
+
+ let reward = 0;
+ if(agent.prev_shaping != null){
+ reward = shaping - agent.prev_shaping;
+ }
+ agent.prev_shaping = shaping;
+
+ for(let a of agent.actions){
+ reward -= agent.agent_body.TORQUE_PENALTY * 80 * Math.max(0, Math.min(Math.abs(a), 1));
+ // normalized to about -50.0 using heuristic, more optimal agent should spend less
+ }
+
+ // Ending conditions
+ let done = false;
+ if(agent.critical_contact || pos.x < 0){
+ reward -= 100;
+ done = true;
+ }
+ if(pos.x > (TERRAIN_LENGTH + INITIAL_TERRAIN_STARTPAD - TERRAIN_END) * TERRAIN_STEP){
+ done = true;
+ }
+ agent.episodic_reward += reward;
+
+ ret.push([state, reward, done, {"success": agent.episodic_reward > 230}]);
+ }
+
+ return ret;
+ }
+
+ /**
+ * Updates the lidars of the given agent by casting a ray for each lidar.
+ * @param agent {Object}
+ */
+ update_lidars(agent){
+ let pos = agent.agent_body.reference_head_object.GetPosition();
+ for(let i = 0; i < NB_LIDAR; i++){
+ agent.lidars[i].fraction = 1.0;
+ agent.lidars[i].p1 = pos;
+ agent.lidars[i].p2 = new b2.Vec2(
+ pos.x + Math.sin(agent.lidars_config.lidar_angle * i / NB_LIDAR + agent.lidars_config.lidar_y_offset) * LIDAR_RANGE,
+ pos.y - Math.cos(agent.lidars_config.lidar_angle * i / NB_LIDAR + agent.lidars_config.lidar_y_offset) * LIDAR_RANGE
+ );
+ this.world.RayCast(agent.lidars[i], agent.lidars[i].p1, agent.lidars[i].p2);
+ }
+ }
+
+ /**
+ * Closes the environment.
+ */
+ close(){
+ this.world.SetContactListener(null);
+ this.contact_listener.Reset();
+ this._destroy();
+ }
+
+ // region Rendering
+ // ------------------------------------------ RENDERING ------------------------------------------
+
+ /**
+ * Renders the environment.
+ */
+ render() {
+ // Calls p5.js draw function once
+ redraw();
+ }
+
+ /**
+ * Sets the rendering viewer variables.
+ * @param width {number}
+ * @param height {number}
+ * @param keep_ratio {boolean}
+ */
+ _SET_RENDERING_VIEWPORT_SIZE(width, height=null, keep_ratio=true){
+ RENDERING_VIEWER_W = width;
+ if(keep_ratio || height == null){
+ RENDERING_VIEWER_H = Math.floor(RENDERING_VIEWER_W / (2 * VIEWPORT_W / VIEWPORT_H));
+ }
+ else{
+ RENDERING_VIEWER_H = height;
+ }
+ }
+ //endregion
+
+ //region Fixtures Initialization
+ // ------------------------------------------ FIXTURES INITIALIZATION ------------------------------------------
+
+ /**
+ * Creates all the Box2D fixtures to be used to generate the terrain.
+ */
+ create_terrain_fixtures(){
+
+ // Polygon fixture
+ this.fd_polygon = new b2.FixtureDef();
+ this.fd_polygon.shape = new b2.PolygonShape();
+ let vertices = [
+ new b2.Vec2(0, 0),
+ new b2.Vec2(1, 0),
+ new b2.Vec2(1, -1),
+ new b2.Vec2(0, -1)];
+ this.fd_polygon.shape.Set(vertices, 4);
+ this.fd_polygon.friction = FRICTION;
+ this.fd_polygon.filter.categoryBits = 0x1;
+ this.fd_polygon.filter.maskBits = 0xFFFF;
+
+ // Edge fixture
+ this.fd_edge = new b2.FixtureDef();
+ this.fd_edge.shape = new b2.EdgeShape();
+ this.fd_edge.shape.Set(new b2.Vec2(0, 0), new b2.Vec2(1, 1));
+ this.fd_edge.friction = FRICTION;
+ this.fd_edge.filter.categoryBits = 0x1;
+ this.fd_edge.filter.maskBits = 0xFFFF;
+
+ // Water fixture
+ this.fd_water = new b2.FixtureDef();
+ this.fd_water.shape = new b2.PolygonShape();
+ vertices = [
+ new b2.Vec2(0, 0),
+ new b2.Vec2(1, 0),
+ new b2.Vec2(1, -1),
+ new b2.Vec2(0, -1)];
+ this.fd_water.shape.Set(vertices, 4);
+ this.fd_water.density = WATER_DENSITY;
+ this.fd_water.isSensor = true;
+
+ // Creeper fixture
+ this.fd_creeper = new b2.FixtureDef();
+ this.fd_creeper.shape = new b2.PolygonShape();
+ vertices = [
+ new b2.Vec2(0, 0),
+ new b2.Vec2(1, 0),
+ new b2.Vec2(1, -1),
+ new b2.Vec2(0, -1)];
+ this.fd_creeper.shape.Set(vertices, 4);
+ this.fd_creeper.density = 5.0;
+ this.fd_creeper.isSensor = true;
+
+ // Circle fixture
+ this.fd_circle = new b2.FixtureDef();
+ this.fd_circle.shape = new b2.CircleShape();
+ this.fd_circle.density = 5.0;
+ this.fd_circle.friction = FRICTION;
+ this.fd_circle.filter.categoryBits = 0x1;
+ this.fd_circle.filter.maskBits = 0xFFFF;
+
+ }
+ //endregion
+
+ // region Game Generation
+ // ------------------------------------------ GAME GENERATION ------------------------------------------
+
+ /**
+ * Generates the different elements of the environment.
+ */
+ generate_game(){
+ this._generate_terrain();
+ this._generate_clouds();
+
+ for(let agent of this.agents){
+ this._generate_agent(agent);
+ }
+ }
+
+ /**
+ * Generates all the Box2D bodies composing the terrain.
+ */
+ _generate_terrain(){
+
+ // Arrays to contain the actual points of ground and ceiling
+ this.terrain_ground = [];
+ this.terrain_ceiling = [];
+
+ // Smooths ground and ceiling by removing points that are too close in order to reduce the number of bodies created
+ let ground = smoothTerrainFiler(this.ground, TERRAIN_STEP);
+ let ceiling = smoothTerrainFiler(this.ceiling, TERRAIN_STEP);
+
+ // Creates startpad
+ for(let i = 0; i < INITIAL_TERRAIN_STARTPAD; i++){
+ this.terrain_ground.push({x: i * TERRAIN_STEP, y: TERRAIN_HEIGHT});
+ this.terrain_ceiling.push({x: i * TERRAIN_STEP, y: TERRAIN_HEIGHT + this.ceiling_offset});
+ }
+
+ /* DRAWING GENERATION: generates the terrain from the ground and ceiling arrays of points */
+ if(window.is_drawing() || ground.length > 0 || ceiling.length > 0){
+
+ // Creates ground terrain
+ if(ground.length > 0){
+
+ // Handles smoothing and alignment of the ground with the startpad
+ let ground_y_offset = 0;
+ if(this.align_terrain.align && this.align_terrain.smoothing != null){
+
+ // Applies the smoothing as the ratio: current smoothing / previous smoothing
+ ground = [...ground.map(p => {
+ return {x: p.x, y: p.y / (this.TERRAIN_CPPN_SCALE / this.align_terrain.smoothing)};
+ })];
+
+ // Aligns the ground with the startpad
+ if(this.align_terrain.ground_offset == null){
+ ground_y_offset = TERRAIN_HEIGHT - ground[0].y;
+ }
+ // Keeps the same ground alignment (adjusted to fit the smoothing)
+ else{
+ ground_y_offset = this.align_terrain.ground_offset - ground[0].y;
+ }
+
+ }
+
+ for(let p of ground){
+ this.terrain_ground.push({x: p.x, y: p.y + ground_y_offset});
+ }
+ }
+
+ // Creates ceiling terrain
+ if(ceiling.length > 0) {
+
+ // Handles smoothing and alignment of the ceiling with the startpad
+ let ceiling_y_offset = 0
+ if(this.align_terrain.align && this.align_terrain.smoothing != null){
+
+ // Applies the smoothing as the ratio: current smoothing / previous smoothing
+ ceiling = [...ceiling.map(p => {
+ return {x: p.x, y: p.y / (this.TERRAIN_CPPN_SCALE / this.align_terrain.smoothing)};
+ })];
+
+ // Aligns the ceiling with the startpad
+ if(this.align_terrain.ceiling_offset == null){
+ ceiling_y_offset = TERRAIN_HEIGHT + this.ceiling_offset - ceiling[0].y;
+ }
+ // Keeps the same ceiling alignment (adjusted to fit the smoothing)
+ else{
+ ceiling_y_offset = (this.ceiling_offset - ceiling[0].y) - this.align_terrain.ceiling_offset;
+ }
+ }
+
+ for(let p of ceiling){
+ this.terrain_ceiling.push({x: p.x, y: p.y + ceiling_y_offset});
+ }
+ }
+ }
+ /* CPPN GENERATION: generates the terrain from the output of the CPPN model encoded with the input vector */
+ else{
+ let cppn_y = this.terrain_CPPN.generate(this.CPPN_input_vector).arraySync();
+ cppn_y = cppn_y.map(e => [e[0] / this.TERRAIN_CPPN_SCALE, e[1] / this.TERRAIN_CPPN_SCALE]);
+
+ // Gets y values for the ground and aligns them with the startpad
+ let ground_offset = TERRAIN_HEIGHT - cppn_y[0][0];
+ let cppn_ground_y = cppn_y.map(e => e[0] + ground_offset);
+
+ // Gets y values for the ceiling and aligns them with the startpad
+ let ceiling_offset = TERRAIN_HEIGHT + this.ceiling_offset - cppn_y[0][1];
+ let cppn_ceiling_y = cppn_y.map(e => e[1] + ceiling_offset);
+
+ // Pushes the terrain values in the lists
+ for(let i = 0; i < TERRAIN_LENGTH; i++){
+ this.terrain_ground.push({x: (INITIAL_TERRAIN_STARTPAD + i) * TERRAIN_STEP, y: cppn_ground_y[i]});
+
+ // Clips ceiling so that it does not overlaps the ground
+ let ceiling_val = cppn_ground_y[i] + this.ceiling_clip_offset;
+ if(cppn_ceiling_y[i] >= ceiling_val){
+ ceiling_val = cppn_ceiling_y[i];
+ }
+ this.terrain_ceiling.push({x: (INITIAL_TERRAIN_STARTPAD + i) * TERRAIN_STEP, y: ceiling_val});
+ }
+ }
+
+ // Stores the terrain shapes (without the startpad) in global variables
+ window.ground = [...this.terrain_ground];
+ window.ground.splice(0, INITIAL_TERRAIN_STARTPAD);
+ window.ceiling = [...this.terrain_ceiling];
+ window.ceiling.splice(0, INITIAL_TERRAIN_STARTPAD);
+
+ /* BOX2D TERRAIN CREATION */
+ this.terrain_bodies = [];
+ this.background_polys = [];
+ let poly;
+ let poly_data;
+
+ // Water
+ this.min_ground_y = Math.min(...this.terrain_ground.map(p => p.y));
+ this.air_max_distance = Math.max(...this.terrain_ceiling.map(p => p.y)) - this.min_ground_y;
+ this.water_y = this.min_ground_y + this.water_level * this.air_max_distance;
+
+ let water_poly = [
+ [this.terrain_ground[0].x - 1000, this.GROUND_LIMIT],
+ [this.terrain_ground[0].x - 1000, this.water_y],
+ [this.terrain_ground[this.terrain_ground.length - 1].x + 1000, this.water_y],
+ [this.terrain_ground[this.terrain_ground.length - 1].x + 1000, this.GROUND_LIMIT]
+ ];
+
+ this.fd_water.shape.Set([new b2.Vec2(water_poly[0][0], water_poly[0][1]),
+ new b2.Vec2(water_poly[1][0], water_poly[1][1]),
+ new b2.Vec2(water_poly[2][0], water_poly[2][1]),
+ new b2.Vec2(water_poly[3][0], water_poly[3][1])],
+ 4);
+ let body_def = new b2.BodyDef();
+ body_def.type = b2.Body.b2_staticBody;
+ let t = this.world.CreateBody(body_def);
+ t.CreateFixture(this.fd_water);
+ t.SetUserData(new CustomUserData("water", CustomUserDataObjectTypes.WATER));
+ let color = "#77ACE5"; // [0.465, 0.676, 0.898];
+ this.water_poly = {
+ type : "water",
+ color: color,
+ vertices: water_poly,
+ body : t
+ };
+
+ // Ground
+ for(let i = 0; i < this.terrain_ground.length - 1; i++){
+ poly = [
+ [this.terrain_ground[i].x, this.terrain_ground[i].y],
+ [this.terrain_ground[i + 1].x, this.terrain_ground[i + 1].y]
+ ];
+ this.fd_edge.shape.Set(new b2.Vec2(poly[0][0], poly[0][1]),
+ new b2.Vec2(poly[1][0], poly[1][1]));
+ let body_def = new b2.BodyDef();
+ body_def.type = b2.Body.b2_staticBody;
+ let t = this.world.CreateBody(body_def);
+ t.CreateFixture(this.fd_edge);
+ t.SetUserData(new CustomUserData("grass", CustomUserDataObjectTypes.TERRAIN));
+ let color = i % 2 == 0 ? "#4dff4d" : "#4dcc4d"; // [0.3, 1.0, 0.3] : [0.3, 0.8, 0.3]
+ poly_data = {
+ type : "ground",
+ color : color,
+ body : t,
+ }
+ this.terrain_bodies.push(poly_data);
+
+ // Visual poly to fill the ground
+ if(i <= this.terrain_ground.length / 2){
+ poly.push([poly[1][0] + 10 * TERRAIN_STEP, 2 * this.GROUND_LIMIT]);
+ poly.push([poly[0][0], 2 * this.GROUND_LIMIT]);
+ }
+ else{
+ poly.push([poly[1][0], 2 * this.GROUND_LIMIT]);
+ poly.push([poly[0][0] - 10 * TERRAIN_STEP, 2 * this.GROUND_LIMIT]);
+ }
+
+ color = "#66994D"; //[0.4, 0.6, 0.3];
+ poly_data = {
+ type : "ground",
+ color : color,
+ vertices : poly,
+ }
+ this.background_polys.push(poly_data);
+ }
+
+ // Ceiling
+ for(let i = 0; i < this.terrain_ceiling.length - 1; i++){
+ poly = [
+ [this.terrain_ceiling[i].x, this.terrain_ceiling[i].y],
+ [this.terrain_ceiling[i + 1].x, this.terrain_ceiling[i + 1].y]
+ ];
+ this.fd_edge.shape.Set(new b2.Vec2(poly[0][0], poly[0][1]),
+ new b2.Vec2(poly[1][0], poly[1][1]));
+ body_def = new b2.BodyDef();
+ body_def.type = b2.Body.b2_staticBody;
+ t = this.world.CreateBody(body_def);
+ t.CreateFixture(this.fd_edge);
+ t.SetUserData(new CustomUserData("rock", CustomUserDataObjectTypes.GRIP_TERRAIN)); // TODO: CustomUserData
+ color = "#004040"; // [0, 0.25, 0.25];
+ poly_data = {
+ type : "ceiling",
+ color : color,
+ body : t,
+ }
+ this.terrain_bodies.push(poly_data);
+
+ // Visual poly to fill the ceiling
+ if(i <= this.terrain_ceiling.length / 2){
+ poly.push([poly[1][0] + 10 * TERRAIN_STEP, 2 * this.CEILING_LIMIT]);
+ poly.push([poly[0][0], 2 * this.CEILING_LIMIT]);
+ }
+ else{
+ poly.push([poly[1][0], 2 * this.CEILING_LIMIT]);
+ poly.push([poly[0][0] - 10 * TERRAIN_STEP, 2 * this.CEILING_LIMIT]);
+ }
+ color = "#808080"; // [0.5, 0.5, 0.5];
+ poly_data = {
+ type : "ceiling",
+ color : color,
+ vertices : poly,
+ }
+ this.background_polys.push(poly_data);
+ }
+
+ // Creepers
+ if(this.creepers_width != null && this.creepers_height != null){
+ let creeper_width = Math.max(0.2, this.creepers_width);
+ let nb_creepers = Math.floor(this.terrain_ceiling[this.terrain_ceiling.length - 1].x / (this.creepers_spacing + creeper_width));
+
+ for(let i = 1; i < nb_creepers; i++){
+ let creeper_height = Math.max(0.2, Math.random() * (0.1 - (- 0.1)) + this.creepers_height - 0.1);
+ let creeper_x_init_pos = i * (this.creepers_spacing + creeper_width);
+ let creeper_y_init_pos = find_best_y(creeper_x_init_pos, this.terrain_ceiling);
+
+ // Breaks creepers in multiple dynamic bodies linked by joints
+ if(this.movable_creepers){
+
+ // Creates a static base to which the creeper is attached
+ this.fd_creeper.shape.SetAsBox(creeper_width/2, 0.2);
+ body_def = new b2.BodyDef();
+ body_def.type = b2.Body.b2_staticBody;
+ body_def.position.Set(creeper_x_init_pos, creeper_y_init_pos - 0.1);
+ t = this.world.CreateBody(body_def);
+ t.CreateFixture(this.fd_creeper);
+ t.SetUserData(new CustomUserData("creeper", CustomUserDataObjectTypes.SENSOR_GRIP_TERRAIN));
+ let previous_creeper_part = t;
+
+ // Cuts the creeper in unit parts
+ for(let w = 0; w < Math.ceil(creeper_height); w++){
+ let h;
+ // last iteration: rest of the creeper
+ if(w == Math.floor(creeper_height / CREEPER_UNIT)){
+ h = Math.max(0.2, creeper_height % CREEPER_UNIT);
+ }
+ else{
+ h = CREEPER_UNIT;
+ }
+
+ this.fd_creeper.shape.SetAsBox(creeper_width/2, h/2);
+ body_def = new b2.BodyDef();
+ body_def.type = b2.Body.b2_dynamicBody;
+ body_def.position.Set(creeper_x_init_pos, creeper_y_init_pos - (w * CREEPER_UNIT) - h/2);
+ t = this.world.CreateBody(body_def);
+ t.CreateFixture(this.fd_creeper);
+ t.SetUserData(new CustomUserData("creeper", CustomUserDataObjectTypes.SENSOR_GRIP_TERRAIN));
+ color = "#6F8060"; // [0.437, 0.504, 0.375];
+ poly_data = {
+ type : "creeper",
+ color1 : color,
+ color2 : color,
+ body : t,
+ }
+ this.terrain_bodies.push(poly_data);
+
+ let rjd_def = new b2.RevoluteJointDef();
+ let anchor = new b2.Vec2(creeper_x_init_pos, creeper_y_init_pos - (w * CREEPER_UNIT));
+ rjd_def.Initialize(previous_creeper_part, t, anchor);
+ rjd_def.enableMotor = false;
+ rjd_def.enableLimit = true;
+ rjd_def.lowerAngle = -0.4 * Math.PI;
+ rjd_def.upperAngle = 0.4 * Math.PI;
+ let joint = this.world.CreateJoint(rjd_def);
+ joint.SetUserData(new CustomMotorUserData("creeper", 6, false));
+ this.creepers_joints.push(joint);
+ previous_creeper_part = t;
+ }
+ }
+
+ // Creates only one static creeper body
+ else{
+ this.fd_creeper.shape.SetAsBox(creeper_width/2, creeper_height/2);
+ body_def = new b2.BodyDef();
+ body_def.type = b2.Body.b2_staticBody;
+ body_def.position.Set(creeper_x_init_pos, creeper_y_init_pos - creeper_height/2);
+ t = this.world.CreateBody(body_def);
+ t.CreateFixture(this.fd_creeper);
+ t.SetUserData(new CustomUserData("creeper", CustomUserDataObjectTypes.SENSOR_GRIP_TERRAIN));
+ color = "#6F8060"; // [0.437, 0.504, 0.375];
+ poly_data = {
+ type : "creeper",
+ color1 : color,
+ body : t,
+ }
+ this.terrain_bodies.push(poly_data);
+ }
+ }
+ }
+ }
+
+ /**
+ * Generates random clouds.
+ */
+ _generate_clouds(){
+ this.cloud_polys = [];
+ for(let i = 0; i < Math.ceil(TERRAIN_LENGTH/10); i++){
+ let x = (Math.random() * 5 * TERRAIN_LENGTH - TERRAIN_LENGTH) * TERRAIN_STEP;
+ let y = Math.random() * RENDERING_VIEWER_H/SCALE + RENDERING_VIEWER_H/SCALE * 1/5;
+ let poly = [];
+ for(let a = 0; a < 10; a++){
+ poly.push([
+ x + 15 * TERRAIN_STEP * Math.sin(Math.PI * 2 * a / 5) + Math.random() * (0 - 5 * TERRAIN_STEP) + 5 * TERRAIN_STEP,
+ y + 5 * TERRAIN_STEP * Math.cos(Math.PI * 2 * a / 5) + Math.random() * (0 - 5 * TERRAIN_STEP) + 5 * TERRAIN_STEP
+ ])
+ }
+ let x1 = Math.min(...poly.map(p => p[0]));
+ let x2 = Math.max(...poly.map(p => p[0]));
+ this.cloud_polys.push({poly: poly, x1: x1, x2: x2});
+ }
+ }
+
+ /**
+ * Generates the given agent by computing its initial position and creating its physics body.
+ * @param agent {Object}
+ * @param init_x {number}
+ * @param init_y {number}
+ */
+ _generate_agent(agent, init_x=null, init_y=null){
+
+ if(init_x == null){
+ // If an init_pos is given for the agent (reset due to terrain reshaping), init_y is computed accordingly
+ if(agent.init_pos != null){
+ init_x = agent.init_pos.x;
+
+ // Computes the best y position corresponding to init_x to always generate the walkers on the ground
+ if (agent.agent_body.body_type == BodyTypesEnum.WALKER) {
+ init_y = find_best_y(init_x, this.terrain_ground) + agent.agent_body.AGENT_CENTER_HEIGHT;
+ }
+
+ // Computes the best y position corresponding to init_x to always generate the swimmers between the ground and the ceiling
+ else if(agent.agent_body.body_type == BodyTypesEnum.SWIMMER){
+ let y_ground = find_best_y(init_x, this.terrain_ground, agent.agent_body.AGENT_WIDTH);
+ if(y_ground == null){
+ y_ground = -Infinity;
+ }
+ let y_ceiling = find_best_y(init_x, this.terrain_ceiling, agent.agent_body.AGENT_WIDTH);
+ if(y_ceiling == null){
+ y_ceiling = Infinity;
+ }
+ 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));
+ }
+ }
+
+ // If no init_pos is given (add_agent), the agent is generated on the startpad
+ else{
+ init_x = TERRAIN_STEP * INITIAL_TERRAIN_STARTPAD / 2;
+
+ // Sets init_y position according to the agent
+ init_y = TERRAIN_HEIGHT + agent.agent_body.AGENT_CENTER_HEIGHT;
+ if(agent.agent_body.body_type == BodyTypesEnum.SWIMMER){
+ init_y = TERRAIN_HEIGHT + 4 * agent.agent_body.AGENT_CENTER_HEIGHT;
+ }
+ }
+ }
+
+ // Creates the Box2D bodies of the agent's morphology
+ agent.agent_body.draw(this.world, init_x, init_y, 0);
+ agent.actions = Array.from({length: agent.agent_body.get_action_size()}, () => 0);
+
+ // If the agent is a climber, initializes its position
+ if(agent.agent_body.body_type == BodyTypesEnum.CLIMBER){
+ this.init_climber_pos(agent);
+ }
+ }
+
+ /**
+ * Sets the position of an agent.
+ * @param agent {Object}
+ * @param init_x {number}
+ * @param init_y {number}
+ */
+ set_agent_position(agent, init_x, init_y) {
+ agent.agent_body.destroy(this.world);
+
+ if (agent.agent_body.body_type == BodyTypesEnum.WALKER) {
+ // Computes the best y position corresponding to init_x to always generate the walkers on the ground
+ init_y = find_best_y(init_x, this.terrain_ground) + agent.agent_body.AGENT_CENTER_HEIGHT;
+ }
+
+ // Computes the best y position corresponding to init_x to always generate the swimmers between the ground and the ceiling
+ else if(agent.agent_body.body_type == BodyTypesEnum.SWIMMER){
+ let y_ground = find_best_y(init_x, this.terrain_ground, agent.agent_body.AGENT_WIDTH);
+ if(y_ground == null){
+ y_ground = -Infinity;
+ }
+ let y_ceiling = find_best_y(init_x, this.terrain_ceiling, agent.agent_body.AGENT_WIDTH);
+ if(y_ceiling == null){
+ y_ceiling = Infinity;
+ }
+ 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));
+ }
+
+ this._generate_agent(agent, init_x, init_y);
+ this.update_lidars(agent);
+ }
+
+ /**
+ * Sets the scroll to follow the given agent or to the given values.
+ * @param agent {Object}
+ * @param h {number} - Horizontal scroll
+ * @param v {number} - Vertical scroll
+ */
+ set_scroll(agent=null, h=null, v=null){
+ let terrain_length = Math.max(this.terrain_ground[this.terrain_ground.length - 1].x, this.terrain_ceiling[this.terrain_ceiling.length - 1].x);
+
+ // Sets the scroll to follow the agent
+ if(agent != null){
+ let x = agent.agent_body.reference_head_object.GetPosition().x;
+ let y = agent.agent_body.reference_head_object.GetPosition().y;
+
+ this.scroll = [
+ (x * this.scale - RENDERING_VIEWER_W/5) * this.zoom,
+ (y * this.scale - RENDERING_VIEWER_H * 2/5) * this.zoom
+ ];
+ }
+
+ // Adjusts the scroll when dragging an agent outside of the canvas
+ else if(window.is_dragging_agent){
+
+ if(window.dragging_side == "left"){
+ this.scroll[0] = window.agent_selected.agent_body.reference_head_object.GetPosition().x * this.scale * this.zoom - RENDERING_VIEWER_W * (0.1 + 0.05)
+ }
+ else if(window.dragging_side == "right"){
+ this.scroll[0] = window.agent_selected.agent_body.reference_head_object.GetPosition().x * this.scale * this.zoom - RENDERING_VIEWER_W * (0.85 + 0.05)
+ }
+
+ // Adjusts the vertical scroll to follow the vertical position of the agent
+ this.scroll[1] = (window.agent_selected.agent_body.reference_head_object.GetPosition().y * this.scale - RENDERING_VIEWER_H * 2/5) * this.zoom;
+ }
+
+ // Sets the scroll to the given values
+ else{
+ this.scroll = [h, v];
+ }
+
+ // Clamps scroll both horizontally and vertically when drawing
+ if(window.is_drawing()){
+ 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));
+ this.scroll[1] = Math.max(-SCROLL_Y_MAX, Math.min(this.scroll[1], SCROLL_Y_MAX));
+ }
+ else{
+ // Clamps scroll only horizontally when not drawing
+ this.scroll[0] = Math.max(INIT_SCROLL_X, Math.min(this.scroll[0], terrain_length * this.scale * this.zoom - RENDERING_VIEWER_W * 0.9));
+ }
+ }
+
+ /**
+ * Sets the zoom to the given value and clamps it in the authorized range.
+ * @param zoom
+ */
+ set_zoom(zoom){
+ this.zoom = Math.max(0.2, Math.min(parseFloat(zoom), 1.5));
+ }
+
+ /**
+ * Adds an agent to the environment, initializes it and runs one step.
+ * @param morphology {string}
+ * @param policy {{name: string, path: string}}
+ * @param pos {{x: number, y: number}}
+ */
+ add_agent(morphology, policy, pos){
+ this.create_agent(morphology, policy, pos);
+ if(pos != null){
+ this._generate_agent(this.agents[this.agents.length - 1], pos.x, pos.y);
+ }
+ else{
+ this._generate_agent(this.agents[this.agents.length - 1]);
+ }
+ this.init_agent(this.agents[this.agents.length - 1]);
+ let step_rets = this.step();
+ window.game.obs.push([...step_rets.map(e => e[0])]);
+ window.game.rewards.push([...step_rets.map(e => e[1])]);
+ }
+
+ /**
+ * Deletes the agent corresponding to the given index in the list of agents.
+ * @param agent_index {number}
+ */
+ delete_agent(agent_index){
+ if(this.agents.length > 0 && agent_index < this.agents.length){
+
+ // Removes the agent from the list and destroys its body
+ let agent = this.agents[agent_index];
+ this.agents.splice(agent_index, 1);
+ agent.agent_body.destroy(this.world);
+
+ // Adjusts the id of the other agents
+ for(let i = 0; i < this.agents.length; i++){
+ this.agents[i].id = i;
+ }
+
+ // Removes the observation of this agent from the list of observations.
+ window.game.obs[window.game.obs.length - 1].splice(agent_index, 1);
+ window.game.rewards[window.game.rewards.length - 1].splice(agent_index, 1);
+ }
+ }
+
+ /**
+ * Creates a circle body at the given position and with the given radius.
+ * @param pos {{x: number, y: number}}
+ * @param radius {number}
+ */
+ create_circle_asset(pos, radius){
+ // Computes the best y position corresponding to pos.x to always generate the assets between the ground and the ceiling
+ let y_ground = find_best_y(pos.x, this.terrain_ground, radius);
+ if(y_ground == null){
+ y_ground = -Infinity;
+ }
+ let y_ceiling = find_best_y(pos.x, this.terrain_ceiling, radius);
+ if(y_ceiling == null){
+ y_ceiling = Infinity;
+ }
+ let y = Math.max(y_ground + radius, Math.min(y_ceiling - radius, pos.y));
+
+ // Box2D creation
+ this.fd_circle.shape.m_radius = radius;
+ let body_def = new b2.BodyDef();
+ body_def.type = b2.Body.b2_dynamicBody;
+ body_def.position.Assign(new b2.Vec2(pos.x, y));
+ let t = this.world.CreateBody(body_def);
+ t.CreateFixture(this.fd_circle);
+ t.SetUserData(new CustomUserData("circle", CustomUserDataObjectTypes.TERRAIN));
+ let poly_data = {
+ type : "circle",
+ color1 : "#885C00", // [136, 92, 0];
+ color2 : "#5F3D0E", // [95, 61, 14];
+ body : t,
+ is_selected: false
+ }
+ this.assets_bodies.push(poly_data);
+ }
+
+ /**
+ * Sets the position of the given asset to the given position.
+ * @param asset {Object}
+ * @param pos {{x: number, y: number}}
+ */
+ set_asset_position(asset, pos){
+ let shape = asset.body.GetFixtureList().GetShape();
+ let radius;
+ // Only supports circle assets for now
+ if(shape.m_type == b2.Shape.e_circle){
+ radius = shape.m_radius;
+ }
+
+ // Computes the best y position corresponding to pos.x to always generate the assets between the ground and the ceiling
+ let y_ground = find_best_y(pos.x, this.terrain_ground, radius);
+ if(y_ground == null){
+ y_ground = -Infinity;
+ }
+ let y_ceiling = find_best_y(pos.x, this.terrain_ceiling, radius);
+ if(y_ceiling == null){
+ y_ceiling = Infinity;
+ }
+ let y = Math.max(y_ground + radius, Math.min(y_ceiling - radius, pos.y));
+ asset.body.SetTransform(new b2.Vec2(pos.x, y),
+ asset.body.GetAngle());
+ asset.body.SetLinearVelocity(new b2.Vec2(0, -0.1));
+ }
+
+ /**
+ * Deletes the given asset.
+ * @param asset {Object}
+ */
+ delete_asset(asset){
+ if(this.assets_bodies.length > 0){
+ let index = this.assets_bodies.indexOf(asset);
+ if(index != -1){
+ // Removes the asset from the list and destroys its body
+ this.assets_bodies.splice(index, 1);
+ this.world.DestroyBody(asset.body);
+ }
+ }
+ }
+ //endregion
+}
+
+/**
+ * Finds the best y value corresponding to the given x value according to the points in the given array.
+ * @param x {number}
+ * @param array {Array}
+ * @param [max_dist=null] {number} - Maximum horizontal distance to consider if no good y position has been found
+ * @return {number}
+ */
+function find_best_y(x, array, max_dist=null){
+ // Finds the closest point to x in array according to the x-coordinate
+ let p1 = array.reduce(function(prev, curr) {
+ return (Math.abs(curr.x - x) < Math.abs(prev.x - x) ? curr : prev);
+ });
+
+ // Gets the index of p1
+ let i1 = array.indexOf(p1);
+
+ // Gets p2 so that x in [p1.x, p2.x] or x in [p2.x, p1.x]
+ let p2;
+
+ // Case x > p1.x --> x in [p1.x, p2.x]
+ if(x > p1.x){
+ if(i1 < array.length - 1){
+ p2 = array[i1 + 1];
+ }
+ else{
+ p2 = p1;
+ }
+ }
+ // Case x <= p1.x --> x in [p2.x, p1.x]
+ else{
+ if(i1 > 0){
+ p2 = array[i1 - 1];
+ }
+ else{
+ p2 = p1;
+ }
+ }
+
+ let y = p1.y;
+ // Computes the equation of the line between p1 and p2 and finds y corresponding to x
+ if(p1.x != p2.x){
+ let a = (p2.y - p1.y) / (p2.x - p1.x);
+ let b = p1.y - a * p1.x;
+ y = a * x + b;
+ }
+
+ // 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
+ else if(max_dist != null && Math.abs(x - p1.x) > max_dist){
+ y = null;
+ }
+ return y;
+}
+
+/**
+ * Smoothes the terrain by filtering points that are horizontally too close according to the given epsilon value.
+ * @param terrain {Array} - Array of points
+ * @param epsilon {number} - Minimal horizontal distance between two points
+ * @return {Array} - Filtered array of points
+ */
+function smoothTerrainFiler(terrain, epsilon){
+ let smooth_terrain = [];
+ if(terrain.length > 0){
+ smooth_terrain.push(terrain[0]);
+ let n = 0;
+ for(let i = 1; i < terrain.length - 1; i++){
+ if(terrain[i].x >= smooth_terrain[n].x + epsilon){
+ smooth_terrain.push(terrain[i]);
+ n += 1;
+ }
+ }
+ smooth_terrain.push(terrain[terrain.length - 1]);
+ }
+ return smooth_terrain;
+}
\ No newline at end of file
diff --git a/js/game.js b/js/game.js
new file mode 100644
index 0000000000000000000000000000000000000000..6f7650e9c70cb7c6a05a936d5e09923f985266e1
--- /dev/null
+++ b/js/game.js
@@ -0,0 +1,165 @@
+/**
+ * @classdesc Class that handles the simulation.
+ */
+class Game {
+ /**
+ * @constructor
+ * @param agents {{morphologies: Array, policies: Array, positions: Array}} - Morphologies, policies and positions of the agents
+ * @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_terrain {Object}
+ */
+ constructor(agents, cppn_input_vector, water_level, creepers_width, creepers_height,
+ creepers_spacing, smoothing, creepers_type, ground, ceiling, align_terrain) {
+
+ this.run_fps = 60;
+ this.obs = [];
+ this.rewards = [];
+ this.initWorld(agents, cppn_input_vector, water_level, creepers_width, creepers_height, creepers_spacing, smoothing,
+ creepers_type, ground, ceiling, align_terrain);
+ this.running = false;
+ }
+
+ /**
+ * Initializes the environment.
+ * @param agents {{morphologies: Array, policies: Array, positions: Array}} - Morphologies, policies and positions of the agents
+ * @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_terrain {Object}
+ */
+ initWorld(agents, cppn_input_vector, water_level, creepers_width, creepers_height, creepers_spacing,
+ smoothing, creepers_type, ground, ceiling, align_terrain) {
+
+ this.env = new MultiAgentsContinuousParkour(
+ agents,
+ 3,
+ smoothing,
+ 200,
+ 90,
+ 20,
+ creepers_type,
+ ground,
+ ceiling,
+ align_terrain);
+
+ this.env.set_environment(cppn_input_vector, water_level, creepers_width, creepers_height, creepers_spacing, smoothing, creepers_type);
+ let step_rets = this.env.reset();
+ this.obs.push([...step_rets.map(e => e[0])]);
+ this.rewards.push([...step_rets.map(e => e[1])]);
+ }
+
+ /**
+ * Pauses the simulation.
+ */
+ pause(){
+ clearInterval(this.runtime);
+ this.running = false;
+ }
+
+ /**
+ * Runs the simulation.
+ * @returns {Promise}
+ */
+ async run(){
+
+ // Loads the policy for each agent before launching the simulation
+ for(let agent of window.game.env.agents){
+ if(agent.policy.path != null){
+ agent.model = await tf.loadGraphModel(agent.policy.path + '/model.json');
+ }
+ else{
+ agent.model = null;
+ }
+ }
+
+ // Creates a repeated interval over time
+ this.runtime = setInterval(() => {
+ this.play();
+ }, 1000 / this.run_fps);
+ this.running = true;
+ }
+
+ /**
+ *
+ * @param agents {{morphologies: Array, policies: Array, positions: Array}} - Morphologies, policies and positions of the agents
+ * @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_terrain {Object}
+ */
+ reset(agents, cppn_input_vector, water_level, creepers_width, creepers_height,
+ creepers_spacing, smoothing, creepers_type, ground, ceiling, align_terrain){
+
+ this.pause();
+ let zoom = window.game.env.zoom;
+ let scroll = [...window.game.env.scroll];
+ this.initWorld(agents, cppn_input_vector, water_level, creepers_width, creepers_height, creepers_spacing, smoothing,
+ creepers_type, ground, ceiling, align_terrain);
+
+ // Keeps the previous zoom and scroll
+ window.game.env.set_zoom(zoom);
+ window.game.env.set_scroll(null, scroll[0], scroll[1]);
+ this.env.render();
+ }
+
+ /**
+ * Plays one simulation step.
+ */
+ play() {
+
+ // Gets the actions to execute for each agent
+ for(let agent of window.game.env.agents){
+ let state = this.obs[this.obs.length - 1][agent.id];
+
+ // Generates the actions thanks to the agent's policy model
+ if(agent.model != null){
+ let envState = tf.tensor(state,[1, state.length]);
+
+ let inputs = {
+ "Placeholder_1:0": envState
+ };
+
+ let output = 'main/mul:0'
+
+ agent.actions = agent.model.execute(inputs, output).arraySync()[0];
+ }
+
+ // Generates random actions
+ else /*if(agent.policy.name == "random")*/{
+ agent.actions = Array.from({length: agent.agent_body.get_action_size()}, () => Math.random() * 2 - 1);
+ }
+
+ // Generates motionless actions
+ /*else{
+ agent.actions = Array.from({length: agent.agent_body.get_action_size()}, () => 0);
+ }*/
+ }
+
+ // Runs one step and stores the resulted states for each agent
+ let step_rets = this.env.step();
+ this.obs.push([...step_rets.map(e => e[0])]);
+ this.rewards.push([...step_rets.map(e => e[1])]);
+
+ this.env.render();
+ }
+}
diff --git a/js/i18n.js b/js/i18n.js
new file mode 100644
index 0000000000000000000000000000000000000000..197275820111fdd73bb00d1cd385a0753acdb254
--- /dev/null
+++ b/js/i18n.js
@@ -0,0 +1,599 @@
+// Internationalization dictionary
+window.lang_dict = {
+
+ // English
+ 'EN': {
+ 'introTour': {
+ 'nextLabel': "Next",
+ 'prevLabel': "Back",
+ 'doneLabel': "Done",
+ 'welcomeTitle': "Welcome!",
+ 'welcomeText': "Here you can play with a simulation where autonomously trained agents are trying to navigate through a 2D environment.",
+ 'viewportTitle': "Viewport simulation",
+ '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.
You can also interact with the simulation using the mouse in order to scroll, zoom or even drag and drop the agents.
Try it now! ",
+ 'runTitle': "Run the simulation",
+ 'runText': 'Click the button to run the simulation. Then, click the button to pause it or the to reset it.
Try it now!',
+ 'baseEnvsTitle': "Some environments",
+ 'baseEnvsText': "Here are some basic environments that will let you become more familiar with the different morphologies of agents. You will be able to load them into the simulation to visualize the behaviour of the different agents.",
+ 'morphologiesTitle': "Agents morphologies",
+ '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.
Each agent has been trained to learn an optimal behaviour to navigate through the environment according to its morphology. Try to compare them!",
+ 'agentsListTitle': "List of running agents",
+ 'agentsListText': "In this section you can find all the agents that are currently running in the simulation.",
+ 'customEnvsTitle': "Custom environments",
+ '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!",
+ 'furtherTitle': "Going further...",
+ 'furtherText': "If you want to customize the environment, access more advanced options or learn more about the context of this demo, open these tabs.
You can scroll horizontally and vertically in the environment by dragging the mouse.
+
You can zoom in or out using the mouse wheel.
+
You can select an agent or an asset by clicking on it, and then delete it by pressing the delete key of your keyboard.
+
You can drag and drop an agent or an asset using the mouse.
+
You can change the eraser and assets radius using the mouse wheel.
+
+ `
+ },
+
+ 'agentsList': {
+ 'title': "List of running agents",
+ 'follow': "Follow",
+ 'followTooltip' : "Center the viewport on the agent",
+ 'savePosTooltip': "Save the agent's position",
+ 'resetPosTooltip': "Reset the agent's position",
+ 'deleteAgentTooltip': "Delete the agent",
+ },
+
+ 'mainButtons':{
+ 'runBtnTooltip': "Run the simulation",
+ 'pauseBtnTooltip': "Pause the simulation",
+ 'resetBtnTooltip': "Reset the simulation",
+ 'saveBtnTooltip': "Save the current environment"
+ },
+
+ 'drawingMode': {
+ 'intro': 'Here you can draw your own parkour!',
+ 'text': `Select the Ground or Ceiling button to start drawing the corresponding terrain shape with the mouse.
+ Be careful not to draw more than one line at different heights if you want the result to be optimal.
+ You can use the Erase button if you need to correct your drawing or the Clear one to clear all your drawing.
+ When you are satisfied with the result, just click the Generate Terrain button.`,
+ 'ground': "Ground",
+ 'ceiling': "Ceiling",
+ 'erase': "Erase",
+ 'clear': "Clear",
+ 'generateTerrain': "Generate Terrain",
+ 'draw': "Draw",
+ },
+
+ 'parkourConfig': {
+ 'terrainGeneration': "Terrain Generation",
+ 'generalParameters': "General Parameters",
+ 'creepers': "Creepers",
+ 'drawTabBtn': "Draw Yourself!",
+ 'procGenTabBtn': "Procedural Generation",
+ 'procGenText': `You can also use these three sliders to generate the terrain shapes automatically.`,
+ 'smoothing': "Smoothing",
+ 'waterLevel': "Water level",
+ 'creepersWidth': "Width",
+ 'creepersHeight': "Height",
+ 'creepersSpacing': "Spacing",
+ //'creepersType': "Type",
+ 'rigid': "Rigid",
+ 'swingable': "Swingable",
+ },
+
+ 'morphologies': {
+ 'title': "Add an agent",
+ 'text': "Here you can add an agent to the simulation with the morphology of your choice.",
+ 'policySelectTooltip': "Select an agent",
+ 'addBtnTooltip': "Add the agent to the simulation",
+ 'baby': "baby",
+ 'teenager': "teenager",
+ 'adult': "adult",
+ 'bipedal': {
+ 'title': "Bipedal",
+ 'description': "This morphology is composed of a head and two legs which allow it to walk on the floor."
+ },
+ 'spider': {
+ 'title': "Spider",
+ 'description': "This morphology is composed of a body and four legs which allow it to walk on the floor."
+ },
+ 'chimpanzee': {
+ 'title': "Chimpanzee",
+ '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.",
+ },
+ 'fish': {
+ 'title': "Fish",
+ 'description': "This morphology is composed of a head, a tail and a fin, allowing it to swim in the water.",
+ },
+ },
+
+ 'envsSets': {
+ 'baseSetText': `To begin you can select one of the following environments to load it into the simulation, and then run it by clicking the button.`,
+ 'customSetText': `In this section you can store your own custom environments by saving them thanks to the button above or by uploading them from a JSON file.`,
+ 'uploadCard': {
+ 'title': "Upload an environment",
+ 'text': `Choose a JSON file then click the button below to save the corresponding environment in your collection.`,
+ 'uploadBtnTooltip': "Upload the environment from the selected file",
+ },
+ 'downloadBtnTooltip': "Download the environment",
+ 'deleteBtnTooltip': "Delete the environment",
+ },
+
+ 'advancedOptions': {
+ 'renderingOptions': ` Rendering Options `,
+ 'drawJoints': "Draw joints",
+ 'drawJointsTooltip': "Joints are what bind the different bodies together. They also act as motors for the agents morphologies.",
+ 'drawLidars': "Draw lidars",
+ 'drawLidarsTooltip': "Lidars are sensors which enable agents to detect nearby obstacles and water.",
+ 'drawNames': "Draw names",
+ 'drawNamesTooltip': "Name of the agents.",
+ 'drawObservation': "Draw observations",
+ 'drawObservationTooltip': "The observation is a partial view of the environment state at each step on which the agents rely to take an action.",
+ 'drawReward': "Draw rewards",
+ 'drawRewardTooltip': "The reward is a value that indicates how good or bad the current state is, according to the objective of the agent.",
+ 'stepReward': "Step reward",
+ 'totalReward': "Cumulative reward",
+ 'assetsTitle': ` Assets `,
+ 'assetsText': "Here you can find several types of assets, which are objects that you can add to the simulation using the mouse.",
+ 'circle': ` Ball`,
+ 'comingSoon': "More assets coming soon...",
+ },
+
+ 'globalElements': {
+ 'demoTitle': "Interactive Deep Reinforcement Learning Demo",
+ 'gettingStarted': "Getting Started",
+ 'parkourCustomization': "Parkour Customization",
+ 'advancedOptions': "Advanced Options",
+ 'about': "About...",
+ 'saveEnvModal': {
+ 'title': `Please enter a name and a description for the current environment.`,
+ '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.",
+ 'nameLabel': "Name",
+ 'descriptionLabel': "Description",
+ 'cancelBtn': "Cancel",
+ 'confirmBtn': "Save",
+ },
+ },
+
+ 'aboutTab': {
+ 'purposeTitle': "Purpose of the demo",
+ 'purposeText': `
+ The goal of this demo is to showcase the challenge of generalization to unknown tasks
+ for Deep Reinforcement Learning (DRL) agents.
+
+
+
+ DRL is a Machine Learning approach for teaching virtual agents
+ how to solve tasks by combining Reinforcement Learning and Deep Learning methods.
+ This approach has been used for a diverse set of applications including robotics (e.g. Solving Rubik's Cube[1]),
+ video games and boardgames (e.g. AlphaGo[2]).
+
+
+
+ In this demo, all the agents have been autonomously trained to learn an efficient behaviour to navigate through a 2D environment,
+ combining a DRL algorithm and a teacher algorithm (see below) so that they can be able to generalize their behaviour to never-seen-before situations.
+
+
+
+ The demo provides different tools to customize the environment in order to test and challenge the
+ robustness of the agents on different situations.
+
+ Reinforcement Learning (RL) is the study of agents and how they learn by trial and error.
+ The main idea is to reward or punish an agent according to the actions it takes in order to make it learn an efficient behavior to reach an objective.
+
+ The RL approaches generally feature an agent which evolves and interacts with a world.
+ At each interaction step, the agent sees a partial observation of the current state of the environment and decides of an action to take.
+ Each action taken by the agent changes the state of the world.
+ The agent also receives a reward signal at each step, that indicates how good or bad the current state is
+ according to the objective the agent has to reach.
+
+
+
+
+
+ The diagram on the right presents this interaction process between the agent and the environment,
+ with the different information they exchange at each step.
+
+ Maximizing the reward over steps is a way for the agent to learn a behaviour, also called policy,
+ to achieve its objective.
+
+
+
+
+
+
`,
+ 'drlTitle': "Deep RL",
+ 'drlText': `
+ In order to remember and improve the actions taken by the agent, DRL algorithms utilize artificial neural networks.
+ With training, these neural networks are able to learn to predict an optimal action to take at each step from the observation received,
+ thanks to all the observations and rewards previously received after each action during training.
+ Thanks to this, DRL algorithms are able to produce behaviours that are very effective in situations similar to those they were trained on.
+
+
+
+
+
+
+
+ However, in real-world applications, the environment rarely remains still and frequently evolves. Therefore one would
+ want DRL agents to be able to generalize their behaviour to previously unseen changes of the environment so that
+ they can adapt to a large range of situations.
+
+ One solution to handle this challenge is to train DRL agents on procedurally generated environments.
+
+ Procedural generation is a method of automatically creating environments according to some parameters.
+ Using this method, DRL agents can be trained on a very wide range of environments, hence allowing them
+ to generalize their behaviour to more different situations.
+
+
+
+ However, randomly generating environments during training implies the risk to generate environments that are too difficult or too easy to resolve
+ for the agents, preventing them to continuously learn in an efficient way.
+
+ Therefore, one would need smarter training strategies that propose relevant environments tailored to the current learning progress of the student (DRL agent).
+ This method is called Automatic Curriculum Learning (ACL)[3] and is embodied by a teacher algorithm which is trained to learn to generate
+ the most relevant environments throughout the entire training process according to the student performances.
+
+ This way, the teacher proposes easy environments to the student at the beginning and gradually increases the difficulty
+ and the diversity of the tasks in order to guarantee that the student is progressing while not always facing the same situation or forgetting what it has already learned.
+
`,
+ 'aboutDemoTitle': "About the demo",
+ 'aboutDemoText': `
+
+ In this demo, all the available agents were trained using Soft Actor Critic[4]
+ as the DRL student algorithm alongside different ACL teacher algorithms such as ALP-GMM[5].
+
+ They successfully learned efficient behaviours to move through the environment and to generalize to never-seen-before situations.
+
+
+
+ The physics of the simulation are handled by the box2d.js physics engine
+ which is a direct port to JavaScript of the Box2D physics engine.
+
+ The pre-trained policies (agents behaviours) are loaded in the browser thanks to TensorFlow.js.
+
+ This demo was designed by Paul Germon as part of an internship within Flowers
+ research team at Inria. This internship was monitored by Rémy Portelas and Clément Romac,
+ and supervised by Pierre-Yves Oudeyer. Special thanks to Nikita Melkozerov for its very helpful contribution.
+ Recommended citation format:
+
+ @misc{germon2021demo,
+ title={Interactive Deep Reinforcement Learning Demo},
+ author={Germon, Paul and Romac, Clément and Portelas, Rémy and Pierre-Yves, Oudeyer},
+ url={https://developmentalsystems.org/Interactive_DeepRL_Demo/},
+ year={2021}
+ }
+
+
+
+
+
The code of this demo is open-source and can be found on this github repository.
+
The code of the environment and agents is adapted from the TeachMyAgent[6] benchmark's Python code to JavaScript.
[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:
+ Solving Rubik's Cube with a Robot Hand (2019). https://arxiv.org/abs/1910.07113
+
[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). https://doi.org/10.1038/nature16961
+
[3] Portelas, R., Colas, C., Weng, L., Hofmann, K., & Oudeyer, P. Y. (2020). Automatic curriculum learning for deep rl: A short survey (2020). https://arxiv.org/abs/2003.04664
+
[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. In International conference on machine learning (pp. 1861-1870). PMLR https://arxiv.org/abs/1801.01290
+
[5] Portelas, R., Colas, C., Hofmann, K., & Oudeyer, P. Y. (2020, May). Teacher algorithms for curriculum learning of deep rl in continuously parameterized environments. In Conference on Robot Learning (pp. 835-853). PMLR. https://arxiv.org/abs/1910.07224
+
[6] Romac, C., Portelas, R., Hofmann, K., & Oudeyer, P.-Y. (2021). TeachMyAgent: A Benchmark for Automatic Curriculum Learning in Deep RL. International Conference on Machine Learning, 9052–9063. https://arxiv.org/abs/2103.09815
+
`,
+
+ }
+ },
+
+ // French
+ 'FR': {
+ 'introTour': {
+ 'nextLabel': "Suivant",
+ 'prevLabel': "Précédent",
+ 'doneLabel': "Fermer",
+ 'welcomeTitle': "Bienvenue !",
+ '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.",
+ 'viewportTitle': "Fenêtre d'affichage",
+ '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.
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.
Tu peux essayer dès maintenant !",
+ 'runTitle': "Lancer la simulation",
+ 'runText': 'Clique sur le bouton pour lancer la simulation. Tu peux ensuite cliquer sur le bouton pour la mettre en pause ou sur le bouton pour la réinitialiser.
Tu peux essayer dès maintenant !',
+ 'baseEnvsTitle': "Quelques environnements",
+ 'baseEnvsText': "Voici quelques environnements de base qui t'aideront à te familiariser avec les différentes morphologies d'agents. Tu pourras les charger dans la simulation pour voir les comportements des différents agents.",
+ 'morphologiesTitle': "Les différentes morphologies d'agents",
+ '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.
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 !",
+ 'agentsListTitle': "List des agents actifs",
+ 'agentsListText': "Dans cette section sont affichés tous les agents actuellement présents dans la simulation.",
+ 'customEnvsTitle': "Environnements personnalisés",
+ '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 !",
+ 'furtherTitle': "Pour aller plus loin...",
+ '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.
Tu peux faire défiler l'environnement horizontalement et verticalement à l'aide de la souris.
+
Tu peux zoomer ou dézoomer avec la molette de la souris.
+
Tu peux sélectionner un agent ou un objet en cliquant dessus, puis le supprimer en appuyant sur la touche suppr de ton clavier.
+
Tu peux déplacer un agent ou un objet en le faisant glisser avec la souris.
+
Tu peux changer le rayon de la gomme ou la taille des objets avec la molette de la souris.
+
+ `
+ },
+
+ 'agentsList': {
+ 'title': "Liste des agents actifs",
+ 'follow': "Suivre",
+ 'followTooltip' : "Centrer la fenêtre d'affichage sur l'agent",
+ 'savePosTooltip': "Sauvegarder la position de l'agent",
+ 'resetPosTooltip': "Réinitiliaser la position de l'agent",
+ 'deleteAgentTooltip': "Supprimer l'agent",
+ },
+
+ 'mainButtons':{
+ 'runBtnTooltip': "Lancer la simulation",
+ 'pauseBtnTooltip': "Mettre la simulation en pause",
+ 'resetBtnTooltip': "Réinitialiser la simulation",
+ 'saveBtnTooltip': "Sauvegarder l'environnement actuel"
+ },
+
+ 'drawingMode': {
+ 'intro': 'Ici tu peux dessiner ton propre parkour !',
+ 'text': `Selectionne les boutons Sol ou Plafond pour commencer à dessiner l'élément du terrain correspondant avec la souris.
+ Fais attention à ne pas superposer plusieurs traits à différentes hauteurs pour obtenir un résultat optimal.
+ Tu peux utiliser le bouton Gommer pour corriger ton dessin ou le bouton Effacer pour tout effacer.
+ Une fois que tu es satisfait du résultat, clique sur le bouton Générer le terrain.`,
+ 'ground': "Sol",
+ 'ceiling': "Plafond",
+ 'erase': "Gommer",
+ 'clear': "Effacer",
+ 'generateTerrain': "Générer le terrain",
+ 'draw': "Dessiner",
+ },
+
+ 'parkourConfig': {
+ 'terrainGeneration': "Génération du terrain",
+ 'generalParameters': "Paramètres Généraux",
+ 'creepers': "Lianes",
+ 'drawTabBtn': "Dessine par toi-même !",
+ 'procGenTabBtn': "Génération procédurale",
+ 'procGenText': `Tu peux aussi utiliser ces trois curseurs pour générer les formes du terrain de manière automatique.`,
+ 'smoothing': "Lissage",
+ 'waterLevel': "Niveau d'eau",
+ 'creepersWidth': "Largeur",
+ 'creepersHeight': "Hauteur",
+ 'creepersSpacing': "Espacement",
+ //'creepersType': "Type",
+ 'rigid': "Rigides",
+ 'swingable': "Flexibles",
+ },
+
+ 'morphologies': {
+ 'title': "Ajouter un agent",
+ 'text': "Ici tu peux ajouter un agent à la simulation avec la morphologie de ton choix.",
+ 'policySelectTooltip': "Sélectionner un agent",
+ 'addBtnTooltip': "Ajouter l'agent à la simulation",
+ 'baby': "bébé",
+ 'teenager': "adolescent",
+ 'adult': "adulte",
+ 'bipedal': {
+ 'title': "Bipède",
+ 'description': "Cette morphologie est composée d'une tête et de deux jambes qui lui permettent de marcher sur le sol."
+ },
+ 'spider': {
+ 'title': "Araignée",
+ 'description': "Cette morphologie est composée d'un corps et de quatre pattes qui lui permettent de marcher sur le sol."
+ },
+ 'chimpanzee': {
+ 'title': "Chimpanzé",
+ '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.",
+ },
+ 'fish': {
+ 'title': "Poisson",
+ '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.",
+ },
+ },
+
+ 'envsSets': {
+ '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 .`,
+ 'customSetText': `Dans cette section tu peux stocker tes propres environnements personnalisés en les sauvegardant grâce au bouton ci-dessus ou en les important depuis un fichier JSON.`,
+ 'uploadCard': {
+ 'title': "Importer un environnement",
+ 'text': `Choisis un fichier JSON puis clique sur le bouton ci-dessous pour importer l'environnement correspondant dans ta collection.`,
+ 'uploadBtnTooltip': "Importer l'environnement depuis le fichier sélectionné",
+ },
+ 'downloadBtnTooltip': "Télécharger l'environnement",
+ 'deleteBtnTooltip': "Supprimer l'environnement",
+ },
+
+ 'advancedOptions': {
+ 'renderingOptions': ` Options d'affichage `,
+ 'drawJoints': "Afficher les joints",
+ 'drawJointsTooltip': "Les joints sont les liens qui attachent les différents objets physiques ensemble. Ils servent aussi de moteurs pour les morphologies des agents.",
+ 'drawLidars': "Afficher les lidars",
+ 'drawLidarsTooltip': "Les lidars sont des capteurs qui permettent aux agents de détecter les obstacles et l'eau à proximité.",
+ 'drawNames': "Afficher les noms",
+ 'drawNamesTooltip': "Nom des agents.",
+ 'drawObservation': "Afficher les observations",
+ '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.",
+ 'drawReward': "Afficher les récompenses",
+ '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.",
+ 'stepReward': "Récompense courante",
+ 'totalReward': "Récompense cumulée",
+ 'assetsTitle': ` Objets `,
+ 'assetsText': "Ici tu peux trouver plusieurs types d'objets que tu peux ajouter à la simulation avec la souris.",
+ 'circle': ` Boule`,
+ 'comingSoon': "Plus d'objets à venir...",
+ },
+
+ 'globalElements': {
+ 'demoTitle': "Démo Interactive de Deep Reinforcement Learning",
+ 'gettingStarted': "Commencer",
+ 'parkourCustomization': "Personnalisation du parkour",
+ 'advancedOptions': "Options avancées",
+ 'about': "À propos...",
+ 'saveEnvModal': {
+ 'title': `Saisis un nom et une description pour l'environnement actuel`,
+ '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.",
+ 'nameLabel': "Nom",
+ 'descriptionLabel': "Description",
+ 'cancelBtn': "Annuler",
+ 'confirmBtn': "Sauvegarder",
+ },
+ },
+
+ 'aboutTab': {
+ 'purposeTitle': "Objectif de la démo",
+ 'purposeText': `
+ Le but de cette démonstration est de mettre en évidence le challenge de généralisation
+ à des nouvelles tâches pour des agents entraînés par Apprentissage par Renforcement Profond (ARP).
+
+
+
+ L'ARP est une approche d'Apprentissage automatique (Machine Learning)
+ qui permet d'apprendre à des agents virtuels à résoudre des tâches en combinant des méthodes
+ d'Apprentissage par renforcement (Reinforcement Learning) et d'Apprentissage profond (Deep Learning).
+ Cette approche a été utilisée pour diverses applications dont notamment la robotique (e.g. Solving Rubik's Cube) [1],
+ les jeux vidéo et les jeux de plateau (e.g. AlphaGo) [2].
+
+
+
+ Dans cette démo, tous les agents ont été entraînés de manière autonome dans le but d'apprendre un
+ comportement efficace pour traverser un environnement 2D.
+ Ils ont été entraînés en combinant un algorithme d'ARP et un algorithme enseignant (voir ci-dessous) pour qu'ils soient capables
+ de généraliser leur comportement à des situations inédites.
+
+
+
+ La démo fournit différents outils pour personnaliser l'environnement dans le but de tester et mettre la
+ robustesse des agents à l'épreuve lorsqu'ils font face à différentes situations.
+
`,
+ 'rlTitle': "Apprentissage par Renforcement",
+ 'rlText': `
+ L'Apprentissage par renforcement (AR) est l'étude des agents virtuels et de leur apprentissage par essais et erreurs.
+ L'idée principale est de récompenser ou punir un agent en fonction des actions qu'il effectue pour qu'il apprenne un comportement efficace pour atteindre un objectif.
+
+ Les approches d'AR sont généralement composées d'un agent qui évolue dans un monde ou environnement et interagit avec.
+ À chaque pas de temps, l'agent voit une observation partielle de l'état actuel de l'environnement et décide d'effectuer une action.
+ Chaque action effectuée par l'agent change l'état de l'environnement. L'agent reçoit également une récompense
+ à 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.
+
+
+
+
+
+ Le schéma ci-contre présente ce processus d'interaction entre l'agent et l'environment,
+ avec les différentes informations qu'ils échangent à chaque pas de temps.
+
+ Maximiser la récompense au cours du temps est un moyen pour l'agent d'apprendre un comportement optimal (ou politique)
+ pour atteindre son objectif.
+
+
+
+
+
+
`,
+ 'drlTitle': "Apprentissage par renforcement profond",
+ 'drlText': `
+ Pour améliorer continuellement les actions effectuées par les agents, les algorithmes d'ARP utilisent
+ des réseaux de neurones artificiels.
+ Avec de l'entraînement, ces réseaux de neurones sont capables d'apprendre à prédire une action optimale
+ à effectuer à chaque étape à partir de l'observation reçue grâce à toutes les
+ observations et les récompenses préalablement reçues après chaque action effectuée au cours de l'entraînement.
+ Grâce à cela, les algorithmes d'ARP sont capables de produire des comportements très efficaces
+ dans des situations similaires à celles sur lesquelles ils ont été entraînés.
+
+
+
+
+
+
+
+ Cependant, dans des applications réelles, l'environnement reste rarement identique en permanence mais évolue. Par conséquent,
+ l'idéal serait que les agents d'ARP soient capables de généraliser leur comportement
+ à des changements inédits du monde pour qu'ils puissent s'adapter à un large éventail de situations.
+
+ Une solution pour résoudre en partie ce problème est d'entraîner les agents d'ARP sur des environnements générés procéduralement.
+
+ La génération procédurale est une méthode permettant de créer automatiquement des environnements en fonction de quelques paramètres.
+ En utilisant cette méthode, les agents peuvent être entraînés sur un très large éventail d'environnements, leur permettant ainsi
+ de généraliser leur comportement à plus de situations différentes.
+
+
+
+ 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
+ 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.
+
+ L'idéal serait donc de disposer de stratégies d'entraînement plus intelligentes qui proposeraient des
+ environnements pertinents adaptés à l'avancement de l'apprentissage de l'élève (agent).
+ Cette méthode est appelée programme d'apprentissage automatique (Automatic Curriculum Learning) [3]
+ et est incarnée par un algorithme enseignant qui est entraîné dans le but d'apprendre à générer des environnements
+ les plus pertinents possibles tout au long du processus d'entraînement de l'élève en fonction de l'évolution de ses performances.
+
+ De cette manière, l'enseignant commence par proposer des environnements faciles à l'élève puis augmente leur difficulté et leur diversité petit à petit
+ pour garantir que l'élève progresse continuellement tout en ne rencontrant pas toujours la même situation et en n'oubliant pas
+ ce qu'il a déjà appris.
+
`,
+ 'aboutDemoTitle': "À propos de la démo",
+ 'aboutDemoText': `
+
+ Dans cette démo, tous les agents ont été entraînés avec Soft Actor Critic[4]
+ comme algorithme élève d'ARP aux côtés de différents algorithmes enseignants comme par exemple ALP-GMM[5].
+
+ Les agents ont ainsi appris avec succès des comportements efficaces pour se déplacer à travers les envirionnements 2D en
+ généralisant leur comportement à des situations inédites.
+
+
+
+ La physique de la simulation est gérée par le moteur physique box2d.js
+ qui est un portage direct en JavaScript du moteur physique Box2D.
+
+ Les politiques pré-entraînées (comportements des agents) sont chargées dans le navigateur grâce à TensorFlow.js.
+
+ Cette démo a été conçue par Paul Germon dans le cadre d'un stage au sein de
+ l'équipe de recherche Flowers au laboratoire Inria.
+ Ce stage était encadré par Rémy Portelas et Clément Romac, et supervisé par Pierre-Yves Oudeyer.
+ Remerciements particuliers à Nikita Melkozerov pour sa contribution.
+ Citation recommandée:
+
+ @misc{germon2021demo,
+ title={Interactive Deep Reinforcement Learning Demo},
+ author={Germon, Paul and Romac, Clément and Portelas, Rémy and Pierre-Yves, Oudeyer},
+ url={https://developmentalsystems.org/Interactive_DeepRL_Demo/},
+ year={2021}
+ }
+
+
+
+
+
Le code source de cette démo est libre d'accès sur ce dépôt github.
+
Le code de l'environnement et des agents a été adapté en JavaScript à partir du code Python de TeachMyAgent[6].
[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:
+ Solving Rubik's Cube with a Robot Hand (2019). https://arxiv.org/abs/1910.07113
+
[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). https://doi.org/10.1038/nature16961
+
[3] Portelas, R., Colas, C., Weng, L., Hofmann, K., & Oudeyer, P. Y. (2020). Automatic curriculum learning for deep rl: A short survey (2020). https://arxiv.org/abs/2003.04664
+
[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. In International conference on machine learning (pp. 1861-1870). PMLR https://arxiv.org/abs/1801.01290
+
[5] Portelas, R., Colas, C., Hofmann, K., & Oudeyer, P. Y. (2020, May). Teacher algorithms for curriculum learning of deep rl in continuously parameterized environments. In Conference on Robot Learning (pp. 835-853). PMLR. https://arxiv.org/abs/1910.07224
+
[6] Romac, C., Portelas, R., Hofmann, K., & Oudeyer, P.-Y. (2021). TeachMyAgent: A Benchmark for Automatic Curriculum Learning in Deep RL. International Conference on Machine Learning, 9052–9063. https://arxiv.org/abs/2103.09815
+
`,
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/js/ui_state/components/about_tab.js b/js/ui_state/components/about_tab.js
new file mode 100644
index 0000000000000000000000000000000000000000..c1cab808bb91c444e053d77ea66e18523f39923c
--- /dev/null
+++ b/js/ui_state/components/about_tab.js
@@ -0,0 +1,54 @@
+import Component from '../lib/component.js';
+import store from '../store/index.js';
+
+/**
+ * @classdesc UI component for "About..." tab.
+ */
+export default class AboutTab extends Component {
+
+ /**
+ * @constructor
+ */
+ constructor() {
+ super({
+ store,
+ element: document.querySelector('#about-tab'),
+ eventName: 'aboutTabChange'
+ });
+ }
+
+ /**
+ * Renders the global UI elements.
+ */
+ render() {
+ let dict = window.lang_dict[store.state.language]['aboutTab'];
+
+ // Purpose section
+ this.element.querySelector('#purpose-title').innerHTML = dict['purposeTitle'];
+ this.element.querySelector('#purpose-text').innerHTML = dict['purposeText'];
+
+ // RL section
+ this.element.querySelector('#rl-title').innerHTML = dict['rlTitle'];
+ this.element.querySelector('#rl-text').innerHTML = dict['rlText'];
+
+ // DRL section
+ this.element.querySelector('#drl-title').innerHTML = dict['drlTitle'];
+ this.element.querySelector('#drl-text').innerHTML = dict['drlText'];
+
+ // ACL section
+ this.element.querySelector('#acl-title').innerHTML = dict['aclTitle'];
+ this.element.querySelector('#acl-text').innerHTML = dict['aclText'];
+
+ // About demo section
+ this.element.querySelector('#about-demo-title').innerHTML = dict['aboutDemoTitle'];
+ this.element.querySelector('#about-demo-text').innerHTML = dict['aboutDemoText'];
+
+ // Credits section
+ this.element.querySelector('#credits-title').innerHTML = dict['creditsTitle'];
+ this.element.querySelector('#credits-text').innerHTML = dict['creditsText'];
+
+ // References section
+ this.element.querySelector('#references-title').innerHTML = dict['referencesTitle'];
+ this.element.querySelector('#references-text').innerHTML = dict['referencesText'];
+ }
+};
\ No newline at end of file
diff --git a/js/ui_state/components/advanced_options.js b/js/ui_state/components/advanced_options.js
new file mode 100644
index 0000000000000000000000000000000000000000..0fd1928253dc7998846f8bcb1f3d438d010f0d79
--- /dev/null
+++ b/js/ui_state/components/advanced_options.js
@@ -0,0 +1,83 @@
+import Component from '../lib/component.js';
+import store from '../store/index.js';
+
+/**
+ * @classdesc UI component for advanced options.
+ */
+export default class AdvancedOptions extends Component {
+
+ /**
+ * @constructor
+ */
+ constructor() {
+ super({
+ store,
+ element: document.querySelector('#advancedOptions'),
+ eventName: 'advancedOptionsChange'
+ });
+ }
+
+ /**
+ * Renders the advanced options UI elements.
+ */
+ render() {
+
+ const state = store.state.advancedOptionsState;
+
+ let dict = window.lang_dict[store.state.language]['advancedOptions'];
+
+ /* Rendering Options */
+ this.element.querySelector('#renderingOptionsTitle').innerHTML = dict['renderingOptions'];
+
+
+ let drawJointsSwitch = this.element.querySelector('#drawJointsSwitch');
+ let drawLidarsSwitch = this.element.querySelector('#drawLidarsSwitch');
+ let drawNamesSwitch = this.element.querySelector('#drawNamesSwitch');
+ let drawObservationSwitch = this.element.querySelector('#drawObservationSwitch');
+ let drawRewardSwitch = this.element.querySelector('#drawRewardSwitch');
+
+ // Checks the draw switches
+ drawJointsSwitch.checked = state.drawJoints;
+ drawLidarsSwitch.checked = state.drawLidars;
+ drawNamesSwitch.checked = state.drawNames;
+ drawObservationSwitch.checked = state.drawObservation;
+ drawRewardSwitch.checked = state.drawReward;
+
+ // Tooltips
+ drawJointsSwitch.title = dict['drawJointsTooltip'];
+ drawLidarsSwitch.title = dict['drawLidarsTooltip'];
+ drawNamesSwitch.title = dict['drawNamesTooltip'];
+ drawObservationSwitch.title = dict['drawObservationTooltip'];
+ drawRewardSwitch.title = dict['drawRewardTooltip'];
+
+ // Switches labels
+ this.element.querySelector('#drawJointsLabel').innerText = dict['drawJoints'];
+ this.element.querySelector('#drawLidarsLabel').innerText = dict['drawLidars'];
+ this.element.querySelector('#drawNamesLabel').innerText = dict['drawNames'];
+ this.element.querySelector('#drawObservationLabel').innerText = dict['drawObservation'];
+ this.element.querySelector('#drawRewardLabel').innerText = dict['drawReward'];
+
+ /* Assets */
+
+ this.element.querySelector('#assetsTitle').innerHTML = dict['assetsTitle'];
+ this.element.querySelector('#assetsText').innerText = dict['assetsText'];
+ this.element.querySelector('#comingSoon').innerText = dict['comingSoon'];
+
+ // Renders the assets buttons
+ let circleAssetButton = this.element.querySelector('#circleAssetButton');
+ circleAssetButton.innerHTML = dict['circle'];
+ if(state.assets.circle){
+ circleAssetButton.className = "btn btn-asset";
+ }
+ else{
+ circleAssetButton.className = "btn btn-outline-asset";
+ }
+
+ /* Initializes tooltips */
+ this.element.querySelectorAll('[data-bs-toggle="tooltip"]').forEach((el, index) => {
+ return new bootstrap.Tooltip(el, {
+ trigger: 'hover'
+ });
+ });
+ }
+};
\ No newline at end of file
diff --git a/js/ui_state/components/agents_list.js b/js/ui_state/components/agents_list.js
new file mode 100644
index 0000000000000000000000000000000000000000..dc97ca2c532e71010f3d00901f739f19a6bc267c
--- /dev/null
+++ b/js/ui_state/components/agents_list.js
@@ -0,0 +1,134 @@
+import Component from '../lib/component.js';
+import store from '../store/index.js';
+
+const thumbnails_path = "images/agents_thumbnails/";
+
+/**
+ * @classdesc UI component for the list of running agents.
+ */
+export default class AgentsList extends Component {
+ constructor() {
+ super({
+ store,
+ element: document.querySelector('#agents_list_container'),
+ eventName: 'agentsListChange'
+ });
+ }
+
+ /**
+ * Renders the list of running agents and adds event listeners to the different elements.
+ */
+ render() {
+ let dict = window.lang_dict[store.state.language]['agentsList'];
+ let morph_dict = window.lang_dict[store.state.language]['morphologies'];
+ this.element.querySelector('#agents_list_title').innerHTML = `${dict['title']}`;
+ this.element.querySelector('#agents_list').innerHTML = store.state.agents.map(agent => {
+ let age = agent.age == "adult" ? "" : " (" + window.lang_dict[window.get_language()]['morphologies'][agent.age] + ")";
+ // Creates a list item for each agent
+ return `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
`;
+ }).join('');
+
+ // Renders the list items differently when drawing or if the agent is selected
+ this.element.querySelectorAll('li[name="agent-list-item"]').forEach((span, index) => {
+ if(store.state.drawingModeState.drawing){
+ span.classList.add('disabled');
+ }
+ else{
+ if(store.state.agents[index] == store.state.simulationState.agentSelected){
+ span.classList.add("active");
+ }
+ else{
+ span.classList.remove("active");
+ }
+ }
+ });
+
+ /* EVENT LISTENERS */
+
+ // Deletes the agent
+ this.element.querySelectorAll('button[name="deleteButton"]').forEach((span, index) => {
+ span.addEventListener('click', () => {
+ store.dispatch('deleteAgent', {index: index});
+ });
+ });
+
+ // Saves the agent's current position as initial position
+ this.element.querySelectorAll('button[name="savePositionButton"]').forEach((span, index) => {
+ span.addEventListener('click', () => {
+ let pos = window.game.env.agents[index].agent_body.reference_head_object.GetPosition().Clone();
+ store.dispatch('setAgentInitPos', {index: index, init_pos: pos});
+ });
+ });
+
+ // Resets the agent's initial position
+ this.element.querySelectorAll('button[name="resetPositionButton"]').forEach((span, index) => {
+ span.addEventListener('click', () => {
+ store.dispatch('setAgentInitPos', {index: index, init_pos: null});
+ let init_x = TERRAIN_STEP * INITIAL_TERRAIN_STARTPAD / 2;
+ window.game.env.set_agent_position(window.game.env.agents[index], init_x, null);
+ window.game.env.render();
+ });
+ });
+
+ // Follows the agent
+ this.element.querySelectorAll('input[name="followSwitch"]').forEach((span, index) => {
+ span.addEventListener('input', () => {
+ store.dispatch('followAgent', {index: span.checked ? index : -1});
+ });
+ span.checked = store.state.simulationState.agentFollowed == store.state.agents[index];
+ });
+
+ // Renames the agent
+ this.element.querySelectorAll('input[name="agentNameArea"]').forEach((span, index) => {
+ span.addEventListener('keydown', (event) => {
+ if(event.keyCode == '13'){
+ store.dispatch('renameAgent', {index: index, value: span.value});
+ }
+ });
+ });
+
+ /* Initializes tooltips */
+ this.element.querySelectorAll('[data-bs-toggle="tooltip"]').forEach((el, index) => {
+ return new bootstrap.Tooltip(el, {
+ trigger: 'hover'
+ });
+ });
+ }
+};
\ No newline at end of file
diff --git a/js/ui_state/components/drawing_mode.js b/js/ui_state/components/drawing_mode.js
new file mode 100644
index 0000000000000000000000000000000000000000..de71272087fa85a9f376e40854786e741c157c38
--- /dev/null
+++ b/js/ui_state/components/drawing_mode.js
@@ -0,0 +1,72 @@
+import Component from '../lib/component.js';
+import store from '../store/index.js';
+
+/**
+ * @classdesc UI component for the drawing mode buttons.
+ */
+export default class DrawingMode extends Component {
+ /**
+ * @constructor
+ */
+ constructor() {
+ super({
+ store,
+ element: document.querySelector('#draw-tab'),
+ eventName: 'drawingModeChange'
+ });
+ }
+
+ /**
+ * Renders the drawing mode buttons.
+ */
+ render() {
+ const state = store.state.drawingModeState;
+ const dict = window.lang_dict[store.state.language]['drawingMode'];
+
+ let drawGroundButton = this.element.querySelector('#drawGroundButton');
+ let drawCeilingButton = this.element.querySelector('#drawCeilingButton');
+ let eraseButton = this.element.querySelector('#eraseButton');
+ let clearButton = this.element.querySelector('#clearButton');
+ let generateTerrainButton = this.element.querySelector('#generateTerrainButton');
+
+ this.element.querySelector('#drawingIntro').innerHTML = dict['intro'];
+ this.element.querySelector('#drawingText').innerHTML = dict['text'];
+
+ drawGroundButton.innerHTML = ` ${dict['ground']}`;
+ drawCeilingButton.innerHTML = ` ${dict['ceiling']}`;
+ eraseButton.innerHTML = ` ${dict['erase']}`;
+ clearButton.innerHTML = ` ${dict['clear']}`;
+
+ // Enables the buttons when drawing mode is active
+ if(state.drawing){
+
+ drawGroundButton.className = "btn btn-outline-success";
+ drawCeilingButton.className = "btn btn-outline-secondary";
+ eraseButton.className = "btn btn-outline-warning";
+
+ // Fills the button corresponding to the current drawing mode
+ if(state.drawing_ground){
+ drawGroundButton.className = "btn btn-success";
+ }
+ else if(state.drawing_ceiling){
+ drawCeilingButton.className = "btn btn-secondary";
+ }
+ else if(state.erasing){
+ eraseButton.className = "btn btn-warning";
+ }
+
+ generateTerrainButton.className = "btn btn-success";
+ generateTerrainButton.innerText = dict['generateTerrain'];
+ }
+
+ // Disables drawing buttons when drawing mode is inactive
+ else{
+ drawGroundButton.className = "btn btn-outline-success disabled";
+ drawCeilingButton.className = "btn btn-outline-secondary disabled";
+ eraseButton.className = "btn btn-outline-warning disabled";
+
+ generateTerrainButton.className = "btn btn-primary";
+ generateTerrainButton.innerText = dict['draw'];
+ }
+ }
+};
\ No newline at end of file
diff --git a/js/ui_state/components/envs_set.js b/js/ui_state/components/envs_set.js
new file mode 100644
index 0000000000000000000000000000000000000000..4b42db5d72d25ac48870c84918407471d09e60a1
--- /dev/null
+++ b/js/ui_state/components/envs_set.js
@@ -0,0 +1,148 @@
+import Component from '../lib/component.js';
+import store from '../store/index.js';
+
+/**
+ * @classdesc UI component for the sets of environments.
+ */
+export default class EnvsSet extends Component{
+
+ /**
+ * @constructor
+ */
+ constructor() {
+ super({
+ store,
+ element: document.querySelector('#getting-started-tab'),
+ eventName: 'envsSetChange'
+ });
+ }
+
+ /**
+ * Renders the sets of environments and adds event listeners to the different elements.
+ */
+ render(){
+ let dict = window.lang_dict[store.state.language]['envsSets'];
+
+ this.element.querySelector('#baseSetText').innerHTML = dict['baseSetText'];
+ this.element.querySelector('#customSetText').innerHTML = dict['customSetText'];
+
+ /* Base envs set */
+ let baseEnvs = this.element.querySelector('#baseEnvsSet');
+
+ baseEnvs.innerHTML = store.state.envsSets.baseEnvsSet.map(e => {
+ // Creates a card for each environment in the base set
+ return `
+
+
+
+
+
+
+
+
${e.description[store.state.language]['name']}
+
${e.description[store.state.language]['text']}
+
+
+
`
+ }).join('');
+
+ /* Custom envs set */
+ let customEnvs = this.element.querySelector('#customEnvsSet');
+
+ // Creates a special card for uploading files
+ let uploadCard = `
+
+
+
${dict['uploadCard']['title']}
+
${dict['uploadCard']['text']}
+
+
+
+
+
+
+
`;
+
+ let envCards = store.state.envsSets.customEnvsSet.map((e, index) => {
+ // Creates a card for each environment in the custom set
+ return `
+
+
+
+
+
+
+
+
${e.description.name}
+
${e.description.text}
+
+
+
+
+
+
`
+ }).join('');
+
+ customEnvs.innerHTML = [uploadCard, envCards].join('');
+
+ /* EVENT LISTENERS */
+
+ // Loads a base environment
+ baseEnvs.querySelectorAll('div[name="env-card"]').forEach((span, index) => {
+ span.addEventListener('click', () => {
+ store.dispatch('loadEnv', store.state.envsSets.baseEnvsSet[index]);
+ })
+ });
+
+ // Loads a custom environment
+ customEnvs.querySelectorAll('div[name="env-card"]').forEach((span, index) => {
+ span.addEventListener('click', () => {
+ store.dispatch('loadEnv', store.state.envsSets.customEnvsSet[index]);
+ })
+ });
+
+ // Parses the uploaded file and adds the corresponding environment to the custom set
+ customEnvs.querySelector('#uploadEnvBtn').addEventListener('click', () => {
+ let reader = new FileReader();
+ reader.addEventListener('load', () => {
+ let env = JSON.parse(reader.result);
+ store.dispatch('addEnv',{set: 'custom', env: env});
+ });
+ let file = customEnvs.querySelector('#uploadEnvFile').files[0];
+ if(file != null){
+ reader.readAsText(file);
+ }
+ });
+
+ // Downloads a custom environment
+ customEnvs.querySelectorAll('button[name="downloadEnvBtn"]').forEach((span, index) => {
+ span.addEventListener('click', () => {
+ downloadObjectAsJson(store.state.envsSets.customEnvsSet[index], store.state.envsSets.customEnvsSet[index].description.name.split(' ').join('_'));
+ })
+ });
+
+ // Deletes a custom environment
+ customEnvs.querySelectorAll('button[name="deleteEnvBtn"]').forEach((span, index) => {
+ span.addEventListener('click', () => {
+ store.dispatch('deleteEnv', index);
+ })
+ });
+
+ /* Initializes tooltips */
+ this.element.querySelectorAll('[data-bs-toggle="tooltip"]').forEach((el, index) => {
+ return new bootstrap.Tooltip(el, {
+ trigger: 'hover'
+ });
+ });
+ }
+};
\ No newline at end of file
diff --git a/js/ui_state/components/global_elements.js b/js/ui_state/components/global_elements.js
new file mode 100644
index 0000000000000000000000000000000000000000..4981d77acddb21fb08cd498587a1207997df3656
--- /dev/null
+++ b/js/ui_state/components/global_elements.js
@@ -0,0 +1,47 @@
+import Component from '../lib/component.js';
+import store from '../store/index.js';
+
+/**
+ * @classdesc UI component for global elements.
+ */
+export default class GlobalElements extends Component{
+
+ /**
+ * @constructor
+ */
+ constructor() {
+ super({
+ store,
+ element: document.body,
+ eventName: 'globalElementsChange'
+ });
+ }
+
+ /**
+ * Renders the global UI elements.
+ */
+ render() {
+ let dict = window.lang_dict[store.state.language]['globalElements'];
+
+ // Title
+ this.element.querySelector('#demoTitle').innerText = dict['demoTitle'];
+
+ // Tabs buttons
+ this.element.querySelector('#getting-started-btn').innerText = dict['gettingStarted'];
+ this.element.querySelector('#parkour-custom-btn').innerText = dict['parkourCustomization'];
+ this.element.querySelector('#advanced-options-btn').innerText = dict['advancedOptions'];
+ this.element.querySelector('#about-btn').innerHTML = ` ${dict['about']}`;
+
+ // Changes the selected index of the language dropdown
+ this.element.querySelector('#langSelect').selectedIndex = store.state.language == 'EN' ? 0 : 1;
+
+ // Save env modal
+ let modal = this.element.querySelector('#saveEnvModal');
+ modal.querySelector('#save-modal-title').innerHTML = dict['saveEnvModal']['title'];
+ modal.querySelector('#save-modal-text').innerText = dict['saveEnvModal']['text'];
+ modal.querySelector('#env-name-label').innerText = dict['saveEnvModal']['nameLabel'];
+ modal.querySelector('#env-description-label').innerText = dict['saveEnvModal']['descriptionLabel'];
+ modal.querySelector('#save-cancel-btn').innerText = dict['saveEnvModal']['cancelBtn'];
+ modal.querySelector('#save-confirm-btn').innerText = dict['saveEnvModal']['confirmBtn'];
+ }
+};
\ No newline at end of file
diff --git a/js/ui_state/components/main_buttons.js b/js/ui_state/components/main_buttons.js
new file mode 100644
index 0000000000000000000000000000000000000000..7520de276f37497351d850659d270187c6a70c8c
--- /dev/null
+++ b/js/ui_state/components/main_buttons.js
@@ -0,0 +1,78 @@
+import Component from '../lib/component.js';
+import store from '../store/index.js';
+
+/**
+ * @classdesc UI component for the main buttons.
+ */
+export default class MainButtons extends Component {
+
+ /**
+ * @constructor
+ */
+ constructor() {
+ super({
+ store,
+ element: document.querySelector('#mainButtons'),
+ eventName: 'mainButtonsChange'
+ });
+ }
+
+ /**
+ * Renders the run, reset and save buttons.
+ */
+ render() {
+ const status = store.state.simulationState.status;
+
+ let runButton = this.element.querySelector("#runButton");
+ let resetButton = this.element.querySelector("#resetButton");
+ let saveEnvButton = this.element.querySelector('#saveEnvButton');
+
+ let dict = window.lang_dict[store.state.language]['mainButtons'];
+
+ // Disables buttons while drawing
+ if(store.state.drawingModeState.drawing){
+ runButton.className = "btn btn-success disabled";
+ runButton.title = dict['runBtnTooltip'];
+ resetButton.className = "btn btn-danger disabled";
+ saveEnvButton.className = "btn btn-primary mx-3 disabled";
+ }
+
+ // Enables buttons when not drawing
+ else{
+ // Turns run button into pause button while running
+ if (status == 'running') {
+ runButton.className = "btn btn-warning";
+ runButton.childNodes[0].classList.add("fa-pause");
+ runButton.childNodes[0].classList.remove("fa-play");
+ runButton.title = dict['pauseBtnTooltip'];
+ }
+
+ // Turns pause button into run button when not running
+ else {
+ runButton.className = "btn btn-success";
+ runButton.childNodes[0].classList.remove("fa-pause");
+ runButton.childNodes[0].classList.add("fa-play");
+ runButton.title = dict['runBtnTooltip'];
+ }
+
+ // Disables save button during intro tour
+ if(store.state.simulationState.intro_tour){
+ saveEnvButton.className = "btn btn-primary mx-3 disabled";
+ }
+ else{
+ saveEnvButton.className = "btn btn-primary mx-3";
+ saveEnvButton.title = dict['saveBtnTooltip'];
+ }
+
+ resetButton.className = "btn btn-danger";
+ resetButton.title = dict['resetBtnTooltip'];
+ }
+
+ /* Initializes tooltips */
+ this.element.querySelectorAll('[data-bs-toggle="tooltip"]').forEach((el, index) => {
+ return new bootstrap.Tooltip(el, {
+ trigger: 'hover'
+ });
+ });
+ }
+};
\ No newline at end of file
diff --git a/js/ui_state/components/morphologies_list.js b/js/ui_state/components/morphologies_list.js
new file mode 100644
index 0000000000000000000000000000000000000000..fd61c80e9a8dfb99d98bada8884a7e925498b659
--- /dev/null
+++ b/js/ui_state/components/morphologies_list.js
@@ -0,0 +1,132 @@
+import Component from '../lib/component.js';
+import store from '../store/index.js';
+
+const thumbnails_path = "images/agents_thumbnails/";
+
+/**
+ * @classdesc UI component for the list of morphologies.
+ */
+export default class MorphologiesList extends Component {
+
+ /**
+ * @constructor
+ */
+ constructor() {
+ super({
+ store,
+ element: document.querySelector('#agents-selection'),
+ eventName: 'morphologiesChange'
+ });
+ }
+
+ /**
+ * Renders the list of morphologies and adds event listeners for the different elements.
+ */
+ render() {
+
+ let dict = window.lang_dict[store.state.language]['morphologies'];
+
+ this.element.querySelector('#agents-selection-title').innerHTML = dict['title'];
+ this.element.querySelector('#agents-selection-text').innerText = dict['text'];
+
+ this.element.querySelector('#morphologies-list').innerHTML = store.state.morphologies.map(m => {
+ // Creates a list item for each morphology
+ return `