├── Examples ├── Asteroids │ ├── asteroid.js │ ├── index.html │ ├── player.js │ ├── population.js │ ├── projectile.js │ ├── sketch.js │ └── spaceship.js ├── Classification │ ├── index.html │ ├── node.js │ ├── player.js │ └── sketch.js ├── PoleBalancing │ ├── CartPole.js │ ├── index.html │ ├── player.js │ ├── population.js │ └── sketch.js ├── SelfDriving │ ├── img │ │ ├── asphalt.jpg │ │ └── wireframe.png │ ├── index.html │ ├── libs │ │ ├── ammo.js │ │ ├── neat │ │ │ ├── connection.js │ │ │ ├── genome.js │ │ │ ├── node.js │ │ │ ├── player.js │ │ │ └── population.js │ │ ├── physi.js │ │ ├── physijs_worker.js │ │ └── util.js │ └── sketch.js └── TargetSeeking │ ├── index.html │ ├── player.js │ ├── population.js │ └── sketch.js ├── README.md ├── Screenshots ├── Classification.png ├── PoleBalancing.png └── TargetSeeking.png ├── _config.yml ├── _layouts └── default.html ├── favicon.ico └── src ├── connection.js ├── genome.js ├── index.html ├── node.js ├── player.js ├── population.js └── sketch.js /Examples/Asteroids/asteroid.js: -------------------------------------------------------------------------------- 1 | class asteroid{ 2 | constructor(pos, radius, angle){ 3 | this.vertexN = random(8,15); 4 | 5 | if(angle && (Math.random() < 0.5)) { 6 | let tempAngle = angle + (Math.random() - 0.5); 7 | this.vel = createVector(Math.cos(tempAngle), Math.sin(tempAngle)) 8 | } else { 9 | this.vel = p5.Vector.random2D(); 10 | } 11 | 12 | 13 | if(pos) { 14 | this.pos = pos.copy(); 15 | } else { 16 | this.pos = createVector(random(0, width), random(0, height)); 17 | while(dist(this.pos.x, this.pos.y, width/2, height/2) < 100){ 18 | this.pos.mult(2); 19 | this.pos.x %= width; 20 | this.pos.y %= height; 21 | } 22 | } 23 | 24 | if(radius) 25 | this.radius = radius; 26 | else 27 | this.radius = random(20,50); 28 | 29 | this.offset = []; 30 | for(let i = 0; i= width ? 0 : this.pos.x <= 0 ? width : this.pos.x; 46 | this.pos.y = this.pos.y >= height ? 0 : this.pos.y <= 0 ? height : this.pos.y; 47 | } 48 | 49 | show(color){ 50 | push(); 51 | noFill(); 52 | stroke(color); 53 | translate(this.pos.x, this.pos.y); 54 | 55 | beginShape(); 56 | for(let i = 0; i 2 | 3 | 4 | 5 | Asteroids - NeatJS 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 40 | 41 | 42 | 43 |
44 |
45 |
46 | 47 |
48 |
49 |

Data:

50 | 51 | Best Fitness: 52 |
53 | Current Generation: 54 |
55 | Prev. Gen. Average Score: 0 56 |
57 | Prev. Gen. Average HitRate: 0 58 |
59 | 60 |

Options:

61 | Show All players: 62 |
63 | Show the best player: 64 |
65 | Run only the best player: 66 |
67 | Exponential Fitness Function: 68 |
69 | Speed: 1 70 |
71 | 72 |
73 |

Best Genome:

74 |
75 |
76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /Examples/Asteroids/player.js: -------------------------------------------------------------------------------- 1 | var maxScore = 0; 2 | var MAX_SPEED = 5; 3 | var asteroidsN = 5; 4 | var asteroidsDistance = 200; 5 | 6 | //The Player Class 7 | //The interface between our 8 | //NeuralNetwork and the game 9 | class Player{ 10 | constructor(id){ 11 | this.brain = new Genome(genomeInputsN, genomeOutputN, id); 12 | this.fitness; 13 | 14 | this.color = color(random(0, 255), random(0, 255), random(0, 255)); 15 | this.radius = 7; 16 | this.ship = new spaceShip(); 17 | this.projectiles = []; 18 | this.asteroids = []; 19 | 20 | let angle = 0; 21 | for(let j = 0; j < asteroidsN; j++){ 22 | let tempAngle = angle + (Math.random() - 0.5); 23 | let astPos = createVector(width/2 + Math.cos(tempAngle) * asteroidsDistance, height/2 + Math.sin(tempAngle) * asteroidsDistance); 24 | let angleToShip = this.angleFromPoint(astPos.x, astPos.y); 25 | 26 | let ast = new asteroid(astPos, random(20,50), angleToShip); 27 | this.asteroids[j] = ast; 28 | angle += (Math.PI * 2) / asteroidsN; 29 | } 30 | 31 | this.score = 1; 32 | this.lifespan = 0; 33 | this.dead = false; 34 | this.decisions = []; //Current Output values 35 | this.vision = []; //Current input values 36 | } 37 | 38 | clone() { //Returns a copy of this player 39 | let clone = new Player(); 40 | clone.brain = this.brain.clone(); 41 | return clone; 42 | } 43 | 44 | crossover(parent){ //Produce a child 45 | let child = new Player(); 46 | if(parent.fitness < this.fitness) 47 | child.brain = this.brain.crossover(parent.brain); 48 | else 49 | child.brain = parent.brain.crossover(this.brain); 50 | 51 | child.brain.mutate() 52 | return child; 53 | } 54 | 55 | 56 | //Game stuff 57 | look(show){ 58 | this.vision = this.ship.look(this.asteroids, show); 59 | } 60 | 61 | think(){ 62 | this.decisions = this.brain.feedForward(this.vision); 63 | } 64 | 65 | move(){ 66 | if(this.decisions[0] > 0.8) 67 | this.ship.forward(); 68 | 69 | if(this.decisions[1] > 0.8) 70 | if(this.decisions[2] > 0.8) 71 | if(this.decisions[1] > this.decisions[2]) 72 | this.ship.right(); 73 | else 74 | this.ship.left(); 75 | else if(this.decisions[2] > 0.8) 76 | this.ship.left(); 77 | 78 | if(this.decisions[3] > 0.8) { 79 | let prj = this.ship.shot(); 80 | if(prj != null) 81 | this.projectiles.push(prj); 82 | } 83 | } 84 | 85 | update(){ 86 | this.ship.update(); 87 | 88 | if(this.asteroids.length > 0){ 89 | for(let j = 0; j < this.asteroids.length; j++){ 90 | if(this.ship.hits(this.asteroids[j])) 91 | this.dead = true; 92 | this.asteroids[j].update(); 93 | } 94 | 95 | for(let i = this.projectiles.length - 1; i >= 0; i--){ 96 | this.projectiles[i].update(); 97 | if(this.projectiles[i].timer > 40){ 98 | this.projectiles.splice(i,1); 99 | }else{ 100 | for(let j = this.asteroids.length - 1; j >= 0; j--){ 101 | if(this.projectiles[i].hits(this.asteroids[j])){ 102 | if(this.asteroids[j].radius > 15){ 103 | let newAsteroids = this.asteroids[j].breakup(); 104 | this.asteroids.push(newAsteroids[0]); 105 | this.asteroids.push(newAsteroids[1]); 106 | } 107 | 108 | this.projectiles.splice(i,1); 109 | this.asteroids.splice(j,1); 110 | this.score++; 111 | break; 112 | } 113 | } 114 | } 115 | } 116 | }else{ 117 | this.dead = true; 118 | } 119 | 120 | this.lifespan ++; 121 | if(this.lifespan > maxLifespan) 122 | this.dead = true; 123 | } 124 | 125 | show(){ 126 | this.ship.show(this.color) 127 | this.asteroids.forEach((element) => { 128 | element.show(this.color); 129 | }); 130 | this.projectiles.forEach((element) => { 131 | element.show(this.color); 132 | }); 133 | } 134 | 135 | calculateFitness(){ //Fitness function : adapt it to the needs of the 136 | this.hitRate = this.score/this.ship.shots; 137 | this.fitness = (this.score)*10; 138 | this.fitness *= this.lifespan; 139 | this.fitness *= this.hitRate*this.hitRate; 140 | this.fitness *= expFunc ? this.fitness : 1; 141 | } 142 | 143 | angleFromPoint(x, y){ 144 | let d = dist(x, y, this.ship.pos.x, this.ship.pos.y); 145 | let dx = (this.ship.pos.x-x) / d; 146 | let dy = (this.ship.pos.y-y) / d; 147 | 148 | let a = Math.acos(dx); 149 | a = dy < 0 ? 2 * Math.PI - a : a; 150 | return a; 151 | } 152 | } 153 | 154 | -------------------------------------------------------------------------------- /Examples/Asteroids/population.js: -------------------------------------------------------------------------------- 1 | let genomeInputsN = 16; 2 | let genomeOutputN = 4; 3 | let bestPlayer; 4 | let bestFitness = 0; 5 | let showBest = true; 6 | let showAll = false; 7 | let runOnlyBest = false; 8 | 9 | //The Population Class 10 | //Here is where the power of all the classes 11 | //comes together to destroy the game score records 12 | class Population{ 13 | constructor(size){ 14 | this.population = []; 15 | 16 | this.generation = 0; 17 | this.matingPool = []; 18 | 19 | for(let i = 0; i < size; i++){ 20 | this.population.push(new Player()); 21 | this.population[i].brain.generateNetwork(); 22 | this.population[i].brain.mutate(); 23 | } 24 | 25 | bestPlayer = this.population[0].clone(); 26 | bestPlayer.brain.id = "BestGenome"; 27 | bestPlayer.brain.draw(400, 300, "svgContainer"); 28 | } 29 | 30 | updateAlive(show){ 31 | if(!runOnlyBest) { 32 | for(let i = 0; i < this.population.length; i++){ 33 | if(!this.population[i].dead){ 34 | this.population[i].look(); 35 | this.population[i].think(); 36 | this.population[i].move(); 37 | this.population[i].update(); 38 | 39 | if(show && showAll) 40 | this.population[i].show(); 41 | } 42 | } 43 | 44 | if(bestPlayer && !bestPlayer.dead){ 45 | bestPlayer.look(); 46 | bestPlayer.think(); 47 | bestPlayer.move(); 48 | bestPlayer.update(); 49 | if(show && showBest) 50 | bestPlayer.show(); 51 | } 52 | } else { 53 | bestPlayer.look(); 54 | bestPlayer.think(); 55 | bestPlayer.move(); 56 | bestPlayer.update(); 57 | bestPlayer.show(); 58 | } 59 | } 60 | 61 | done(){ 62 | if(runOnlyBest && bestPlayer.dead){ 63 | bestPlayer = bestPlayer.clone(); 64 | return true; 65 | } 66 | 67 | for(let i = 0; i < this.population.length; i++){ 68 | if(!this.population[i].dead){ 69 | return false; 70 | } 71 | } 72 | 73 | bestPlayer = bestPlayer.clone(); 74 | return true; 75 | } 76 | 77 | naturalSelection(){ 78 | if(runOnlyBest) 79 | return; 80 | 81 | this.calculateFitness(); 82 | 83 | let averageSums = this.getAverageScore(); 84 | console.log(averageSums[0]); 85 | document.getElementById("prevScore").innerHTML = averageSums[0]; 86 | document.getElementById("prevHitRate").innerHTML = averageSums[1]; 87 | 88 | let children = []; 89 | 90 | this.fillMatingPool(); 91 | for(let i = 0; i < this.population.length; i++){ 92 | let parent1 = this.selectPlayer(); 93 | let parent2 = this.selectPlayer(); 94 | if(parent1.fitness > parent2.fitness) 95 | children.push(parent1.crossover(parent2)); 96 | else 97 | children.push(parent2.crossover(parent1)); 98 | } 99 | 100 | 101 | this.population.splice(0, this.population.length); 102 | this.population = children.slice(0); 103 | this.generation++; 104 | this.population.forEach((element) => { 105 | element.brain.generateNetwork(); 106 | }); 107 | 108 | console.log("Generation " + this.generation); 109 | //console.log(this); 110 | } 111 | 112 | calculateFitness(){ 113 | let currentMax = 0; 114 | this.population.forEach((element) => { 115 | element.calculateFitness(); 116 | if(element.fitness > bestFitness){ 117 | bestFitness = element.fitness; 118 | bestPlayer = element.clone(); 119 | bestPlayer.brain.id = "BestGenome"; 120 | bestPlayer.brain.draw(); 121 | } 122 | 123 | if(element.fitness > currentMax) 124 | currentMax = element.fitness; 125 | }); 126 | 127 | //Normalize 128 | this.population.forEach((element, elementN) => { 129 | element.fitness /= currentMax; 130 | }); 131 | } 132 | 133 | fillMatingPool(){ 134 | this.matingPool.splice(0, this.matingPool.length); 135 | this.population.forEach((element, elementN) => { 136 | let n = element.fitness * 100; 137 | for(let i = 0; i < n; i++) 138 | this.matingPool.push(elementN); 139 | }); 140 | } 141 | 142 | selectPlayer(){ 143 | let rand = Math.floor(Math.random() * this.matingPool.length); 144 | return this.population[this.matingPool[rand]]; 145 | } 146 | 147 | getAverageScore(){ 148 | let avSum = 0; 149 | let avHit = 0; 150 | this.population.forEach((element) => { 151 | avSum += element.score; 152 | avHit += element.hitRate; 153 | }); 154 | 155 | return [avSum / this.population.length, avHit / this.population.length]; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /Examples/Asteroids/projectile.js: -------------------------------------------------------------------------------- 1 | class projectile{ 2 | constructor(x, y, d){ 3 | this.timer = 0; 4 | this.position = createVector(x ,y); 5 | this.direction = d; 6 | this.velocity = createVector(0,0); 7 | this.velocity.x -= Math.cos(this.direction)*10; 8 | this.velocity.y -= Math.sin(this.direction)*10; 9 | } 10 | 11 | update(){ 12 | this.timer ++; 13 | this.position.add(this.velocity); 14 | 15 | this.position.x = this.position.x >= width ? 0 : this.position.x <= 0 ? width : this.position.x; 16 | this.position.y = this.position.y >= height ? 0 : this.position.y <= 0 ? height : this.position.y; 17 | } 18 | 19 | show(color){ 20 | push(); 21 | fill(color); 22 | ellipse(this.position.x, this.position.y, 5); 23 | pop(); 24 | } 25 | 26 | hits(ast){ 27 | let d = dist(this.position.x, this.position.y, ast.pos.x, ast.pos.y); 28 | if(d <= ast.radius) 29 | return true; 30 | return false; 31 | } 32 | 33 | hitEdge(){ 34 | if(this.position.x >= width || this.position.x <= 0 || this.position.y >= height || this.position.y <= 0){ 35 | return true; 36 | } 37 | 38 | return false; 39 | } 40 | } -------------------------------------------------------------------------------- /Examples/Asteroids/sketch.js: -------------------------------------------------------------------------------- 1 | let population; 2 | let cycles = 1; 3 | let game; 4 | let first = 0; 5 | let x = 255; 6 | let maxLifespan = 200; 7 | var expFunc = false; 8 | 9 | function setup() { 10 | let canvas = createCanvas(600, 400); 11 | canvas.parent('canvascontainer'); 12 | 13 | population = new Population(150); 14 | } 15 | 16 | function draw() { 17 | document.getElementById("generationN").innerHTML = population.generation; 18 | document.getElementById("bestFitness").innerHTML = bestFitness; 19 | 20 | showAll = document.getElementById("allCheckbox").checked; 21 | showBest = document.getElementById("bestCheckbox").checked; 22 | runOnlyBest = document.getElementById("runBestCheckbox").checked; 23 | 24 | cycles = select('#speedSlider').value(); 25 | select('#speed').html(cycles); 26 | background(51); 27 | 28 | for(let i = 0; i < cycles; i++) 29 | if(!population.done()){ 30 | population.updateAlive(cycles < 5 && i == 0); 31 | } else { 32 | population.naturalSelection(); 33 | maxLifespan += 10; 34 | } 35 | } -------------------------------------------------------------------------------- /Examples/Asteroids/spaceship.js: -------------------------------------------------------------------------------- 1 | let nbSensors = 16; 2 | let maxSensorSize = 256; 3 | 4 | 5 | class spaceShip{ 6 | constructor(){ 7 | this.pos = createVector(width/2, height/2); 8 | this.vel = createVector(0,0); 9 | this.angularVelocity = 0; 10 | this.direction = 90; 11 | this.radius = 7; 12 | 13 | this.counter = 0; 14 | this.shots = 5; 15 | } 16 | 17 | forward(){ 18 | this.vel.x -= Math.cos((this.direction*Math.PI*2)/360); 19 | this.vel.y -= Math.sin((this.direction*Math.PI*2)/360); 20 | } 21 | 22 | backward(){ 23 | //this.vel.x += Math.cos((this.direction*Math.PI*2)/360); 24 | //this.vel.y += Math.sin((this.direction*Math.PI*2)/360); 25 | } 26 | 27 | right(){ 28 | this.angularVelocity += 0.5; 29 | } 30 | 31 | left(){ 32 | this.angularVelocity -= 0.5; 33 | } 34 | 35 | shot(){ 36 | if(this.counter > 20) { 37 | this.counter = 0; 38 | this.shots++; 39 | return new projectile(this.pos.x, this.pos.y, this.direction * Math.PI/180); 40 | } 41 | return null; 42 | } 43 | 44 | hits(ast){ 45 | let d = dist(this.pos.x, this.pos.y, ast.pos.x, ast.pos.y); 46 | if(d <= this.radius + ast.radius) 47 | return true; 48 | return false; 49 | } 50 | 51 | update() { 52 | this.counter++; 53 | this.pos.add(this.vel); 54 | this.direction += this.angularVelocity; 55 | this.vel.mult(0.90); 56 | this.angularVelocity *= 0.90; 57 | 58 | this.pos.x = this.pos.x >= width ? 0 : this.pos.x <= 0 ? width : this.pos.x; 59 | this.pos.y = this.pos.y >= height ? 0 : this.pos.y <= 0 ? height : this.pos.y; 60 | } 61 | 62 | show(color){ 63 | push(); 64 | noFill(); 65 | strokeWeight(2); 66 | stroke(color); 67 | translate(this.pos.x, this.pos.y); 68 | rotate(((this.direction-90)*Math.PI*2)/360); 69 | triangle(-this.radius, this.radius, this.radius, this.radius, 0, -this.radius); 70 | pop(); 71 | } 72 | 73 | 74 | //------------------------------------------------------ 75 | look(asteroids, show = false){ 76 | 77 | x = 255; 78 | let direction; 79 | let vision = []; 80 | for (let i = 0; i < nbSensors; i++) { 81 | direction = p5.Vector.fromAngle((this.direction*Math.PI/180) + i*(PI/8)); 82 | direction.mult(10); 83 | let dirLook = this.lookInDirection(direction, asteroids, show); 84 | vision.push(dirLook); 85 | x -= 30; 86 | } 87 | 88 | return vision; 89 | } 90 | 91 | lookInDirection(direction, asteroids, show){ 92 | let position = createVector(this.pos.x,this.pos.y); 93 | let distance = 0; 94 | //move once in the desired direction before starting 95 | position.add(direction); 96 | distance +=1; 97 | 98 | while(distance < 40){ 99 | if(show){ 100 | push(); 101 | fill(x); 102 | ellipse(position.x, position.y, 5); 103 | pop(); 104 | } 105 | 106 | for(let i = 0; i < asteroids.length; i++){ 107 | let d = dist(position.x, position.y, asteroids[i].pos.x, asteroids[i].pos.y); 108 | if(d <= asteroids[i].radius){ 109 | return map(distance, 0, 40, 1, 0); 110 | } 111 | } 112 | 113 | position.add(direction); 114 | position.x = position.x >= width ? 0 : position.x <= 0 ? width : position.x; 115 | position.y = position.y >= height ? 0 : position.y <= 0 ? height : position.y; 116 | distance++; 117 | } 118 | 119 | return 0; 120 | } 121 | } -------------------------------------------------------------------------------- /Examples/Classification/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Classification - NeatJS 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 27 | 28 | 29 | 30 | 31 |
32 |
33 |

Correctly classified as: Class 1 & Class 2
34 | Not Correctly Classified

35 |
36 |

Options:

37 | Use Sin as activation: 38 |
39 | Speed: 1 40 |
41 |
42 | 43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Examples/Classification/node.js: -------------------------------------------------------------------------------- 1 | var activationsNames = ["Sigmoid", "Identity", "Step", "Tanh", "ReLu", "Sin"]; //Used in the svg drawing 2 | 3 | //The Node Class 4 | //This is where math appends 5 | class Node { 6 | constructor(num, lay, isOutput) { 7 | this.number = num; 8 | this.layer = lay; 9 | this.activationFunction = Math.floor(Math.random() * (sinAllowed ? 6 : 5)); //Number between 0 and 4 10 | this.bias = Math.random() * 2 - 1; 11 | this.output = isOutput || false; //is this node an Output node? 12 | 13 | this.inputSum = 0; 14 | this.outputValue = 0; 15 | this.outputConnections = []; 16 | } 17 | 18 | engage() { //Pass down the network the calculated output value 19 | if (this.layer != 0) //No activation function on input nodes 20 | this.outputValue = this.activation(this.inputSum + this.bias); 21 | 22 | this.outputConnections.forEach((conn) => { 23 | if (conn.enabled) //Do not pass value if connection is disabled 24 | conn.toNode.inputSum += conn.weight * this.outputValue; //Weighted output sum 25 | }); 26 | } 27 | 28 | mutateBias() { //Randomly mutate the bias of this node 29 | let rand = Math.random(); 30 | if (rand < 0.05) //5% chance of being assigned a new random value 31 | this.bias = Math.random() * 2 - 1; 32 | else //95% chance of being uniformly perturbed 33 | this.bias += randomGaussian() / 50; 34 | } 35 | 36 | mutateActivation() { //Randomly choose a new activationFunction 37 | this.activationFunction = Math.floor(Math.random() * (sinAllowed ? 6 : 5)); //Number between 0 and 4 38 | } 39 | 40 | isConnectedTo(node) { //Check if two nodes are connected 41 | if (node.layer == this.layer) //nodes in the same layer cannot be connected 42 | return false; 43 | 44 | 45 | if (node.layer < this.layer) { //Check parameter node connections 46 | node.outputConnections.forEach((conn) => { 47 | if (conn.toNode == this) //Is Node connected to This? 48 | return true; 49 | }); 50 | } else { //Check this node connections 51 | this.outputConnections.forEach((conn) => { 52 | if (conn.toNode == node) //Is This connected to Node? 53 | return true; 54 | }); 55 | } 56 | 57 | return false; 58 | } 59 | 60 | clone() { //Returns a copy of this node 61 | let node = new Node(this.number, this.layer, this.output); 62 | node.bias = this.bias; //Same bias 63 | node.activationFunction = this.activationFunction; //Same activationFunction 64 | return node; 65 | } 66 | 67 | activation(x) { //All the possible activation Functions 68 | switch (this.activationFunction) { 69 | case 0: //Sigmoid 70 | return 1 / (1 + Math.pow(Math.E, -4.9 * x)); 71 | break; 72 | case 1: //Identity 73 | return x; 74 | break; 75 | case 2: //Step 76 | return x > 0 ? 1 : 0; 77 | break; 78 | case 3: //Tanh 79 | return Math.tanh(x); 80 | break; 81 | case 4: //ReLu 82 | return x < 0 ? 0 : x; 83 | break; 84 | case 5: //Sin 85 | return Math.sin(x); 86 | break; 87 | default: //Sigmoid 88 | return 1 / (1 + Math.pow(Math.E, -4.9 * x)); 89 | break; 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /Examples/Classification/player.js: -------------------------------------------------------------------------------- 1 | //The Player Class 2 | //The interface between our 3 | //NeuralNetwork and the game 4 | class Player{ 5 | constructor(id){ 6 | this.brain = new Genome(genomeInputsN, genomeOutputN, id); 7 | this.fitness; 8 | 9 | this.score = 1; 10 | this.lifespan = 0; 11 | this.dead = false; 12 | this.decisions = []; //Current Output values 13 | this.vision = []; //Current input values 14 | } 15 | 16 | clone() { //Returns a copy of this player 17 | let clone = new Player(); 18 | clone.brain = this.brain.clone(); 19 | return clone; 20 | } 21 | 22 | crossover(parent){ //Produce a child 23 | let child = new Player(); 24 | if(parent.fitness < this.fitness) 25 | child.brain = this.brain.crossover(parent.brain); 26 | else 27 | child.brain = parent.brain.crossover(this.brain); 28 | 29 | child.brain.mutate() 30 | return child; 31 | } 32 | 33 | 34 | //Game stuff 35 | look(){ 36 | this.vision = [points[this.lifespan].x, points[this.lifespan].y]; 37 | this.correctVal = points[this.lifespan].type; 38 | } 39 | 40 | think(){ 41 | this.decisions = this.brain.feedForward(this.vision); 42 | } 43 | 44 | move(){ 45 | let maxIndex = 0; 46 | for(let i = 0; i < this.decisions.length; i++) 47 | if(this.decisions[i] > this.decisions[maxIndex]) 48 | maxIndex = i; 49 | 50 | this.val = this.decisions[maxIndex] >= 0 ? 1 : 0; 51 | } 52 | 53 | update(){ 54 | if(this.correctVal == this.val) { 55 | this.score++; 56 | } 57 | 58 | this.lifespan++; 59 | if(this.lifespan >= points.length) 60 | this.dead = true; 61 | } 62 | 63 | show(){ 64 | push(); 65 | if(this.correctVal == this.val) { 66 | if(this.correctVal == 1) 67 | fill(0, 255, 0); 68 | 69 | if(this.correctVal == 0) 70 | fill(0, 0, 255); 71 | 72 | } else { 73 | fill(255, 0, 0); 74 | } 75 | 76 | ellipse(map(points[this.lifespan - 1].x, 0, Math.PI*4, 0, width), points[this.lifespan - 1].y * height, 6) 77 | pop(); 78 | } 79 | 80 | calculateFitness(){ //Fitness function : adapt it to the needs of the 81 | this.fitness = this.score; 82 | } 83 | } -------------------------------------------------------------------------------- /Examples/Classification/sketch.js: -------------------------------------------------------------------------------- 1 | var population; 2 | var cycles, sinAllowed; 3 | var points = []; 4 | var myFunction = function(x) { 5 | return Math.sin(x)*0.4 + 0.5; 6 | }; 7 | 8 | function setup() { 9 | let canvas = createCanvas(600, 400); 10 | canvas.parent('canvascontainer'); 11 | background(61); 12 | 13 | //Initialize the population 14 | population = new Population(500); 15 | 16 | //Generate and Draw points 17 | for(let i = 0; i < 1000; i++) { 18 | let y = Math.random(), x = Math.random() * Math.PI * 4; 19 | let type = y > myFunction(x) ? 1 : 0; 20 | points.push({x: x, y: y, type: type}); 21 | 22 | fill(255); 23 | ellipse(map(points[i].x, 0, Math.PI*4, 0, width), points[i].y * height, 10) 24 | } 25 | 26 | //Draw separation line 27 | for(let i = 0; i < width; i += 1) { 28 | fill(50); 29 | ellipse(i, myFunction(map(i, 0, width, 0, Math.PI*4)) * height, 10) 30 | } 31 | } 32 | 33 | function draw() { 34 | sinAllowed = document.getElementById("sinCheckbox").checked; 35 | cycles = select('#speedSlider').value(); 36 | select('#speed').html(cycles); 37 | 38 | for(let i = 0; i < cycles; i++) 39 | if(!population.done()) 40 | population.updateAlive(); 41 | else 42 | population.naturalSelection(); 43 | } -------------------------------------------------------------------------------- /Examples/PoleBalancing/CartPole.js: -------------------------------------------------------------------------------- 1 | class CartPole { 2 | /** 3 | * Constructor of CartPole. 4 | */ 5 | constructor() { 6 | // Constants that characterize the system. 7 | this.gravity = 9.8; 8 | this.massCart = 1.0; 9 | this.massPole = 0.1; 10 | this.totalMass = this.massCart + this.massPole; 11 | this.cartWidth = 0.2; 12 | this.cartHeight = 0.1; 13 | this.length = select('#lengthSlider').value()/10; 14 | this.poleMoment = this.massPole * this.length; 15 | this.forceMag = select('#forceSlider').value(); 16 | this.tau = 0.02; // Seconds between state updates. 17 | 18 | // Threshold values, beyond which a simulation will be marked as failed. 19 | this.xThreshold = 2.4; 20 | this.thetaThreshold = select('#thetaSlider').value() / 360 * 2 * Math.PI; 21 | 22 | this.setRandomState(); 23 | } 24 | 25 | /** 26 | * Set the state of the cart-pole system randomly. 27 | */ 28 | setRandomState() { 29 | // The control-theory state variables of the cart-pole system. 30 | // Cart position, meters. 31 | this.x = Math.random() - 0.5; 32 | // Cart velocity. 33 | this.xDot = (Math.random() - 0.5) * 1; 34 | // Pole angle, radians. 35 | this.theta = (Math.random() - 0.5) * 2 * (6 / 360 * 2 * Math.PI); 36 | // Pole angle velocity. 37 | this.thetaDot = (Math.random() - 0.5) * 0.5; 38 | } 39 | 40 | /** 41 | * Get current state as a tf.Tensor of shape [1, 4]. 42 | */ 43 | getStateTensor() { 44 | return [this.x, this.xDot, this.theta, this.thetaDot]; 45 | } 46 | 47 | /** 48 | * Update the cart-pole system using an action. 49 | * @param {number} action Only the sign of `action` matters. 50 | * A value > 0 leads to a rightward force of a fixed magnitude. 51 | * A value <= 0 leads to a leftward force of the same fixed magnitude. 52 | */ 53 | update(action) { 54 | const force = action > 0 ? this.forceMag : -this.forceMag; 55 | 56 | const cosTheta = Math.cos(this.theta); 57 | const sinTheta = Math.sin(this.theta); 58 | 59 | const temp = 60 | (force + this.poleMoment * this.thetaDot * this.thetaDot * sinTheta) / 61 | this.totalMass; 62 | const thetaAcc = (this.gravity * sinTheta - cosTheta * temp) / 63 | (this.length * 64 | (4 / 3 - this.massPole * cosTheta * cosTheta / this.totalMass)); 65 | var xAcc = temp - this.poleMoment * thetaAcc * cosTheta / this.totalMass; 66 | 67 | // Update the four state variables, using Euler's metohd. 68 | this.x += this.tau * this.xDot; 69 | this.xDot += this.tau * xAcc; 70 | this.theta += this.tau * this.thetaDot; 71 | this.thetaDot += this.tau * thetaAcc; 72 | 73 | return this.isDone(); 74 | } 75 | 76 | /** 77 | * Determine whether this simulation is done. 78 | * 79 | * A simulation is done when `x` (position of the cart) goes out of bound 80 | * or when `theta` (angle of the pole) goes out of bound. 81 | * 82 | * @returns {bool} Whether the simulation is done. 83 | */ 84 | isDone() { 85 | return this.x < -this.xThreshold || this.x > this.xThreshold || 86 | this.theta < -this.thetaThreshold || this.theta > this.thetaThreshold; 87 | } 88 | } -------------------------------------------------------------------------------- /Examples/PoleBalancing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pole Balancing - NeatJS 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 50 | 51 | 52 | 53 |
54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
64 | 65 |
66 |
67 |

Data:

68 | Current Generation: 69 |
70 | Prev. Gen. Mean Fitness: 71 |
72 | 73 |

Options:

74 | Pole length: 5 75 |
76 | Angle Threshold: 5 77 |
78 | Cart Force: 10 79 |
80 |
81 | 82 |
83 |

Best Genome:

84 | Best Fitness: 85 |
86 |
87 |
88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Examples/PoleBalancing/player.js: -------------------------------------------------------------------------------- 1 | var maxScore = 0; 2 | var MAX_SPEED = 5; 3 | var counter = 1; 4 | 5 | //The interface between our 6 | //NeuralNetwork and the game 7 | class Player{ 8 | constructor(){ 9 | this.brain = new Genome(genomeInputsN, genomeOutputN); 10 | this.fitness; 11 | this.score = 1; 12 | this.lifespan = 0; 13 | this.dead = false; 14 | this.decisions = []; //Current Output values 15 | this.vision = []; //Current input values 16 | 17 | this.cartPole = new CartPole(); 18 | } 19 | 20 | clone() { //Returns a copy of this player 21 | let clone = new Player(this.squad); 22 | clone.brain = this.brain.clone(); 23 | return clone; 24 | } 25 | 26 | crossover(parent){ //Produce a child 27 | let child = new Player(this.squad); 28 | if(parent.fitness < this.fitness) 29 | child.brain = this.brain.crossover(parent.brain); 30 | else 31 | child.brain = parent.brain.crossover(this.brain); 32 | 33 | child.brain.mutate() 34 | return child; 35 | } 36 | 37 | 38 | //Game stuff 39 | look(){ 40 | this.vision = this.cartPole.getStateTensor(); 41 | } 42 | 43 | think(){ 44 | this.decisions = this.brain.feedForward(this.vision); 45 | } 46 | 47 | move(){ 48 | } 49 | 50 | 51 | update(){ 52 | this.lifespan++; 53 | this.cartPole.update(this.decisions[0]); 54 | 55 | if(this.cartPole.isDone() || this.lifespan > maxLifespan) 56 | this.dead = true; 57 | } 58 | 59 | show(){ 60 | renderCartPole(this.cartPole, document.getElementById("canv" + counter), counter); 61 | } 62 | 63 | 64 | 65 | calculateFitness(){ //Fitness function : adapt it to the needs of the 66 | this.fitness = this.lifespan; 67 | this.fitness /= this.cartPole.theta > 0 ? this.cartPole.theta : -this.cartPole.theta; 68 | 69 | this.score = this.fitness; 70 | } 71 | } -------------------------------------------------------------------------------- /Examples/PoleBalancing/population.js: -------------------------------------------------------------------------------- 1 | let genomeInputsN = 4; 2 | let genomeOutputN = 1; 3 | let showBest = false; 4 | let bestPlayer; 5 | let bestFitness = 0; 6 | let meanFitness = 0; 7 | //The Population Class 8 | //Here is where the power of all the classes 9 | //comes together to destroy the game score records 10 | class Population{ 11 | constructor(size){ 12 | this.population = []; 13 | 14 | this.generation = 0; 15 | this.matingPool = []; 16 | 17 | for(let i = 0; i < size; i++){ 18 | this.population.push(new Player()); 19 | this.population[i].brain.generateNetwork(); 20 | this.population[i].brain.mutate(); 21 | } 22 | } 23 | 24 | updateAlive(show){ 25 | for(let i = 0; i < this.population.length; i++){ 26 | if(!this.population[i].dead){ 27 | this.population[i].look(); 28 | this.population[i].think(); 29 | this.population[i].move(); 30 | this.population[i].update(); 31 | 32 | if(show){ 33 | this.population[i].show(); 34 | } 35 | } 36 | 37 | counter++; 38 | 39 | if(counter == 9) 40 | counter = 1; 41 | } 42 | } 43 | 44 | done(){ 45 | for(let i = 0; i < this.population.length; i++){ 46 | if(!this.population[i].dead){ 47 | return false; 48 | } 49 | } 50 | 51 | return true; 52 | } 53 | 54 | naturalSelection(){ 55 | this.calculateFitness(); 56 | 57 | let averageSum = this.getAverageScore(); 58 | meanFitness = averageSum; 59 | console.log(averageSum); 60 | let children = []; 61 | 62 | this.fillMatingPool(); 63 | for(let i = 0; i < this.population.length; i++){ 64 | let parent1 = this.selectPlayer(); 65 | let parent2 = this.selectPlayer(); 66 | if(parent1.fitness > parent2.fitness) 67 | children.push(parent1.crossover(parent2)); 68 | else 69 | children.push(parent2.crossover(parent1)); 70 | } 71 | 72 | 73 | this.population.splice(0, this.population.length); 74 | this.population = children.slice(0); 75 | this.generation++; 76 | this.population.forEach((element) => { 77 | element.brain.generateNetwork(); 78 | }); 79 | 80 | console.log("Generation " + this.generation); 81 | //console.log(this); 82 | } 83 | 84 | calculateFitness(){ 85 | let currentMax = 0; 86 | this.population.forEach((element) => { 87 | element.calculateFitness(); 88 | if(element.fitness > bestFitness){ 89 | bestFitness = element.fitness; 90 | bestPlayer = element.clone(); 91 | bestPlayer.brain.id = "BestGenome"; 92 | bestPlayer.brain.draw(200,200); 93 | } 94 | 95 | if(element.fitness > currentMax) 96 | currentMax = element.fitness; 97 | }); 98 | 99 | //Normalize 100 | this.population.forEach((element, elementN) => { 101 | element.fitness /= currentMax; 102 | }); 103 | } 104 | 105 | fillMatingPool(){ 106 | this.matingPool.splice(0, this.matingPool.length); 107 | this.population.forEach((element, elementN) => { 108 | let n = element.fitness * 100; 109 | for(let i = 0; i < n; i++) 110 | this.matingPool.push(elementN); 111 | }); 112 | } 113 | 114 | selectPlayer(){ 115 | let rand = Math.floor(Math.random() * this.matingPool.length); 116 | return this.population[this.matingPool[rand]]; 117 | } 118 | 119 | getAverageScore(){ 120 | let avSum = 0; 121 | this.population.forEach((element) => { 122 | avSum += element.score; 123 | }); 124 | 125 | return avSum / this.population.length; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Examples/PoleBalancing/sketch.js: -------------------------------------------------------------------------------- 1 | var population; 2 | var canvas; 3 | var maxLifespan = 400; 4 | 5 | function setup() { 6 | population = new Population(8); 7 | } 8 | 9 | function draw() { 10 | document.getElementById("generationN").innerHTML = population.generation; 11 | document.getElementById("bestFitness").innerHTML = bestFitness; 12 | document.getElementById("meanFit").innerHTML = meanFitness; 13 | length = select('#lengthSlider').value(); 14 | select('#length').html(length); 15 | theta = select('#thetaSlider').value(); 16 | select('#theta').html(theta); 17 | force = select('#forceSlider').value(); 18 | select('#force').html(force); 19 | 20 | if(population.done()) 21 | population.naturalSelection(); 22 | else 23 | population.updateAlive(true); 24 | } 25 | 26 | function renderCartPole(cartPole, canvas, count) { 27 | if (!canvas.style.display) { 28 | canvas.style.display = 'block'; 29 | } 30 | const X_MIN = -cartPole.xThreshold; 31 | const X_MAX = cartPole.xThreshold; 32 | const xRange = X_MAX - X_MIN; 33 | const scale = canvas.width / xRange; 34 | 35 | const context = canvas.getContext('2d'); 36 | context.clearRect(0, 0, canvas.width, canvas.height); 37 | const halfW = canvas.width / 2; 38 | 39 | // Draw the cart. 40 | const railY = canvas.height * 0.8; 41 | const cartW = cartPole.cartWidth * scale; 42 | const cartH = cartPole.cartHeight * scale; 43 | 44 | const cartX = cartPole.x * scale + halfW; 45 | 46 | context.beginPath(); 47 | context.strokeStyle = '#000000'; 48 | context.lineWidth = 2; 49 | context.rect(cartX - cartW / 2, railY - cartH / 2, cartW, cartH); 50 | context.stroke(); 51 | 52 | // Draw the wheels under the cart. 53 | const wheelRadius = cartH / 4; 54 | for (const offsetX of [-1, 1]) { 55 | context.beginPath(); 56 | context.lineWidth = 2; 57 | context.arc( 58 | cartX - cartW / 4 * offsetX, railY + cartH / 2 + wheelRadius, 59 | wheelRadius, 0, 2 * Math.PI); 60 | context.stroke(); 61 | } 62 | 63 | // Draw the pole. 64 | const angle = cartPole.theta + Math.PI / 2; 65 | const poleTopX = 66 | halfW + scale * (cartPole.x + Math.cos(angle) * cartPole.length); 67 | const poleTopY = railY - 68 | scale * (cartPole.cartHeight / 2 + Math.sin(angle) * cartPole.length); 69 | context.beginPath(); 70 | context.strokeStyle = '#ffa500'; 71 | context.lineWidth = 6; 72 | context.moveTo(cartX, railY - cartH / 2); 73 | context.lineTo(poleTopX, poleTopY); 74 | context.stroke(); 75 | 76 | // Draw the ground. 77 | const groundY = railY + cartH / 2 + wheelRadius * 2; 78 | context.beginPath(); 79 | context.strokeStyle = '#000000'; 80 | context.lineWidth = 1; 81 | context.moveTo(0, groundY); 82 | context.lineTo(canvas.width, groundY); 83 | context.stroke(); 84 | 85 | const nDivisions = 40; 86 | for (let i = 0; i < nDivisions; ++i) { 87 | const x0 = canvas.width / nDivisions * i; 88 | const x1 = x0 + canvas.width / nDivisions / 2; 89 | const y0 = groundY + canvas.width / nDivisions / 2; 90 | const y1 = groundY; 91 | context.beginPath(); 92 | context.moveTo(x0, y0); 93 | context.lineTo(x1, y1); 94 | context.stroke(); 95 | } 96 | 97 | // Draw the left and right limits. 98 | const limitTopY = groundY - canvas.height / 2; 99 | context.beginPath(); 100 | context.strokeStyle = '#ff0000'; 101 | context.lineWidth = 2; 102 | context.moveTo(1, groundY); 103 | context.lineTo(1, limitTopY); 104 | context.stroke(); 105 | context.beginPath(); 106 | context.moveTo(canvas.width - 1, groundY); 107 | context.lineTo(canvas.width - 1, limitTopY); 108 | context.stroke(); 109 | 110 | context.font = "30px Arial"; 111 | context.fillText(count, 10, 50); 112 | } -------------------------------------------------------------------------------- /Examples/SelfDriving/img/asphalt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GabrielTavernini/NeatJS/4ce41c680778386129f4129db099f2da37b5ba55/Examples/SelfDriving/img/asphalt.jpg -------------------------------------------------------------------------------- /Examples/SelfDriving/img/wireframe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GabrielTavernini/NeatJS/4ce41c680778386129f4129db099f2da37b5ba55/Examples/SelfDriving/img/wireframe.png -------------------------------------------------------------------------------- /Examples/SelfDriving/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Self Driving - NeatJs 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 44 | 45 | 46 | 47 |
48 |
49 |
50 | 51 |
52 |
53 |

Current Genome:

54 | 55 | Current Genome: 56 |
57 | Current Generation: 58 |
59 | Current Steering: 60 |
61 | Current Velocity: 62 |
63 | Current Average Velocity: 64 | 65 |

Options:

66 | Follow the Car: 67 |
68 | Malus for car crash: 69 |
70 | Exponential Fitness Function: 71 |
72 | Include velocity into Fitness: 73 |
74 |
75 | 76 |
77 |

Best Genome:

78 | 79 | Best Score: 80 |
81 | Best Fitness: 82 |
83 |
84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /Examples/SelfDriving/libs/neat/connection.js: -------------------------------------------------------------------------------- 1 | //The Connection Class 2 | //Is where all the weights are stored 3 | //Mostly used for a cleaner and more readable code. 4 | class Connection { 5 | constructor(from, to, weight){ 6 | this.fromNode = from; //type: Node 7 | this.toNode = to; //type: Node 8 | this.weight = weight; //type: Number 9 | this.enabled = true; 10 | } 11 | 12 | mutateWeight(){ //Randomly mutate the weight of this connection 13 | let rand = Math.random(); 14 | if (rand < 0.05) //5% chance of being assigned a new random value 15 | this.weight = Math.random() * 2 - 1; 16 | else //95% chance of being uniformly perturbed 17 | this.weight += p5.prototype.randomGaussian() / 50; 18 | } 19 | 20 | clone(){ //Returns a copy of this connection 21 | let clone = new Connection(this.fromNode, this.toNode, this.weight); 22 | clone.enabled = this.enabled; 23 | return clone; 24 | } 25 | 26 | getInnovationNumber(){ //Using https://en.wikipedia.org/wiki/Pairing_function#Cantor_pairing_function 27 | return (1/2)*(this.fromNode.number + this.toNode.number)*(this.fromNode.number + this.toNode.number + 1) + this.toNode.number; 28 | } 29 | } -------------------------------------------------------------------------------- /Examples/SelfDriving/libs/neat/genome.js: -------------------------------------------------------------------------------- 1 | //The Genome Class 2 | //Well.. this is the main class 3 | //This is where all the magic appends 4 | class Genome { 5 | constructor(inp, out, id, offSpring = false) { 6 | this.inputs = inp; //Number of inputs 7 | this.outputs = out; //Number of outputs 8 | this.id = id; //Genome id -> used for the drawing 9 | this.layers = 2; 10 | this.nextNode = 0; 11 | 12 | this.nodes = []; 13 | this.connections = []; 14 | 15 | if(!offSpring) { //This is not an offspring genome generate a fullyConnected net 16 | for (let i = 0; i < this.inputs; i++) { 17 | this.nodes.push(new Node(this.nextNode, 0)); 18 | this.nextNode++; 19 | } 20 | 21 | for (let i = 0; i < this.outputs; i++) { 22 | let node = new Node(this.nextNode, 1, true); 23 | this.nodes.push(node); 24 | this.nextNode++; 25 | } 26 | 27 | 28 | for (let i = 0; i < this.inputs; i++) { 29 | for (let j = this.inputs; j < this.outputs + this.inputs; j++) { 30 | let weight = Math.random() * 4 - 2; 31 | this.connections.push(new Connection(this.nodes[i], this.nodes[j], weight)); 32 | } 33 | } 34 | } 35 | } 36 | 37 | //Network Core 38 | generateNetwork() { 39 | //Clear all outputConnections in the nodes 40 | this.nodes.forEach((node) => { 41 | node.outputConnections.splice(0, node.outputConnections.length); 42 | }); 43 | 44 | //Add the connections to the Nodes 45 | this.connections.forEach((conn) => { 46 | conn.fromNode.outputConnections.push(conn); 47 | }); 48 | 49 | //Prepare for feed forward 50 | this.sortByLayer(); 51 | } 52 | 53 | feedForward(inputValues) { 54 | this.generateNetwork(); //Connect all up 55 | 56 | //Clear old inputs 57 | this.nodes.forEach((node) => { node.inputSum = 0; }); 58 | 59 | //asin new inputs 60 | for (let i = 0; i < this.inputs; i++) 61 | this.nodes[i].outputValue = inputValues[i]; 62 | 63 | //Engage all nodes and Extract the results from the outputs 64 | let result = []; 65 | this.nodes.forEach((node) => { 66 | node.engage(); 67 | 68 | if (node.output) 69 | result.push(node.outputValue); 70 | }); 71 | return result; 72 | } 73 | 74 | 75 | //Crossover 76 | crossover(partner) { 77 | //TODO: find a good way to generate unique ids 78 | let offSpring = new Genome(this.inputs, this.outputs, 0, true); //Child genome 79 | offSpring.nextNode = this.nextNode; 80 | 81 | 82 | //Take all nodes from this parent - output node activation 50%-50% 83 | for(let i = 0; i < this.nodes.length; i++){ 84 | let node = this.nodes[i].clone(); 85 | if(node.output) { 86 | let partnerNode = partner.nodes[partner.getNode(node.number)]; 87 | if(Math.random() > 0.5) { 88 | node.activationFunction = partnerNode.activationFunction; 89 | node.bias = partnerNode.bias; 90 | } 91 | } 92 | offSpring.nodes.push(node); 93 | } 94 | 95 | //Randomly take connections from this or the partner network 96 | let maxLayer = 0; 97 | for(let i = 0; i < this.connections.length; i++) { 98 | let index = this.commonConnection(this.connections[i].getInnovationNumber(), partner.connections); 99 | 100 | if(index != -1) { //There is a commonConnection 101 | let conn = Math.random() > 0.5 ? this.connections[i].clone() : partner.connections[index].clone(); 102 | 103 | //Reassign nodes 104 | let fromNode = offSpring.nodes[offSpring.getNode(conn.fromNode.number)]; 105 | let toNode = offSpring.nodes[offSpring.getNode(conn.toNode.number)]; 106 | conn.fromNode = fromNode; 107 | conn.toNode = toNode; 108 | 109 | //Add this connection to the child 110 | if(fromNode && toNode) 111 | offSpring.connections.push(conn); 112 | } 113 | else { //No common connection -> take from this 114 | let conn = this.connections[i].clone(); 115 | 116 | //Reassign nodes 117 | let fromNode = offSpring.nodes[offSpring.getNode(conn.fromNode.number)]; 118 | let toNode = offSpring.nodes[offSpring.getNode(conn.toNode.number)]; 119 | conn.fromNode = fromNode; 120 | conn.toNode = toNode; 121 | 122 | //Add this connection to the child 123 | if(fromNode && toNode) 124 | offSpring.connections.push(conn); 125 | } 126 | } 127 | 128 | offSpring.layers = this.layers; 129 | return offSpring; 130 | } 131 | 132 | 133 | 134 | //Mutation Stuff 135 | mutate() { 136 | //console.log("Mutation..."); 137 | let mut; 138 | 139 | if(Math.random() < 0.8) { //80% 140 | //MOD Connections 141 | mut = "ModConn"; 142 | //let i = Math.floor(Math.random() * this.connections.length); 143 | //this.connections[i].mutateWeight(); 144 | for (var i = 0; i < this.connections.length; i++) { 145 | this.connections[i].mutateWeight(); 146 | } 147 | } 148 | 149 | if(Math.random() < 0.5) { //50% 150 | //MOD Bias 151 | mut = "ModBias"; 152 | //let i = Math.floor(Math.random() * this.nodes.length); 153 | //this.nodes[i].mutateBias(); 154 | for (var i = 0; i < this.nodes.length; i++) { 155 | this.nodes[i].mutateBias(); 156 | } 157 | } 158 | 159 | if(Math.random() < 0.1) { //10% 160 | //MOD Node 161 | mut = "ModAct"; 162 | let i = Math.floor(Math.random() * this.nodes.length); 163 | this.nodes[i].mutateActivation(); 164 | } 165 | 166 | if(Math.random() < 0.05) { //5% 167 | //ADD Connections 168 | mut = "AddConn"; 169 | this.addConnection(); 170 | } 171 | 172 | if(Math.random() < 0.01) { //1% 173 | //ADD Node 174 | mut = "AddNode"; 175 | this.addNode(); 176 | } 177 | } 178 | 179 | addNode() { //Add a node to the network 180 | //Get a random connection to replace with a node 181 | let connectionIndex = Math.floor(Math.random() * this.connections.length); 182 | let pickedConnection = this.connections[connectionIndex]; 183 | pickedConnection.enabled = false; 184 | this.connections.splice(connectionIndex, 1); //Delete the connection 185 | 186 | //Create the new node 187 | let newNode = new Node(this.nextNode, pickedConnection.fromNode.layer + 1); 188 | this.nodes.forEach((node) => { //Shift all nodes layer value 189 | if (node.layer > pickedConnection.fromNode.layer) 190 | node.layer++; 191 | }); 192 | 193 | //New connections 194 | let newConnection1 = new Connection(pickedConnection.fromNode, newNode, 1); 195 | let newConnection2 = new Connection(newNode, pickedConnection.toNode, pickedConnection.weight); 196 | 197 | this.layers++; 198 | this.connections.push(newConnection1); //Add connection 199 | this.connections.push(newConnection2); //Add connection 200 | this.nodes.push(newNode); //Add node 201 | this.nextNode++; 202 | } 203 | 204 | addConnection() { //Add a connection to the network 205 | if (this.fullyConnected()) 206 | return; //Cannot add connections if it's fullyConnected 207 | 208 | //Choose to nodes to connect 209 | let node1 = Math.floor(Math.random() * this.nodes.length); 210 | let node2 = Math.floor(Math.random() * this.nodes.length); 211 | 212 | //Search for two valid nodes 213 | while (this.nodes[node1].layer == this.nodes[node2].layer 214 | || this.nodesConnected(this.nodes[node1], this.nodes[node2])) { 215 | node1 = Math.floor(Math.random() * this.nodes.length); 216 | node2 = Math.floor(Math.random() * this.nodes.length); 217 | } 218 | 219 | //Switch nodes based on their layer 220 | if (this.nodes[node1].layer > this.nodes[node2].layer) { 221 | let temp = node1; 222 | node1 = node2; 223 | node2 = temp; 224 | } 225 | 226 | //add the connection 227 | let newConnection = new Connection(this.nodes[node1], this.nodes[node2], Math.random() * this.inputs * Math.sqrt(2 / this.inputs)); 228 | this.connections.push(newConnection); 229 | } 230 | 231 | 232 | 233 | //Utilities 234 | commonConnection(innN, connections) { 235 | //Search through all connections to check for 236 | //one with the correct Innovation Number 237 | for(let i = 0; i < connections.length; i++){ 238 | if(innN == connections[i].getInnovationNumber()) 239 | return i; 240 | } 241 | 242 | //Found nothing 243 | return -1; 244 | } 245 | 246 | nodesConnected(node1, node2) { 247 | //Search if there is a connection between node1 & node2 248 | for (let i = 0; i < this.connections.length; i++) { 249 | let conn = this.connections[i]; 250 | if ((conn.fromNode == node1 && conn.toNode == node2) 251 | || (conn.fromNode == node2 && conn.toNode == node1)) { 252 | return true; 253 | } 254 | }; 255 | 256 | return false; 257 | } 258 | 259 | fullyConnected() { 260 | //check if the network is fully connected 261 | let maxConnections = 0; 262 | let nodesPerLayer = []; 263 | 264 | //Calculate all possible connections 265 | this.nodes.forEach((node) => { 266 | if (nodesPerLayer[node.layer] != undefined) 267 | nodesPerLayer[node.layer]++; 268 | else 269 | nodesPerLayer[node.layer] = 1; 270 | }); 271 | 272 | for (let i = 0; i < this.layers - 1; i++) 273 | for (let j = i + 1; j < this.layers; j++) 274 | maxConnections += nodesPerLayer[i] * nodesPerLayer[j]; 275 | 276 | //Compare 277 | return maxConnections == this.connections.length; 278 | } 279 | 280 | sortByLayer(){ 281 | //Sort all nodes by layer 282 | this.nodes.sort((a, b) => { 283 | return a.layer - b.layer; 284 | }); 285 | } 286 | 287 | clone() { //Returns a copy of this genome 288 | let clone = new Genome(this.inputs, this.outputs, this.id); 289 | clone.nodes = this.nodes.slice(0, this.nodes.length); 290 | clone.connections = this.connections.slice(0, this.connections.length); 291 | 292 | return clone; 293 | } 294 | 295 | getNode(x){ //Returns the index of a node with that Number 296 | for(let i = 0; i < this.nodes.length; i++) 297 | if(this.nodes[i].number == x) 298 | return i; 299 | 300 | return -1; 301 | } 302 | 303 | calculateWeight() { //Computational weight of the network 304 | return this.connections.length + this.nodes.length; 305 | } 306 | 307 | draw(svgContainer) { //Draw the genome to a svg 308 | var element = document.getElementById(this.id); 309 | if (element) 310 | element.parentNode.removeChild(element); 311 | 312 | var width = 350, 313 | height = 350; 314 | 315 | var svg = d3.select("body").append("svg") 316 | .attr("width", width) 317 | .attr("height", height) 318 | .attr("id", this.id); 319 | 320 | 321 | var force = d3.layout.force() 322 | .gravity(.05) 323 | .distance(100) 324 | .charge(-100) 325 | .size([width, height]); 326 | 327 | 328 | let connections = []; 329 | this.connections.forEach(conn => { 330 | connections.push({ source: this.getNode(conn.fromNode.number), target: this.getNode(conn.toNode.number), weight: conn.weight, enabled: conn.enabled }); 331 | }); 332 | 333 | let nodes = []; 334 | this.nodes.forEach(originalNode => { 335 | let node = originalNode.clone(); 336 | if(node.layer == 0) { 337 | node.fixed = true; 338 | node.y = height - (height * 0.2); 339 | node.x = ((width/this.inputs) * node.number) + (width/this.inputs)/3; 340 | } 341 | 342 | if(node.output) { 343 | node.fixed = true; 344 | node.y = (height * 0.2); 345 | node.x = ((width/this.outputs) * (node.number - this.inputs)) + (width/this.outputs)/2; 346 | } 347 | 348 | nodes.push(node); 349 | }); 350 | 351 | force.nodes(nodes) 352 | .links(connections) 353 | .start(); 354 | 355 | var link = svg.selectAll(".link") 356 | .data(connections) 357 | .enter().append("line") 358 | .attr("class", "link") 359 | .style("stroke-width", function (d) { return d.enabled ? (d.weight > 0 ? 0.3 + d.weight : 0.3 + d.weight*-1) : 0 }) 360 | .style("stroke", function (d) { return d.weight > 0 ? "#0f0" : "#f00"; }); 361 | 362 | var node = svg.selectAll(".node") 363 | .data(nodes) 364 | .enter().append("g") 365 | .attr("class", "node") 366 | .call(force.drag); 367 | 368 | node.append("circle") 369 | .attr("r", "5") 370 | .attr("fill", function (d) { return d.layer == 0 ? "#00f" : d.output ? "#f00" : "#000" }); 371 | 372 | node.append("text") 373 | .attr("dx", 12) 374 | .attr("dy", ".35em") 375 | .text(function(d) { return d.number + (d.layer > 0 ? "(" + activationsNames[d.activationFunction] + ")" : null) }); 376 | 377 | force.on("tick", function () { 378 | link.attr("x1", function (d) { return d.source.x; }) 379 | .attr("y1", function (d) { return d.source.y; }) 380 | .attr("x2", function (d) { return d.target.x; }) 381 | .attr("y2", function (d) { return d.target.y; }); 382 | 383 | node.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; }); 384 | }); 385 | 386 | var element = document.getElementById(this.id); 387 | document.getElementById(svgContainer).append(element); 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /Examples/SelfDriving/libs/neat/node.js: -------------------------------------------------------------------------------- 1 | var activationsNames = ["Sigmoid", "Identity", "Step", "Tanh", "ReLu"]; //Used in the svg drawing 2 | 3 | //The Node Class 4 | //This is where math appends 5 | class Node { 6 | constructor(num, lay, isOutput) { 7 | this.number = num; 8 | this.layer = lay; 9 | this.activationFunction = Math.floor(Math.random() * 5); //Number between 0 and 4 10 | this.bias = Math.random() * 2 - 1; 11 | this.output = isOutput || false; //is this node an Output node? 12 | 13 | this.inputSum = 0; 14 | this.outputValue = 0; 15 | this.outputConnections = []; 16 | } 17 | 18 | engage() { //Pass down the network the calculated output value 19 | if (this.layer != 0) //No activation function on input nodes 20 | this.outputValue = this.activation(this.inputSum + this.bias); 21 | 22 | 23 | this.outputConnections.forEach((conn) => { 24 | if (conn.enabled) //Do not pass value if connection is disabled 25 | conn.toNode.inputSum += conn.weight * this.outputValue; //Weighted output sum 26 | }); 27 | } 28 | 29 | mutateBias() { //Randomly mutate the bias of this node 30 | let rand = Math.random(); 31 | if (rand < 0.05) //5% chance of being assigned a new random value 32 | this.bias = Math.random() * 2 - 1; 33 | else //95% chance of being uniformly perturbed 34 | this.bias += p5.prototype.randomGaussian() / 50; 35 | } 36 | 37 | mutateActivation() { //Randomly choose a new activationFunction 38 | this.activationFunction = Math.floor(Math.random() * 5); //Number between 0 and 4 39 | } 40 | 41 | isConnectedTo(node) { //Check if two nodes are connected 42 | if (node.layer == this.layer) //nodes in the same layer cannot be connected 43 | return false; 44 | 45 | 46 | if (node.layer < this.layer) { //Check parameter node connections 47 | node.outputConnections.forEach((conn) => { 48 | if (conn.toNode == this) //Is Node connected to This? 49 | return true; 50 | }); 51 | } else { //Check this node connections 52 | this.outputConnections.forEach((conn) => { 53 | if (conn.toNode == node) //Is This connected to Node? 54 | return true; 55 | }); 56 | } 57 | 58 | return false; 59 | } 60 | 61 | clone() { //Returns a copy of this node 62 | let node = new Node(this.number, this.layer, this.output); 63 | node.bias = this.bias; //Same bias 64 | node.activationFunction = this.activationFunction; //Same activationFunction 65 | return node; 66 | } 67 | 68 | activation(x) { //All the possible activation Functions 69 | switch (this.activationFunction) { 70 | case 0: //Sigmoid 71 | return 1 / (1 + Math.pow(Math.E, -4.9 * x)); 72 | break; 73 | case 1: //Identity 74 | return x; 75 | break; 76 | case 2: //Step 77 | return x > 0 ? 1 : 0; 78 | break; 79 | case 3: //Tanh 80 | return Math.tanh(x); 81 | break; 82 | case 4: //ReLu 83 | return x < 0 ? 0 : x; 84 | break; 85 | default: //Sigmoid 86 | return 1 / (1 + pow(Math.E, -4.9 * x)); 87 | break; 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /Examples/SelfDriving/libs/neat/player.js: -------------------------------------------------------------------------------- 1 | //The Player Class 2 | //The interface between our 3 | //NeuralNetwork and the game 4 | class Player{ 5 | constructor(id){ 6 | this.brain = new Genome(genomeInputsN, genomeOutputN, id); 7 | this.fitness; 8 | 9 | this.score = 1; 10 | this.lifespan = 0; 11 | this.dead = false; 12 | this.decisions = []; //Current Output values 13 | this.vision = []; //Current input values 14 | } 15 | 16 | clone() { //Returns a copy of this player 17 | let clone = new Player(); 18 | clone.brain = this.brain.clone(); 19 | return clone; 20 | } 21 | 22 | crossover(parent){ //Produce a child 23 | let child = new Player(); 24 | if(parent.fitness < this.fitness) 25 | child.brain = this.brain.crossover(parent.brain); 26 | else 27 | child.brain = parent.brain.crossover(this.brain); 28 | 29 | child.brain.mutate() 30 | return child; 31 | } 32 | 33 | think(){ 34 | this.decisions = this.brain.feedForward(this.vision); 35 | } 36 | 37 | calculateFitness(){ //Fitness function : adapt it to the needs of the 38 | this.fitness = this.score; 39 | } 40 | } -------------------------------------------------------------------------------- /Examples/SelfDriving/libs/neat/population.js: -------------------------------------------------------------------------------- 1 | 2 | let genomeInputsN = 3; 3 | let genomeOutputN = 2; 4 | let showBest = true; 5 | 6 | //The Population Class 7 | //Here is where the power of all the classes 8 | //comes together to destroy the game score records 9 | class Population{ 10 | constructor(size){ 11 | this.population = []; 12 | this.bestPlayer; 13 | this.bestFitness = 0; 14 | this.bestScore = 0; 15 | 16 | this.generation = 0; 17 | this.matingPool = []; 18 | 19 | for(let i = 0; i < size; i++){ 20 | this.population.push(new Player(i)); 21 | this.population[i].brain.generateNetwork(); 22 | this.population[i].brain.mutate(); 23 | } 24 | } 25 | 26 | naturalSelection(){ 27 | this.calculateFitness(); 28 | 29 | let averageSum = this.getAverageScore(); 30 | console.log(averageSum); 31 | let children = []; 32 | 33 | this.fillMatingPool(); 34 | for(let i = 0; i < this.population.length; i++){ 35 | let parent1 = this.selectPlayer(); 36 | let parent2 = this.selectPlayer(); 37 | if(parent1.fitness > parent2.fitness) 38 | children.push(parent1.crossover(parent2)); 39 | else 40 | children.push(parent2.crossover(parent1)); 41 | } 42 | 43 | 44 | this.population.splice(0, this.population.length); 45 | this.population = children.slice(0); 46 | this.generation++; 47 | this.population.forEach((element) => { 48 | element.brain.generateNetwork(); 49 | }); 50 | 51 | console.log("Generation " + this.generation); 52 | //console.log(this); 53 | 54 | this.bestPlayer.lifespan = 0; 55 | this.bestPlayer.dead = false; 56 | this.bestPlayer.score = 1; 57 | } 58 | 59 | calculateFitness(){ 60 | let currentMax = 0; 61 | this.population.forEach((element) => { 62 | element.calculateFitness(); 63 | 64 | if(element.fitness > currentMax) 65 | currentMax = element.fitness; 66 | }); 67 | 68 | //Normalize 69 | this.population.forEach((element, elementN) => { 70 | element.fitness /= currentMax; 71 | }); 72 | } 73 | 74 | fillMatingPool(){ 75 | this.matingPool.splice(0, this.matingPool.length); 76 | this.population.forEach((element, elementN) => { 77 | let n = element.fitness * 100; 78 | for(let i = 0; i < n; i++) 79 | this.matingPool.push(elementN); 80 | }); 81 | } 82 | 83 | selectPlayer(){ 84 | let rand = Math.floor(Math.random() * this.matingPool.length); 85 | return this.population[this.matingPool[rand]]; 86 | } 87 | 88 | getAverageScore(){ 89 | let avSum = 0; 90 | this.population.forEach((element) => { 91 | avSum += element.score; 92 | }); 93 | 94 | return avSum / this.population.length; 95 | } 96 | } -------------------------------------------------------------------------------- /Examples/SelfDriving/libs/physijs_worker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var 3 | transferableMessage = self.webkitPostMessage || self.postMessage, 4 | 5 | // enum 6 | MESSAGE_TYPES = { 7 | WORLDREPORT: 0, 8 | COLLISIONREPORT: 1, 9 | VEHICLEREPORT: 2, 10 | CONSTRAINTREPORT: 3 11 | }, 12 | 13 | // temp variables 14 | _object, 15 | _vector, 16 | _transform, 17 | 18 | // functions 19 | public_functions = {}, 20 | getShapeFromCache, 21 | setShapeCache, 22 | createShape, 23 | reportWorld, 24 | reportVehicles, 25 | reportCollisions, 26 | reportConstraints, 27 | 28 | // world variables 29 | fixedTimeStep, // used when calling stepSimulation 30 | rateLimit, // sets whether or not to sync the simulation rate with fixedTimeStep 31 | last_simulation_time, 32 | last_simulation_duration = 0, 33 | world, 34 | transform, 35 | _vec3_1, 36 | _vec3_2, 37 | _vec3_3, 38 | _quat, 39 | // private cache 40 | _objects = {}, 41 | _vehicles = {}, 42 | _constraints = {}, 43 | _materials = {}, 44 | _objects_ammo = {}, 45 | _num_objects = 0, 46 | _num_wheels = 0, 47 | _num_constraints = 0, 48 | _object_shapes = {}, 49 | 50 | // The following objects are to track objects that ammo.js doesn't clean 51 | // up. All are cleaned up when they're corresponding body is destroyed. 52 | // Unfortunately, it's very difficult to get at these objects from the 53 | // body, so we have to track them ourselves. 54 | _motion_states = {}, 55 | // Don't need to worry about it for cached shapes. 56 | _noncached_shapes = {}, 57 | // A body with a compound shape always has a regular shape as well, so we 58 | // have track them separately. 59 | _compound_shapes = {}, 60 | 61 | // object reporting 62 | REPORT_CHUNKSIZE, // report array is increased in increments of this chunk size 63 | 64 | WORLDREPORT_ITEMSIZE = 14, // how many float values each reported item needs 65 | worldreport, 66 | 67 | COLLISIONREPORT_ITEMSIZE = 5, // one float for each object id, and a Vec3 contact normal 68 | collisionreport, 69 | 70 | VEHICLEREPORT_ITEMSIZE = 9, // vehicle id, wheel index, 3 for position, 4 for rotation 71 | vehiclereport, 72 | 73 | CONSTRAINTREPORT_ITEMSIZE = 6, // constraint id, offset object, offset, applied impulse 74 | constraintreport; 75 | 76 | var ab = new ArrayBuffer( 1 ); 77 | 78 | transferableMessage( ab, [ab] ); 79 | var SUPPORT_TRANSFERABLE = ( ab.byteLength === 0 ); 80 | 81 | getShapeFromCache = function ( cache_key ) { 82 | if ( _object_shapes[ cache_key ] !== undefined ) { 83 | return _object_shapes[ cache_key ]; 84 | } 85 | return null; 86 | }; 87 | 88 | setShapeCache = function ( cache_key, shape ) { 89 | _object_shapes[ cache_key ] = shape; 90 | } 91 | 92 | createShape = function( description ) { 93 | var cache_key, shape; 94 | 95 | _transform.setIdentity(); 96 | switch ( description.type ) { 97 | case 'plane': 98 | cache_key = 'plane_' + description.normal.x + '_' + description.normal.y + '_' + description.normal.z; 99 | if ( ( shape = getShapeFromCache( cache_key ) ) === null ) { 100 | _vec3_1.setX(description.normal.x); 101 | _vec3_1.setY(description.normal.y); 102 | _vec3_1.setZ(description.normal.z); 103 | shape = new Ammo.btStaticPlaneShape(_vec3_1, 0 ); 104 | setShapeCache( cache_key, shape ); 105 | } 106 | break; 107 | 108 | case 'box': 109 | cache_key = 'box_' + description.width + '_' + description.height + '_' + description.depth; 110 | if ( ( shape = getShapeFromCache( cache_key ) ) === null ) { 111 | _vec3_1.setX(description.width / 2); 112 | _vec3_1.setY(description.height / 2); 113 | _vec3_1.setZ(description.depth / 2); 114 | shape = new Ammo.btBoxShape(_vec3_1); 115 | setShapeCache( cache_key, shape ); 116 | } 117 | break; 118 | 119 | case 'sphere': 120 | cache_key = 'sphere_' + description.radius; 121 | if ( ( shape = getShapeFromCache( cache_key ) ) === null ) { 122 | shape = new Ammo.btSphereShape( description.radius ); 123 | setShapeCache( cache_key, shape ); 124 | } 125 | break; 126 | 127 | case 'cylinder': 128 | cache_key = 'cylinder_' + description.width + '_' + description.height + '_' + description.depth; 129 | if ( ( shape = getShapeFromCache( cache_key ) ) === null ) { 130 | _vec3_1.setX(description.width / 2); 131 | _vec3_1.setY(description.height / 2); 132 | _vec3_1.setZ(description.depth / 2); 133 | shape = new Ammo.btCylinderShape(_vec3_1); 134 | setShapeCache( cache_key, shape ); 135 | } 136 | break; 137 | 138 | case 'capsule': 139 | cache_key = 'capsule_' + description.radius + '_' + description.height; 140 | if ( ( shape = getShapeFromCache( cache_key ) ) === null ) { 141 | // In Bullet, capsule height excludes the end spheres 142 | shape = new Ammo.btCapsuleShape( description.radius, description.height - 2 * description.radius ); 143 | setShapeCache( cache_key, shape ); 144 | } 145 | break; 146 | 147 | case 'cone': 148 | cache_key = 'cone_' + description.radius + '_' + description.height; 149 | if ( ( shape = getShapeFromCache( cache_key ) ) === null ) { 150 | shape = new Ammo.btConeShape( description.radius, description.height ); 151 | setShapeCache( cache_key, shape ); 152 | } 153 | break; 154 | 155 | case 'concave': 156 | var i, triangle, triangle_mesh = new Ammo.btTriangleMesh; 157 | if (!description.triangles.length) return false 158 | 159 | for ( i = 0; i < description.triangles.length; i++ ) { 160 | triangle = description.triangles[i]; 161 | 162 | _vec3_1.setX(triangle[0].x); 163 | _vec3_1.setY(triangle[0].y); 164 | _vec3_1.setZ(triangle[0].z); 165 | 166 | _vec3_2.setX(triangle[1].x); 167 | _vec3_2.setY(triangle[1].y); 168 | _vec3_2.setZ(triangle[1].z); 169 | 170 | _vec3_3.setX(triangle[2].x); 171 | _vec3_3.setY(triangle[2].y); 172 | _vec3_3.setZ(triangle[2].z); 173 | 174 | triangle_mesh.addTriangle( 175 | _vec3_1, 176 | _vec3_2, 177 | _vec3_3, 178 | true 179 | ); 180 | } 181 | 182 | shape = new Ammo.btBvhTriangleMeshShape( 183 | triangle_mesh, 184 | true, 185 | true 186 | ); 187 | _noncached_shapes[description.id] = shape; 188 | break; 189 | 190 | case 'convex': 191 | var i, point, shape = new Ammo.btConvexHullShape; 192 | for ( i = 0; i < description.points.length; i++ ) { 193 | point = description.points[i]; 194 | 195 | _vec3_1.setX(point.x); 196 | _vec3_1.setY(point.y); 197 | _vec3_1.setZ(point.z); 198 | 199 | shape.addPoint(_vec3_1); 200 | 201 | } 202 | _noncached_shapes[description.id] = shape; 203 | break; 204 | 205 | case 'heightfield': 206 | 207 | var ptr = Ammo.allocate(4 * description.xpts * description.ypts, "float", Ammo.ALLOC_NORMAL); 208 | 209 | for (var f = 0; f < description.points.length; f++) { 210 | Ammo.setValue(ptr + f, description.points[f] , 'float'); 211 | } 212 | 213 | shape = new Ammo.btHeightfieldTerrainShape( 214 | description.xpts, 215 | description.ypts, 216 | ptr, 217 | 1, 218 | -description.absMaxHeight, 219 | description.absMaxHeight, 220 | 2, 221 | 0, 222 | false 223 | ); 224 | 225 | _vec3_1.setX(description.xsize/(description.xpts - 1)); 226 | _vec3_1.setY(description.ysize/(description.ypts - 1)); 227 | _vec3_1.setZ(1); 228 | 229 | shape.setLocalScaling(_vec3_1); 230 | _noncached_shapes[description.id] = shape; 231 | break; 232 | 233 | default: 234 | // Not recognized 235 | return; 236 | break; 237 | } 238 | 239 | return shape; 240 | }; 241 | 242 | public_functions.init = function( params ) { 243 | importScripts( params.ammo ); 244 | 245 | _transform = new Ammo.btTransform; 246 | _vec3_1 = new Ammo.btVector3(0,0,0); 247 | _vec3_2 = new Ammo.btVector3(0,0,0); 248 | _vec3_3 = new Ammo.btVector3(0,0,0); 249 | _quat = new Ammo.btQuaternion(0,0,0,0); 250 | 251 | REPORT_CHUNKSIZE = params.reportsize || 50; 252 | if ( SUPPORT_TRANSFERABLE ) { 253 | // Transferable messages are supported, take advantage of them with TypedArrays 254 | worldreport = new Float32Array(2 + REPORT_CHUNKSIZE * WORLDREPORT_ITEMSIZE); // message id + # of objects to report + chunk size * # of values per object 255 | collisionreport = new Float32Array(2 + REPORT_CHUNKSIZE * COLLISIONREPORT_ITEMSIZE); // message id + # of collisions to report + chunk size * # of values per object 256 | vehiclereport = new Float32Array(2 + REPORT_CHUNKSIZE * VEHICLEREPORT_ITEMSIZE); // message id + # of vehicles to report + chunk size * # of values per object 257 | constraintreport = new Float32Array(2 + REPORT_CHUNKSIZE * CONSTRAINTREPORT_ITEMSIZE); // message id + # of constraints to report + chunk size * # of values per object 258 | } else { 259 | // Transferable messages are not supported, send data as normal arrays 260 | worldreport = []; 261 | collisionreport = []; 262 | vehiclereport = []; 263 | constraintreport = []; 264 | } 265 | worldreport[0] = MESSAGE_TYPES.WORLDREPORT; 266 | collisionreport[0] = MESSAGE_TYPES.COLLISIONREPORT; 267 | vehiclereport[0] = MESSAGE_TYPES.VEHICLEREPORT; 268 | constraintreport[0] = MESSAGE_TYPES.CONSTRAINTREPORT; 269 | 270 | var collisionConfiguration = new Ammo.btDefaultCollisionConfiguration, 271 | dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration ), 272 | solver = new Ammo.btSequentialImpulseConstraintSolver, 273 | broadphase; 274 | 275 | if ( !params.broadphase ) params.broadphase = { type: 'dynamic' }; 276 | switch ( params.broadphase.type ) { 277 | case 'sweepprune': 278 | 279 | _vec3_1.setX(params.broadphase.aabbmin.x); 280 | _vec3_1.setY(params.broadphase.aabbmin.y); 281 | _vec3_1.setZ(params.broadphase.aabbmin.z); 282 | 283 | _vec3_2.setX(params.broadphase.aabbmax.x); 284 | _vec3_2.setY(params.broadphase.aabbmax.y); 285 | _vec3_2.setZ(params.broadphase.aabbmax.z); 286 | 287 | broadphase = new Ammo.btAxisSweep3( 288 | _vec3_1, 289 | _vec3_2 290 | ); 291 | 292 | break; 293 | 294 | case 'dynamic': 295 | default: 296 | broadphase = new Ammo.btDbvtBroadphase; 297 | break; 298 | } 299 | 300 | world = new Ammo.btDiscreteDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration ); 301 | 302 | fixedTimeStep = params.fixedTimeStep; 303 | rateLimit = params.rateLimit; 304 | 305 | transferableMessage({ cmd: 'worldReady' }); 306 | }; 307 | 308 | public_functions.registerMaterial = function( description ) { 309 | _materials[ description.id ] = description; 310 | }; 311 | 312 | public_functions.unRegisterMaterial = function( description ) { 313 | delete _materials[ description.id ]; 314 | }; 315 | 316 | public_functions.setFixedTimeStep = function( description ) { 317 | fixedTimeStep = description; 318 | }; 319 | 320 | public_functions.setGravity = function( description ) { 321 | _vec3_1.setX(description.x); 322 | _vec3_1.setY(description.y); 323 | _vec3_1.setZ(description.z); 324 | world.setGravity(_vec3_1); 325 | }; 326 | 327 | public_functions.addObject = function( description ) { 328 | 329 | var i, 330 | localInertia, shape, motionState, rbInfo, body; 331 | 332 | shape = createShape( description ); 333 | if (!shape) return 334 | // If there are children then this is a compound shape 335 | if ( description.children ) { 336 | var compound_shape = new Ammo.btCompoundShape, _child; 337 | compound_shape.addChildShape( _transform, shape ); 338 | 339 | for ( i = 0; i < description.children.length; i++ ) { 340 | _child = description.children[i]; 341 | 342 | var trans = new Ammo.btTransform; 343 | trans.setIdentity(); 344 | 345 | _vec3_1.setX(_child.position_offset.x); 346 | _vec3_1.setY(_child.position_offset.y); 347 | _vec3_1.setZ(_child.position_offset.z); 348 | trans.setOrigin(_vec3_1); 349 | 350 | _quat.setX(_child.rotation.x); 351 | _quat.setY(_child.rotation.y); 352 | _quat.setZ(_child.rotation.z); 353 | _quat.setW(_child.rotation.w); 354 | trans.setRotation(_quat); 355 | 356 | shape = createShape( description.children[i] ); 357 | compound_shape.addChildShape( trans, shape ); 358 | Ammo.destroy(trans); 359 | } 360 | 361 | shape = compound_shape; 362 | _compound_shapes[ description.id ] = shape; 363 | } 364 | _vec3_1.setX(0); 365 | _vec3_1.setY(0); 366 | _vec3_1.setZ(0); 367 | shape.calculateLocalInertia( description.mass, _vec3_1 ); 368 | 369 | _transform.setIdentity(); 370 | 371 | _vec3_2.setX(description.position.x); 372 | _vec3_2.setY(description.position.y); 373 | _vec3_2.setZ(description.position.z); 374 | _transform.setOrigin(_vec3_2); 375 | 376 | _quat.setX(description.rotation.x); 377 | _quat.setY(description.rotation.y); 378 | _quat.setZ(description.rotation.z); 379 | _quat.setW(description.rotation.w); 380 | _transform.setRotation(_quat); 381 | 382 | motionState = new Ammo.btDefaultMotionState( _transform ); // #TODO: btDefaultMotionState supports center of mass offset as second argument - implement 383 | rbInfo = new Ammo.btRigidBodyConstructionInfo( description.mass, motionState, shape, _vec3_1 ); 384 | 385 | if ( description.materialId !== undefined ) { 386 | rbInfo.set_m_friction( _materials[ description.materialId ].friction ); 387 | rbInfo.set_m_restitution( _materials[ description.materialId ].restitution ); 388 | } 389 | 390 | body = new Ammo.btRigidBody( rbInfo ); 391 | Ammo.destroy(rbInfo); 392 | 393 | if ( typeof description.collision_flags !== 'undefined' ) { 394 | body.setCollisionFlags( description.collision_flags ); 395 | } 396 | 397 | world.addRigidBody( body ); 398 | 399 | body.id = description.id; 400 | _objects[ body.id ] = body; 401 | _motion_states[ body.id ] = motionState; 402 | 403 | var ptr = body.a != undefined ? body.a : body.ptr; 404 | _objects_ammo[ptr] = body.id; 405 | _num_objects++; 406 | 407 | transferableMessage({ cmd: 'objectReady', params: body.id }); 408 | }; 409 | 410 | public_functions.addVehicle = function( description ) { 411 | var vehicle_tuning = new Ammo.btVehicleTuning(), 412 | vehicle; 413 | 414 | vehicle_tuning.set_m_suspensionStiffness( description.suspension_stiffness ); 415 | vehicle_tuning.set_m_suspensionCompression( description.suspension_compression ); 416 | vehicle_tuning.set_m_suspensionDamping( description.suspension_damping ); 417 | vehicle_tuning.set_m_maxSuspensionTravelCm( description.max_suspension_travel ); 418 | vehicle_tuning.set_m_maxSuspensionForce( description.max_suspension_force ); 419 | 420 | vehicle = new Ammo.btRaycastVehicle( vehicle_tuning, _objects[ description.rigidBody ], new Ammo.btDefaultVehicleRaycaster( world ) ); 421 | vehicle.tuning = vehicle_tuning; 422 | 423 | _objects[ description.rigidBody ].setActivationState( 4 ); 424 | vehicle.setCoordinateSystem( 0, 1, 2 ); 425 | 426 | world.addVehicle( vehicle ); 427 | _vehicles[ description.id ] = vehicle; 428 | }; 429 | public_functions.removeVehicle = function( description ) { 430 | delete _vehicles[ description.id ]; 431 | }; 432 | 433 | public_functions.addWheel = function( description ) { 434 | if ( _vehicles[description.id] !== undefined ) { 435 | var tuning = _vehicles[description.id].tuning; 436 | if ( description.tuning !== undefined ) { 437 | tuning = new Ammo.btVehicleTuning(); 438 | tuning.set_m_suspensionStiffness( description.tuning.suspension_stiffness ); 439 | tuning.set_m_suspensionCompression( description.tuning.suspension_compression ); 440 | tuning.set_m_suspensionDamping( description.tuning.suspension_damping ); 441 | tuning.set_m_maxSuspensionTravelCm( description.tuning.max_suspension_travel ); 442 | tuning.set_m_maxSuspensionForce( description.tuning.max_suspension_force ); 443 | } 444 | 445 | _vec3_1.setX(description.connection_point.x); 446 | _vec3_1.setY(description.connection_point.y); 447 | _vec3_1.setZ(description.connection_point.z); 448 | 449 | _vec3_2.setX(description.wheel_direction.x); 450 | _vec3_2.setY(description.wheel_direction.y); 451 | _vec3_2.setZ(description.wheel_direction.z); 452 | 453 | _vec3_3.setX(description.wheel_axle.x); 454 | _vec3_3.setY(description.wheel_axle.y); 455 | _vec3_3.setZ(description.wheel_axle.z); 456 | 457 | _vehicles[description.id].addWheel( 458 | _vec3_1, 459 | _vec3_2, 460 | _vec3_3, 461 | description.suspension_rest_length, 462 | description.wheel_radius, 463 | tuning, 464 | description.is_front_wheel 465 | ); 466 | } 467 | 468 | _num_wheels++; 469 | 470 | if ( SUPPORT_TRANSFERABLE ) { 471 | vehiclereport = new Float32Array(1 + _num_wheels * VEHICLEREPORT_ITEMSIZE); // message id & ( # of objects to report * # of values per object ) 472 | vehiclereport[0] = MESSAGE_TYPES.VEHICLEREPORT; 473 | } else { 474 | vehiclereport = [ MESSAGE_TYPES.VEHICLEREPORT ]; 475 | } 476 | }; 477 | 478 | public_functions.setSteering = function( details ) { 479 | if ( _vehicles[details.id] !== undefined ) { 480 | _vehicles[details.id].setSteeringValue( details.steering, details.wheel ); 481 | } 482 | }; 483 | public_functions.setBrake = function( details ) { 484 | if ( _vehicles[details.id] !== undefined ) { 485 | _vehicles[details.id].setBrake( details.brake, details.wheel ); 486 | } 487 | }; 488 | public_functions.applyEngineForce = function( details ) { 489 | if ( _vehicles[details.id] !== undefined ) { 490 | _vehicles[details.id].applyEngineForce( details.force, details.wheel ); 491 | } 492 | }; 493 | 494 | public_functions.removeObject = function( details ) { 495 | world.removeRigidBody( _objects[details.id] ); 496 | Ammo.destroy(_objects[details.id]); 497 | Ammo.destroy(_motion_states[details.id]); 498 | if (_compound_shapes[details.id]) Ammo.destroy(_compound_shapes[details.id]); 499 | if (_noncached_shapes[details.id]) Ammo.destroy(_noncached_shapes[details.id]); 500 | var ptr = _objects[details.id].a != undefined ? _objects[details.id].a : _objects[details.id].ptr; 501 | delete _objects_ammo[ptr]; 502 | delete _objects[details.id]; 503 | delete _motion_states[details.id]; 504 | if (_compound_shapes[details.id]) delete _compound_shapes[details.id]; 505 | if (_noncached_shapes[details.id]) delete _noncached_shapes[details.id]; 506 | _num_objects--; 507 | }; 508 | 509 | public_functions.updateTransform = function( details ) { 510 | _object = _objects[details.id]; 511 | _object.getMotionState().getWorldTransform( _transform ); 512 | 513 | if ( details.pos ) { 514 | _vec3_1.setX(details.pos.x); 515 | _vec3_1.setY(details.pos.y); 516 | _vec3_1.setZ(details.pos.z); 517 | _transform.setOrigin(_vec3_1); 518 | } 519 | 520 | if ( details.quat ) { 521 | _quat.setX(details.quat.x); 522 | _quat.setY(details.quat.y); 523 | _quat.setZ(details.quat.z); 524 | _quat.setW(details.quat.w); 525 | _transform.setRotation(_quat); 526 | } 527 | 528 | _object.setWorldTransform( _transform ); 529 | _object.activate(); 530 | }; 531 | 532 | public_functions.updateMass = function( details ) { 533 | // #TODO: changing a static object into dynamic is buggy 534 | _object = _objects[details.id]; 535 | 536 | // Per http://www.bulletphysics.org/Bullet/phpBB3/viewtopic.php?p=&f=9&t=3663#p13816 537 | world.removeRigidBody( _object ); 538 | 539 | _vec3_1.setX(0); 540 | _vec3_1.setY(0); 541 | _vec3_1.setZ(0); 542 | 543 | _object.setMassProps( details.mass, _vec3_1 ); 544 | world.addRigidBody( _object ); 545 | _object.activate(); 546 | }; 547 | 548 | public_functions.applyCentralImpulse = function ( details ) { 549 | 550 | _vec3_1.setX(details.x); 551 | _vec3_1.setY(details.y); 552 | _vec3_1.setZ(details.z); 553 | 554 | _objects[details.id].applyCentralImpulse(_vec3_1); 555 | _objects[details.id].activate(); 556 | }; 557 | 558 | public_functions.applyImpulse = function ( details ) { 559 | 560 | _vec3_1.setX(details.impulse_x); 561 | _vec3_1.setY(details.impulse_y); 562 | _vec3_1.setZ(details.impulse_z); 563 | 564 | _vec3_2.setX(details.x); 565 | _vec3_2.setY(details.y); 566 | _vec3_2.setZ(details.z); 567 | 568 | _objects[details.id].applyImpulse( 569 | _vec3_1, 570 | _vec3_2 571 | ); 572 | _objects[details.id].activate(); 573 | }; 574 | 575 | public_functions.applyTorque = function ( details ) { 576 | 577 | _vec3_1.setX(details.torque_x); 578 | _vec3_1.setY(details.torque_y); 579 | _vec3_1.setZ(details.torque_z); 580 | 581 | _objects[details.id].applyTorque( 582 | _vec3_1 583 | ); 584 | _objects[details.id].activate(); 585 | }; 586 | 587 | public_functions.applyCentralForce = function ( details ) { 588 | 589 | _vec3_1.setX(details.x); 590 | _vec3_1.setY(details.y); 591 | _vec3_1.setZ(details.z); 592 | 593 | _objects[details.id].applyCentralForce(_vec3_1); 594 | _objects[details.id].activate(); 595 | }; 596 | 597 | public_functions.applyForce = function ( details ) { 598 | 599 | _vec3_1.setX(details.force_x); 600 | _vec3_1.setY(details.force_y); 601 | _vec3_1.setZ(details.force_z); 602 | 603 | _vec3_2.setX(details.x); 604 | _vec3_2.setY(details.y); 605 | _vec3_2.setZ(details.z); 606 | 607 | _objects[details.id].applyForce( 608 | _vec3_1, 609 | _vec3_2 610 | ); 611 | _objects[details.id].activate(); 612 | }; 613 | 614 | public_functions.onSimulationResume = function( params ) { 615 | last_simulation_time = Date.now(); 616 | }; 617 | 618 | public_functions.setAngularVelocity = function ( details ) { 619 | 620 | _vec3_1.setX(details.x); 621 | _vec3_1.setY(details.y); 622 | _vec3_1.setZ(details.z); 623 | 624 | _objects[details.id].setAngularVelocity( 625 | _vec3_1 626 | ); 627 | _objects[details.id].activate(); 628 | }; 629 | 630 | public_functions.setLinearVelocity = function ( details ) { 631 | 632 | _vec3_1.setX(details.x); 633 | _vec3_1.setY(details.y); 634 | _vec3_1.setZ(details.z); 635 | 636 | _objects[details.id].setLinearVelocity( 637 | _vec3_1 638 | ); 639 | _objects[details.id].activate(); 640 | }; 641 | 642 | public_functions.setAngularFactor = function ( details ) { 643 | 644 | _vec3_1.setX(details.x); 645 | _vec3_1.setY(details.y); 646 | _vec3_1.setZ(details.z); 647 | 648 | _objects[details.id].setAngularFactor( 649 | _vec3_1 650 | ); 651 | }; 652 | 653 | public_functions.setLinearFactor = function ( details ) { 654 | 655 | _vec3_1.setX(details.x); 656 | _vec3_1.setY(details.y); 657 | _vec3_1.setZ(details.z); 658 | 659 | _objects[details.id].setLinearFactor( 660 | _vec3_1 661 | ); 662 | }; 663 | 664 | public_functions.setDamping = function ( details ) { 665 | _objects[details.id].setDamping( details.linear, details.angular ); 666 | }; 667 | 668 | public_functions.setCcdMotionThreshold = function ( details ) { 669 | _objects[details.id].setCcdMotionThreshold( details.threshold ); 670 | }; 671 | 672 | public_functions.setCcdSweptSphereRadius = function ( details ) { 673 | _objects[details.id].setCcdSweptSphereRadius( details.radius ); 674 | }; 675 | 676 | public_functions.addConstraint = function ( details ) { 677 | var constraint; 678 | 679 | switch ( details.type ) { 680 | 681 | case 'point': 682 | if ( details.objectb === undefined ) { 683 | 684 | _vec3_1.setX(details.positiona.x); 685 | _vec3_1.setY(details.positiona.y); 686 | _vec3_1.setZ(details.positiona.z); 687 | 688 | constraint = new Ammo.btPoint2PointConstraint( 689 | _objects[ details.objecta ], 690 | _vec3_1 691 | ); 692 | } else { 693 | 694 | _vec3_1.setX(details.positiona.x); 695 | _vec3_1.setY(details.positiona.y); 696 | _vec3_1.setZ(details.positiona.z); 697 | 698 | _vec3_2.setX(details.positionb.x); 699 | _vec3_2.setY(details.positionb.y); 700 | _vec3_2.setZ(details.positionb.z); 701 | 702 | constraint = new Ammo.btPoint2PointConstraint( 703 | _objects[ details.objecta ], 704 | _objects[ details.objectb ], 705 | _vec3_1, 706 | _vec3_2 707 | ); 708 | } 709 | break; 710 | 711 | case 'hinge': 712 | if ( details.objectb === undefined ) { 713 | 714 | _vec3_1.setX(details.positiona.x); 715 | _vec3_1.setY(details.positiona.y); 716 | _vec3_1.setZ(details.positiona.z); 717 | 718 | _vec3_2.setX(details.axis.x); 719 | _vec3_2.setY(details.axis.y); 720 | _vec3_2.setZ(details.axis.z); 721 | 722 | constraint = new Ammo.btHingeConstraint( 723 | _objects[ details.objecta ], 724 | _vec3_1, 725 | _vec3_2 726 | ); 727 | } else { 728 | 729 | _vec3_1.setX(details.positiona.x); 730 | _vec3_1.setY(details.positiona.y); 731 | _vec3_1.setZ(details.positiona.z); 732 | 733 | _vec3_2.setX(details.positionb.x); 734 | _vec3_2.setY(details.positionb.y); 735 | _vec3_2.setZ(details.positionb.z); 736 | 737 | _vec3_3.setX(details.axis.x); 738 | _vec3_3.setY(details.axis.y); 739 | _vec3_3.setZ(details.axis.z); 740 | 741 | constraint = new Ammo.btHingeConstraint( 742 | _objects[ details.objecta ], 743 | _objects[ details.objectb ], 744 | _vec3_1, 745 | _vec3_2, 746 | _vec3_3, 747 | _vec3_3 748 | ); 749 | } 750 | break; 751 | 752 | case 'slider': 753 | var transforma, transformb, rotation; 754 | 755 | transforma = new Ammo.btTransform(); 756 | 757 | _vec3_1.setX(details.positiona.x); 758 | _vec3_1.setY(details.positiona.y); 759 | _vec3_1.setZ(details.positiona.z); 760 | 761 | transforma.setOrigin(_vec3_1); 762 | 763 | var rotation = transforma.getRotation(); 764 | rotation.setEuler( details.axis.x, details.axis.y, details.axis.z ); 765 | transforma.setRotation( rotation ); 766 | 767 | if ( details.objectb ) { 768 | transformb = new Ammo.btTransform(); 769 | 770 | _vec3_2.setX(details.positionb.x); 771 | _vec3_2.setY(details.positionb.y); 772 | _vec3_2.setZ(details.positionb.z); 773 | 774 | transformb.setOrigin(_vec3_2); 775 | 776 | rotation = transformb.getRotation(); 777 | rotation.setEuler( details.axis.x, details.axis.y, details.axis.z ); 778 | transformb.setRotation( rotation ); 779 | 780 | constraint = new Ammo.btSliderConstraint( 781 | _objects[ details.objecta ], 782 | _objects[ details.objectb ], 783 | transforma, 784 | transformb, 785 | true 786 | ); 787 | } else { 788 | constraint = new Ammo.btSliderConstraint( 789 | _objects[ details.objecta ], 790 | transforma, 791 | true 792 | ); 793 | } 794 | 795 | Ammo.destroy(transforma); 796 | if (transformb != undefined) { 797 | Ammo.destroy(transformb); 798 | } 799 | break; 800 | 801 | case 'conetwist': 802 | var transforma, transformb; 803 | 804 | transforma = new Ammo.btTransform(); 805 | transforma.setIdentity(); 806 | 807 | transformb = new Ammo.btTransform(); 808 | transformb.setIdentity(); 809 | 810 | _vec3_1.setX(details.positiona.x); 811 | _vec3_1.setY(details.positiona.y); 812 | _vec3_1.setZ(details.positiona.z); 813 | 814 | _vec3_2.setX(details.positionb.x); 815 | _vec3_2.setY(details.positionb.y); 816 | _vec3_2.setZ(details.positionb.z); 817 | 818 | transforma.setOrigin(_vec3_1); 819 | transformb.setOrigin(_vec3_2); 820 | 821 | var rotation = transforma.getRotation(); 822 | rotation.setEulerZYX( -details.axisa.z, -details.axisa.y, -details.axisa.x ); 823 | transforma.setRotation( rotation ); 824 | 825 | rotation = transformb.getRotation(); 826 | rotation.setEulerZYX( -details.axisb.z, -details.axisb.y, -details.axisb.x ); 827 | transformb.setRotation( rotation ); 828 | 829 | constraint = new Ammo.btConeTwistConstraint( 830 | _objects[ details.objecta ], 831 | _objects[ details.objectb ], 832 | transforma, 833 | transformb 834 | ); 835 | 836 | constraint.setLimit( Math.PI, 0, Math.PI ); 837 | 838 | Ammo.destroy(transforma); 839 | Ammo.destroy(transformb); 840 | 841 | break; 842 | 843 | case 'dof': 844 | var transforma, transformb, rotation; 845 | 846 | transforma = new Ammo.btTransform(); 847 | transforma.setIdentity(); 848 | 849 | _vec3_1.setX(details.positiona.x); 850 | _vec3_1.setY(details.positiona.y); 851 | _vec3_1.setZ(details.positiona.z); 852 | 853 | transforma.setOrigin(_vec3_1 ); 854 | 855 | rotation = transforma.getRotation(); 856 | rotation.setEulerZYX( -details.axisa.z, -details.axisa.y, -details.axisa.x ); 857 | transforma.setRotation( rotation ); 858 | 859 | if ( details.objectb ) { 860 | transformb = new Ammo.btTransform(); 861 | transformb.setIdentity(); 862 | 863 | _vec3_2.setX(details.positionb.x); 864 | _vec3_2.setY(details.positionb.y); 865 | _vec3_2.setZ(details.positionb.z); 866 | 867 | transformb.setOrigin(_vec3_2); 868 | 869 | rotation = transformb.getRotation(); 870 | rotation.setEulerZYX( -details.axisb.z, -details.axisb.y, -details.axisb.x ); 871 | transformb.setRotation( rotation ); 872 | 873 | constraint = new Ammo.btGeneric6DofConstraint( 874 | _objects[ details.objecta ], 875 | _objects[ details.objectb ], 876 | transforma, 877 | transformb 878 | ); 879 | } else { 880 | constraint = new Ammo.btGeneric6DofConstraint( 881 | _objects[ details.objecta ], 882 | transforma 883 | ); 884 | } 885 | Ammo.destroy(transforma); 886 | if (transformb != undefined) { 887 | Ammo.destroy(transformb); 888 | } 889 | break; 890 | 891 | default: 892 | return; 893 | 894 | }; 895 | 896 | world.addConstraint( constraint ); 897 | 898 | constraint.enableFeedback(); 899 | _constraints[ details.id ] = constraint; 900 | _num_constraints++; 901 | 902 | if ( SUPPORT_TRANSFERABLE ) { 903 | constraintreport = new Float32Array(1 + _num_constraints * CONSTRAINTREPORT_ITEMSIZE); // message id & ( # of objects to report * # of values per object ) 904 | constraintreport[0] = MESSAGE_TYPES.CONSTRAINTREPORT; 905 | } else { 906 | constraintreport = [ MESSAGE_TYPES.CONSTRAINTREPORT ]; 907 | } 908 | }; 909 | 910 | public_functions.removeConstraint = function( details ) { 911 | var constraint = _constraints[ details.id ]; 912 | if ( constraint !== undefined ) { 913 | world.removeConstraint( constraint ); 914 | delete _constraints[ details.id ]; 915 | _num_constraints--; 916 | } 917 | }; 918 | 919 | public_functions.constraint_setBreakingImpulseThreshold = function( details ) { 920 | var constraint = _constraints[ details.id ]; 921 | if ( constraint !== undefind ) { 922 | constraint.setBreakingImpulseThreshold( details.threshold ); 923 | } 924 | }; 925 | 926 | public_functions.simulate = function simulate( params ) { 927 | if ( world ) { 928 | params = params || {}; 929 | 930 | if ( !params.timeStep ) { 931 | if ( last_simulation_time ) { 932 | params.timeStep = 0; 933 | while ( params.timeStep + last_simulation_duration <= fixedTimeStep ) { 934 | params.timeStep = ( Date.now() - last_simulation_time ) / 1000; // time since last simulation 935 | } 936 | } else { 937 | params.timeStep = fixedTimeStep; // handle first frame 938 | } 939 | } else { 940 | if ( params.timeStep < fixedTimeStep ) { 941 | params.timeStep = fixedTimeStep; 942 | } 943 | } 944 | 945 | params.maxSubSteps = params.maxSubSteps || Math.ceil( params.timeStep / fixedTimeStep ); // If maxSubSteps is not defined, keep the simulation fully up to date 946 | 947 | last_simulation_duration = Date.now(); 948 | world.stepSimulation( params.timeStep, params.maxSubSteps, fixedTimeStep ); 949 | 950 | reportVehicles(); 951 | reportCollisions(); 952 | reportConstraints(); 953 | reportWorld(); 954 | 955 | last_simulation_duration = ( Date.now() - last_simulation_duration ) / 1000; 956 | last_simulation_time = Date.now(); 957 | } 958 | }; 959 | 960 | 961 | // Constraint functions 962 | public_functions.hinge_setLimits = function( params ) { 963 | _constraints[ params.constraint ].setLimit( params.low, params.high, 0, params.bias_factor, params.relaxation_factor ); 964 | }; 965 | public_functions.hinge_enableAngularMotor = function( params ) { 966 | var constraint = _constraints[ params.constraint ]; 967 | constraint.enableAngularMotor( true, params.velocity, params.acceleration ); 968 | constraint.getRigidBodyA().activate(); 969 | if ( constraint.getRigidBodyB() ) { 970 | constraint.getRigidBodyB().activate(); 971 | } 972 | }; 973 | public_functions.hinge_disableMotor = function( params ) { 974 | _constraints[ params.constraint ].enableMotor( false ); 975 | if ( constraint.getRigidBodyB() ) { 976 | constraint.getRigidBodyB().activate(); 977 | } 978 | }; 979 | 980 | public_functions.slider_setLimits = function( params ) { 981 | var constraint = _constraints[ params.constraint ]; 982 | constraint.setLowerLinLimit( params.lin_lower || 0 ); 983 | constraint.setUpperLinLimit( params.lin_upper || 0 ); 984 | 985 | constraint.setLowerAngLimit( params.ang_lower || 0 ); 986 | constraint.setUpperAngLimit( params.ang_upper || 0 ); 987 | }; 988 | public_functions.slider_setRestitution = function( params ) { 989 | var constraint = _constraints[ params.constraint ]; 990 | constraint.setSoftnessLimLin( params.linear || 0 ); 991 | constraint.setSoftnessLimAng( params.angular || 0 ); 992 | }; 993 | public_functions.slider_enableLinearMotor = function( params ) { 994 | var constraint = _constraints[ params.constraint ]; 995 | constraint.setTargetLinMotorVelocity( params.velocity ); 996 | constraint.setMaxLinMotorForce( params.acceleration ); 997 | constraint.setPoweredLinMotor( true ); 998 | constraint.getRigidBodyA().activate(); 999 | if ( constraint.getRigidBodyB() ) { 1000 | constraint.getRigidBodyB().activate(); 1001 | } 1002 | }; 1003 | public_functions.slider_disableLinearMotor = function( params ) { 1004 | var constraint = _constraints[ params.constraint ]; 1005 | constraint.setPoweredLinMotor( false ); 1006 | if ( constraint.getRigidBodyB() ) { 1007 | constraint.getRigidBodyB().activate(); 1008 | } 1009 | }; 1010 | public_functions.slider_enableAngularMotor = function( params ) { 1011 | var constraint = _constraints[ params.constraint ]; 1012 | constraint.setTargetAngMotorVelocity( params.velocity ); 1013 | constraint.setMaxAngMotorForce( params.acceleration ); 1014 | constraint.setPoweredAngMotor( true ); 1015 | constraint.getRigidBodyA().activate(); 1016 | if ( constraint.getRigidBodyB() ) { 1017 | constraint.getRigidBodyB().activate(); 1018 | } 1019 | }; 1020 | public_functions.slider_disableAngularMotor = function( params ) { 1021 | var constraint = _constraints[ params.constraint ]; 1022 | constraint.setPoweredAngMotor( false ); 1023 | constraint.getRigidBodyA().activate(); 1024 | if ( constraint.getRigidBodyB() ) { 1025 | constraint.getRigidBodyB().activate(); 1026 | } 1027 | }; 1028 | 1029 | public_functions.conetwist_setLimit = function( params ) { 1030 | _constraints[ params.constraint ].setLimit( params.z, params.y, params.x ); // ZYX order 1031 | }; 1032 | public_functions.conetwist_enableMotor = function( params ) { 1033 | var constraint = _constraints[ params.constraint ]; 1034 | constraint.enableMotor( true ); 1035 | constraint.getRigidBodyA().activate(); 1036 | constraint.getRigidBodyB().activate(); 1037 | }; 1038 | public_functions.conetwist_setMaxMotorImpulse = function( params ) { 1039 | var constraint = _constraints[ params.constraint ]; 1040 | constraint.setMaxMotorImpulse( params.max_impulse ); 1041 | constraint.getRigidBodyA().activate(); 1042 | constraint.getRigidBodyB().activate(); 1043 | }; 1044 | public_functions.conetwist_setMotorTarget = function( params ) { 1045 | var constraint = _constraints[ params.constraint ]; 1046 | 1047 | _quat.setX(params.x); 1048 | _quat.setY(params.y); 1049 | _quat.setZ(params.z); 1050 | _quat.setW(params.w); 1051 | 1052 | constraint.setMotorTarget(_quat); 1053 | 1054 | constraint.getRigidBodyA().activate(); 1055 | constraint.getRigidBodyB().activate(); 1056 | }; 1057 | public_functions.conetwist_disableMotor = function( params ) { 1058 | var constraint = _constraints[ params.constraint ]; 1059 | constraint.enableMotor( false ); 1060 | constraint.getRigidBodyA().activate(); 1061 | constraint.getRigidBodyB().activate(); 1062 | }; 1063 | 1064 | public_functions.dof_setLinearLowerLimit = function( params ) { 1065 | var constraint = _constraints[ params.constraint ]; 1066 | 1067 | _vec3_1.setX(params.x); 1068 | _vec3_1.setY(params.y); 1069 | _vec3_1.setZ(params.z); 1070 | 1071 | constraint.setLinearLowerLimit(_vec3_1); 1072 | 1073 | constraint.getRigidBodyA().activate(); 1074 | if ( constraint.getRigidBodyB() ) { 1075 | constraint.getRigidBodyB().activate(); 1076 | } 1077 | }; 1078 | public_functions.dof_setLinearUpperLimit = function( params ) { 1079 | var constraint = _constraints[ params.constraint ]; 1080 | 1081 | _vec3_1.setX(params.x); 1082 | _vec3_1.setY(params.y); 1083 | _vec3_1.setZ(params.z); 1084 | 1085 | constraint.setLinearUpperLimit(_vec3_1); 1086 | 1087 | constraint.getRigidBodyA().activate(); 1088 | if ( constraint.getRigidBodyB() ) { 1089 | constraint.getRigidBodyB().activate(); 1090 | } 1091 | }; 1092 | public_functions.dof_setAngularLowerLimit = function( params ) { 1093 | var constraint = _constraints[ params.constraint ]; 1094 | 1095 | _vec3_1.setX(params.x); 1096 | _vec3_1.setY(params.y); 1097 | _vec3_1.setZ(params.z); 1098 | 1099 | constraint.setAngularLowerLimit(_vec3_1); 1100 | 1101 | constraint.getRigidBodyA().activate(); 1102 | if ( constraint.getRigidBodyB() ) { 1103 | constraint.getRigidBodyB().activate(); 1104 | } 1105 | }; 1106 | public_functions.dof_setAngularUpperLimit = function( params ) { 1107 | var constraint = _constraints[ params.constraint ]; 1108 | 1109 | _vec3_1.setX(params.x); 1110 | _vec3_1.setY(params.y); 1111 | _vec3_1.setZ(params.z); 1112 | 1113 | constraint.setAngularUpperLimit(_vec3_1); 1114 | 1115 | constraint.getRigidBodyA().activate(); 1116 | if ( constraint.getRigidBodyB() ) { 1117 | constraint.getRigidBodyB().activate(); 1118 | } 1119 | }; 1120 | public_functions.dof_enableAngularMotor = function( params ) { 1121 | var constraint = _constraints[ params.constraint ]; 1122 | 1123 | var motor = constraint.getRotationalLimitMotor( params.which ); 1124 | motor.set_m_enableMotor( true ); 1125 | 1126 | constraint.getRigidBodyA().activate(); 1127 | if ( constraint.getRigidBodyB() ) { 1128 | constraint.getRigidBodyB().activate(); 1129 | } 1130 | }; 1131 | public_functions.dof_configureAngularMotor = function( params ) { 1132 | var constraint = _constraints[ params.constraint ]; 1133 | 1134 | var motor = constraint.getRotationalLimitMotor( params.which ); 1135 | 1136 | motor.set_m_loLimit( params.low_angle ); 1137 | motor.set_m_hiLimit( params.high_angle ); 1138 | motor.set_m_targetVelocity( params.velocity ); 1139 | motor.set_m_maxMotorForce( params.max_force ); 1140 | 1141 | constraint.getRigidBodyA().activate(); 1142 | if ( constraint.getRigidBodyB() ) { 1143 | constraint.getRigidBodyB().activate(); 1144 | } 1145 | }; 1146 | public_functions.dof_disableAngularMotor = function( params ) { 1147 | var constraint = _constraints[ params.constraint ]; 1148 | 1149 | var motor = constraint.getRotationalLimitMotor( params.which ); 1150 | motor.set_m_enableMotor( false ); 1151 | 1152 | constraint.getRigidBodyA().activate(); 1153 | if ( constraint.getRigidBodyB() ) { 1154 | constraint.getRigidBodyB().activate(); 1155 | } 1156 | }; 1157 | 1158 | reportWorld = function() { 1159 | var index, object, 1160 | transform, origin, rotation, 1161 | offset = 0, 1162 | i = 0; 1163 | 1164 | if ( SUPPORT_TRANSFERABLE ) { 1165 | if ( worldreport.length < 2 + _num_objects * WORLDREPORT_ITEMSIZE ) { 1166 | worldreport = new Float32Array( 1167 | 2 + // message id & # objects in report 1168 | ( Math.ceil( _num_objects / REPORT_CHUNKSIZE ) * REPORT_CHUNKSIZE ) * WORLDREPORT_ITEMSIZE // # of values needed * item size 1169 | ); 1170 | worldreport[0] = MESSAGE_TYPES.WORLDREPORT; 1171 | } 1172 | } 1173 | 1174 | worldreport[1] = _num_objects; // record how many objects we're reporting on 1175 | 1176 | //for ( i = 0; i < worldreport[1]; i++ ) { 1177 | for ( index in _objects ) { 1178 | if ( _objects.hasOwnProperty( index ) ) { 1179 | object = _objects[index]; 1180 | 1181 | // #TODO: we can't use center of mass transform when center of mass can change, 1182 | // but getMotionState().getWorldTransform() screws up on objects that have been moved 1183 | //object.getMotionState().getWorldTransform( transform ); 1184 | transform = object.getCenterOfMassTransform(); 1185 | 1186 | origin = transform.getOrigin(); 1187 | rotation = transform.getRotation(); 1188 | 1189 | // add values to report 1190 | offset = 2 + (i++) * WORLDREPORT_ITEMSIZE; 1191 | 1192 | worldreport[ offset ] = object.id; 1193 | 1194 | worldreport[ offset + 1 ] = origin.x(); 1195 | worldreport[ offset + 2 ] = origin.y(); 1196 | worldreport[ offset + 3 ] = origin.z(); 1197 | 1198 | worldreport[ offset + 4 ] = rotation.x(); 1199 | worldreport[ offset + 5 ] = rotation.y(); 1200 | worldreport[ offset + 6 ] = rotation.z(); 1201 | worldreport[ offset + 7 ] = rotation.w(); 1202 | 1203 | _vector = object.getLinearVelocity(); 1204 | worldreport[ offset + 8 ] = _vector.x(); 1205 | worldreport[ offset + 9 ] = _vector.y(); 1206 | worldreport[ offset + 10 ] = _vector.z(); 1207 | 1208 | _vector = object.getAngularVelocity(); 1209 | worldreport[ offset + 11 ] = _vector.x(); 1210 | worldreport[ offset + 12 ] = _vector.y(); 1211 | worldreport[ offset + 13 ] = _vector.z(); 1212 | } 1213 | } 1214 | 1215 | 1216 | if ( SUPPORT_TRANSFERABLE ) { 1217 | transferableMessage( worldreport.buffer, [worldreport.buffer] ); 1218 | } else { 1219 | transferableMessage( worldreport ); 1220 | } 1221 | 1222 | }; 1223 | 1224 | reportCollisions = function() { 1225 | var i, offset, 1226 | dp = world.getDispatcher(), 1227 | num = dp.getNumManifolds(), 1228 | manifold, num_contacts, j, pt, 1229 | _collided = false; 1230 | 1231 | if ( SUPPORT_TRANSFERABLE ) { 1232 | if ( collisionreport.length < 2 + num * COLLISIONREPORT_ITEMSIZE ) { 1233 | collisionreport = new Float32Array( 1234 | 2 + // message id & # objects in report 1235 | ( Math.ceil( _num_objects / REPORT_CHUNKSIZE ) * REPORT_CHUNKSIZE ) * COLLISIONREPORT_ITEMSIZE // # of values needed * item size 1236 | ); 1237 | collisionreport[0] = MESSAGE_TYPES.COLLISIONREPORT; 1238 | } 1239 | } 1240 | 1241 | collisionreport[1] = 0; // how many collisions we're reporting on 1242 | 1243 | for ( i = 0; i < num; i++ ) { 1244 | manifold = dp.getManifoldByIndexInternal( i ); 1245 | 1246 | num_contacts = manifold.getNumContacts(); 1247 | if ( num_contacts === 0 ) { 1248 | continue; 1249 | } 1250 | 1251 | for ( j = 0; j < num_contacts; j++ ) { 1252 | pt = manifold.getContactPoint( j ); 1253 | //if ( pt.getDistance() < 0 ) { 1254 | offset = 2 + (collisionreport[1]++) * COLLISIONREPORT_ITEMSIZE; 1255 | collisionreport[ offset ] = _objects_ammo[ manifold.getBody0() ]; 1256 | collisionreport[ offset + 1 ] = _objects_ammo[ manifold.getBody1() ]; 1257 | 1258 | _vector = pt.get_m_normalWorldOnB(); 1259 | collisionreport[ offset + 2 ] = _vector.x(); 1260 | collisionreport[ offset + 3 ] = _vector.y(); 1261 | collisionreport[ offset + 4 ] = _vector.z(); 1262 | break; 1263 | //} 1264 | 1265 | transferableMessage( _objects_ammo ); 1266 | 1267 | } 1268 | } 1269 | 1270 | 1271 | if ( SUPPORT_TRANSFERABLE ) { 1272 | transferableMessage( collisionreport.buffer, [collisionreport.buffer] ); 1273 | } else { 1274 | transferableMessage( collisionreport ); 1275 | } 1276 | }; 1277 | 1278 | reportVehicles = function() { 1279 | var index, vehicle, 1280 | transform, origin, rotation, 1281 | offset = 0, 1282 | i = 0, j = 0; 1283 | 1284 | if ( SUPPORT_TRANSFERABLE ) { 1285 | if ( vehiclereport.length < 2 + _num_wheels * VEHICLEREPORT_ITEMSIZE ) { 1286 | vehiclereport = new Float32Array( 1287 | 2 + // message id & # objects in report 1288 | ( Math.ceil( _num_wheels / REPORT_CHUNKSIZE ) * REPORT_CHUNKSIZE ) * VEHICLEREPORT_ITEMSIZE // # of values needed * item size 1289 | ); 1290 | vehiclereport[0] = MESSAGE_TYPES.VEHICLEREPORT; 1291 | } 1292 | } 1293 | 1294 | for ( index in _vehicles ) { 1295 | if ( _vehicles.hasOwnProperty( index ) ) { 1296 | vehicle = _vehicles[index]; 1297 | 1298 | for ( j = 0; j < vehicle.getNumWheels(); j++ ) { 1299 | 1300 | //vehicle.updateWheelTransform( j, true ); 1301 | 1302 | //transform = vehicle.getWheelTransformWS( j ); 1303 | transform = vehicle.getWheelInfo( j ).get_m_worldTransform(); 1304 | 1305 | origin = transform.getOrigin(); 1306 | rotation = transform.getRotation(); 1307 | 1308 | // add values to report 1309 | offset = 1 + (i++) * VEHICLEREPORT_ITEMSIZE; 1310 | 1311 | vehiclereport[ offset ] = index; 1312 | vehiclereport[ offset + 1 ] = j; 1313 | 1314 | vehiclereport[ offset + 2 ] = origin.x(); 1315 | vehiclereport[ offset + 3 ] = origin.y(); 1316 | vehiclereport[ offset + 4 ] = origin.z(); 1317 | 1318 | vehiclereport[ offset + 5 ] = rotation.x(); 1319 | vehiclereport[ offset + 6 ] = rotation.y(); 1320 | vehiclereport[ offset + 7 ] = rotation.z(); 1321 | vehiclereport[ offset + 8 ] = rotation.w(); 1322 | 1323 | } 1324 | 1325 | } 1326 | } 1327 | 1328 | if ( j !== 0 ) { 1329 | if ( SUPPORT_TRANSFERABLE ) { 1330 | transferableMessage( vehiclereport.buffer, [vehiclereport.buffer] ); 1331 | } else { 1332 | transferableMessage( vehiclereport ); 1333 | } 1334 | } 1335 | }; 1336 | 1337 | reportConstraints = function() { 1338 | var index, constraint, 1339 | offset_body, 1340 | transform, origin, 1341 | offset = 0, 1342 | i = 0; 1343 | 1344 | if ( SUPPORT_TRANSFERABLE ) { 1345 | if ( constraintreport.length < 2 + _num_constraints * CONSTRAINTREPORT_ITEMSIZE ) { 1346 | constraintreport = new Float32Array( 1347 | 2 + // message id & # objects in report 1348 | ( Math.ceil( _num_constraints / REPORT_CHUNKSIZE ) * REPORT_CHUNKSIZE ) * CONSTRAINTREPORT_ITEMSIZE // # of values needed * item size 1349 | ); 1350 | constraintreport[0] = MESSAGE_TYPES.CONSTRAINTREPORT; 1351 | } 1352 | } 1353 | 1354 | for ( index in _constraints ) { 1355 | if ( _constraints.hasOwnProperty( index ) ) { 1356 | constraint = _constraints[index]; 1357 | offset_body = constraint.getRigidBodyA(); 1358 | transform = constraint.getFrameOffsetA(); 1359 | origin = transform.getOrigin(); 1360 | 1361 | // add values to report 1362 | offset = 1 + (i++) * CONSTRAINTREPORT_ITEMSIZE; 1363 | 1364 | constraintreport[ offset ] = index; 1365 | constraintreport[ offset + 1 ] = offset_body.id; 1366 | constraintreport[ offset + 2 ] = origin.getX(); 1367 | constraintreport[ offset + 3 ] = origin.getY(); 1368 | constraintreport[ offset + 4 ] = origin.getZ(); 1369 | constraintreport[ offset + 5 ] = constraint.getAppliedImpulse(); 1370 | } 1371 | } 1372 | 1373 | 1374 | if ( i !== 0 ) { 1375 | if ( SUPPORT_TRANSFERABLE ) { 1376 | transferableMessage( constraintreport.buffer, [constraintreport.buffer] ); 1377 | } else { 1378 | transferableMessage( constraintreport ); 1379 | } 1380 | } 1381 | 1382 | }; 1383 | 1384 | self.onmessage = function( event ) { 1385 | 1386 | if ( event.data instanceof Float32Array ) { 1387 | // transferable object 1388 | 1389 | switch ( event.data[0] ) { 1390 | case MESSAGE_TYPES.WORLDREPORT: 1391 | worldreport = new Float32Array( event.data ); 1392 | break; 1393 | 1394 | case MESSAGE_TYPES.COLLISIONREPORT: 1395 | collisionreport = new Float32Array( event.data ); 1396 | break; 1397 | 1398 | case MESSAGE_TYPES.VEHICLEREPORT: 1399 | vehiclereport = new Float32Array( event.data ); 1400 | break; 1401 | 1402 | case MESSAGE_TYPES.CONSTRAINTREPORT: 1403 | constraintreport = new Float32Array( event.data ); 1404 | break; 1405 | } 1406 | 1407 | return; 1408 | } 1409 | 1410 | if ( event.data.cmd && public_functions[event.data.cmd] ) { 1411 | //if ( event.data.params.id !== undefined && _objects[event.data.params.id] === undefined && event.data.cmd !== 'addObject' && event.data.cmd !== 'registerMaterial' ) return; 1412 | public_functions[event.data.cmd]( event.data.params ); 1413 | } 1414 | 1415 | }; 1416 | -------------------------------------------------------------------------------- /Examples/SelfDriving/libs/util.js: -------------------------------------------------------------------------------- 1 | /* desc Static function. Find point on lines nearest test point 2 | test point pXy with properties .x and .y 3 | lines defined by array aXys with nodes having properties .x and .y 4 | return is object with .x and .y properties and property i indicating nearest segment in aXys 5 | and property fFrom the fractional distance of the returned point from aXy[i-1] 6 | and property fTo the fractional distance of the returned point from aXy[i] */ 7 | 8 | 9 | function getClosestPointOnLines(pXy, aXys) { 10 | 11 | var minDist; 12 | var fTo; 13 | var fFrom; 14 | var x; 15 | var y; 16 | var i; 17 | var dist; 18 | 19 | if (aXys.length > 1) { 20 | 21 | for (var n = 1 ; n < aXys.length ; n++) { 22 | 23 | if (aXys[n].x != aXys[n - 1].x) { 24 | var a = (aXys[n].y - aXys[n - 1].y) / (aXys[n].x - aXys[n - 1].x); 25 | var b = aXys[n].y - a * aXys[n].x; 26 | dist = Math.abs(a * pXy.x + b - pXy.y) / Math.sqrt(a * a + 1); 27 | } 28 | else 29 | dist = Math.abs(pXy.x - aXys[n].x) 30 | 31 | // length^2 of line segment 32 | var rl2 = Math.pow(aXys[n].y - aXys[n - 1].y, 2) + Math.pow(aXys[n].x - aXys[n - 1].x, 2); 33 | 34 | // distance^2 of pt to end line segment 35 | var ln2 = Math.pow(aXys[n].y - pXy.y, 2) + Math.pow(aXys[n].x - pXy.x, 2); 36 | 37 | // distance^2 of pt to begin line segment 38 | var lnm12 = Math.pow(aXys[n - 1].y - pXy.y, 2) + Math.pow(aXys[n - 1].x - pXy.x, 2); 39 | 40 | // minimum distance^2 of pt to infinite line 41 | var dist2 = Math.pow(dist, 2); 42 | 43 | // calculated length^2 of line segment 44 | var calcrl2 = ln2 - dist2 + lnm12 - dist2; 45 | 46 | // redefine minimum distance to line segment (not infinite line) if necessary 47 | if (calcrl2 > rl2) 48 | dist = Math.sqrt(Math.min(ln2, lnm12)); 49 | 50 | if ((minDist == null) || (minDist > dist)) { 51 | if (calcrl2 > rl2) { 52 | if (lnm12 < ln2) { 53 | fTo = 0;//nearer to previous point 54 | fFrom = 1; 55 | } 56 | else { 57 | fFrom = 0;//nearer to current point 58 | fTo = 1; 59 | } 60 | } 61 | else { 62 | // perpendicular from point intersects line segment 63 | fTo = ((Math.sqrt(lnm12 - dist2)) / Math.sqrt(rl2)); 64 | fFrom = ((Math.sqrt(ln2 - dist2)) / Math.sqrt(rl2)); 65 | } 66 | minDist = dist; 67 | i = n; 68 | } 69 | } 70 | 71 | var dx = aXys[i - 1].x - aXys[i].x; 72 | var dy = aXys[i - 1].y - aXys[i].y; 73 | 74 | x = aXys[i - 1].x - (dx * fTo); 75 | y = aXys[i - 1].y - (dy * fTo); 76 | 77 | } 78 | 79 | return { 'x': x, 'y': y, 'i': i, 'fTo': fTo, 'fFrom': fFrom }; 80 | } -------------------------------------------------------------------------------- /Examples/SelfDriving/sketch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Physijs.scripts.worker = 'libs/physijs_worker.js'; 4 | Physijs.scripts.ammo = 'ammo.js'; 5 | 6 | var initScene, render, renderer, scene, ground, light, camera; 7 | var stats, orbit; 8 | var car, walls = [], constraints = []; 9 | var cameraFollow = true; 10 | var raycaster = new THREE.Raycaster(); 11 | raycaster.far = 75; 12 | raycaster.near = 5; 13 | var controls; 14 | 15 | //Path 16 | var points = [ 17 | {l: [0,0], r: [0,50]}, 18 | {l: [100,0], r: [75,50]}, 19 | {l: [150,50], r: [100,75]}, 20 | {l: [150,125], r: [100,150]}, 21 | {l: [175,150], r: [150,200]}, 22 | {l: [250,150], r: [285,200]}, 23 | {l: [310,50], r: [345,100]}, 24 | {l: [400,50], r: [425,100]}, 25 | {l: [450,0], r: [475,50]}, 26 | {l: [550,0], r: [500,50]}, 27 | {l: [575,25], r: [500,50]}, 28 | {l: [520,150], r: [450,150]}, 29 | {l: [520,150], r: [470,200]}, 30 | {l: [600,150], r: [625,200]}, 31 | {l: [700,50], r: [725,85]}, 32 | {l: [800,50], r: [800,85]}, 33 | ]; 34 | var path = []; 35 | for(let i = 0; i < points.length; i++) 36 | path.push({x: (points[i].l[0]+points[i].r[0])/2, y: (points[i].l[1]+points[i].r[1])/2}) 37 | 38 | 39 | var textureGround = new THREE.TextureLoader().load( "./img/asphalt.jpg" ); 40 | var textureWalls = new THREE.TextureLoader().load( "./img/wireframe.png" ); 41 | 42 | textureGround.wrapS = THREE.RepeatWrapping; 43 | textureGround.wrapT = THREE.RepeatWrapping; 44 | textureGround.repeat.set( 4, 4 ); 45 | 46 | textureWalls.wrapS = THREE.RepeatWrapping; 47 | textureWalls.wrapT = THREE.RepeatWrapping; 48 | textureWalls.repeat.set( 4, 4 ); 49 | 50 | var width = 800;//800; 51 | var height = 700;//800; 52 | var groundWidth = 800; 53 | var groundHeight = 200; 54 | 55 | var gravity = -290; 56 | 57 | var wheel_mass = 100; 58 | var wheel_front_friction = 1.5; 59 | var wheel_front_restitution = .3; 60 | var wheel_rear_friction = 1.5; 61 | var wheel_rear_restitution = .3; 62 | 63 | var body_mass = 2000; 64 | 65 | 66 | //Neat Stuff 67 | var population = new Population(20); 68 | var vision = []; 69 | var playerCounter = 0; 70 | var deltaLifespan = 0; 71 | var maxLifespan = 200; 72 | var prevPosition = []; 73 | var mediumVelocity = 0; 74 | var sumCounter = 0; 75 | var velocitySum = 0; 76 | 77 | initScene = function () { 78 | 79 | population.population[playerCounter].brain.id = "currentGenome"; 80 | population.population[playerCounter].brain.draw("currentSvgContainer"); 81 | 82 | // AmbientLight 83 | var ambient = new THREE.AmbientLight(0xFFFFFF,2); 84 | scene.add(ambient); 85 | 86 | createGround(); 87 | car = new createCar(); 88 | car.rotation.order = "YXZ" 89 | 90 | if(cameraFollow) { 91 | camera.position.set(0, 300, 0); 92 | camera.lookAt(new THREE.Vector3(30, 0, -20)); 93 | } 94 | 95 | 96 | controls = new function () { 97 | this.velocity = 0; 98 | this.wheelAngle = 0; 99 | 100 | this.changeVelocity = function () { 101 | // if you add a motor, the current constraint is overridden 102 | // if you want to rotate set min higher then max 103 | car.userData.rlConstraint.configureAngularMotor(2, 0, -1, controls.velocity, 20000); 104 | car.userData.rrConstraint.configureAngularMotor(2, 0, -1, controls.velocity, 20000); 105 | 106 | }; 107 | 108 | this.changeOrientation = function () { 109 | // backwheels don't move themselves and are restriced in their 110 | // movement. They should be able to rotate along the z-axis 111 | // same here, if the complete angle is allowed set lower higher 112 | // than upper. 113 | // by setting the lower and upper to the same value you can 114 | // fix the position 115 | // we can set the x position to 'loosen' the axis for the directional 116 | car.userData.frConstraint.setAngularLowerLimit({x: 0, y: controls.wheelAngle, z: 2}); 117 | car.userData.frConstraint.setAngularUpperLimit({x: 0, y: controls.wheelAngle, z: 0}); 118 | car.userData.flConstraint.setAngularLowerLimit({x: 0, y: controls.wheelAngle, z: 2}); 119 | car.userData.flConstraint.setAngularUpperLimit({x: 0, y: controls.wheelAngle, z: 0}); 120 | } 121 | }; 122 | 123 | var pressed = {}; 124 | document.addEventListener('keydown', function(e) { 125 | var key = e.keyCode; 126 | pressed[key] = true; 127 | 128 | orbit.enabled = false; 129 | 130 | if (key === 38) { // up 131 | controls.velocity = -20; 132 | controls.changeVelocity(); 133 | } 134 | else if (key === 40) { // down 135 | controls.velocity = 20; 136 | controls.changeVelocity(); 137 | } 138 | else if (key === 37) { // left 139 | controls.wheelAngle = +.4; 140 | controls.changeOrientation(); 141 | } 142 | else if (key === 39) { // right 143 | controls.wheelAngle = -.4; 144 | controls.changeOrientation(); 145 | } 146 | if (key === 32) { // space 147 | // https://github.com/chandlerprall/Physijs/wiki/Updating-an-object's-position-&-rotation 148 | car.setLinearVelocity(new THREE.Vector3(0, 0, 0)); 149 | car.setAngularVelocity(new THREE.Vector3(0, 0, 0)); 150 | 151 | car.position.y = 20; 152 | car.__dirtyPosition = true; 153 | 154 | car.rotation.x += Math.PI; 155 | car.rotation.y += Math.PI; 156 | car.rotation.z += 0; 157 | car.__dirtyRotation = true; 158 | } 159 | }) 160 | 161 | document.addEventListener('keyup', function(e) { 162 | var key = e.keyCode; 163 | pressed[key] = false; 164 | 165 | orbit.enabled = true; 166 | 167 | if (!pressed[38] && !pressed[40]) { 168 | controls.velocity = 0; 169 | controls.changeVelocity(); 170 | } 171 | if (!pressed[37] && !pressed[39]) { 172 | controls.wheelAngle = 0; 173 | controls.changeOrientation(); 174 | } 175 | }) 176 | 177 | 178 | 179 | 180 | controls.changeVelocity(); 181 | controls.changeOrientation(); 182 | }; 183 | 184 | function createCar() { 185 | 186 | function createWheel(position, friction, restitution) { 187 | var mat = Physijs.createMaterial(new THREE.MeshLambertMaterial({color: 0x111111 }), friction, restitution); 188 | var geo = new THREE.CylinderGeometry(3, 3, 2, 32); 189 | var mesh = new Physijs.CylinderMesh(geo, mat, wheel_mass); 190 | 191 | mesh.rotation.x = Math.PI / 2; 192 | mesh.castShadow = true; 193 | mesh.position.copy(position); 194 | return mesh; 195 | } 196 | 197 | var x = -groundWidth/2 + 25; 198 | var z = -groundHeight/2 + 25; 199 | // create the car body 200 | var geom = new THREE.BoxGeometry(15, 4, 4); 201 | var mat = new THREE.MeshLambertMaterial({ color: 0xFF0000 }); 202 | var body = new Physijs.BoxMesh(geom, mat, body_mass); 203 | var mat2 = new THREE.MeshLambertMaterial({ opacity: 0, transparent: true }); 204 | var geom2 = new THREE.BoxGeometry(15, 2, 4); 205 | var body2 = new Physijs.BoxMesh(geom2, mat2, 0); 206 | var geom3 = new THREE.BoxGeometry(17, 4, 13); 207 | var body3 = new Physijs.BoxMesh(geom3, mat2, 0); 208 | body.position.set(x, 5, z); 209 | body2.position.set(0, 3, 0); 210 | body3.position.set(0, 3, 0); 211 | body2.add(body3); 212 | body.add(body2); 213 | scene.add(body); 214 | 215 | body.addEventListener( 'collision', function() { 216 | restart(true); 217 | }); 218 | 219 | // create the wheels 220 | var fr = createWheel(new THREE.Vector3(x+5, 4, z+5), wheel_rear_friction, wheel_rear_restitution); 221 | var fl = createWheel(new THREE.Vector3(x+5, 4, z-5), wheel_rear_friction, wheel_rear_restitution); 222 | var rr = createWheel(new THREE.Vector3(x-5, 4, z+5), wheel_front_friction, wheel_front_restitution); 223 | var rl = createWheel(new THREE.Vector3(x-5, 4, z-5), wheel_front_friction, wheel_front_restitution); 224 | 225 | // add the wheels to the scene 226 | scene.add(fr); 227 | scene.add(fl); 228 | scene.add(rr); 229 | scene.add(rl); 230 | 231 | var frConstraint = new Physijs.DOFConstraint(fr, body, new THREE.Vector3(x+5, 4, z+3)); 232 | scene.addConstraint(frConstraint); 233 | 234 | var flConstraint = new Physijs.DOFConstraint(fl, body, new THREE.Vector3(x+5, 4, z-3)); 235 | scene.addConstraint(flConstraint); 236 | 237 | var rrConstraint = new Physijs.DOFConstraint(rr, body, new THREE.Vector3(x-5, 4, z+3)); 238 | scene.addConstraint(rrConstraint); 239 | 240 | var rlConstraint = new Physijs.DOFConstraint(rl, body, new THREE.Vector3(x-5, 4, z-3)); 241 | scene.addConstraint(rlConstraint); 242 | 243 | constraints.push(frConstraint); 244 | constraints.push(flConstraint); 245 | constraints.push(rrConstraint); 246 | constraints.push(rlConstraint); 247 | 248 | 249 | // front wheels should only move along the z axis. 250 | // we don't need to specify anything here, since 251 | // that value is overridden by the motors 252 | rrConstraint.setAngularLowerLimit({x: 0, y: 0, z: 0}); 253 | rrConstraint.setAngularUpperLimit({x: 0, y: 0, z: 0}); 254 | rlConstraint.setAngularLowerLimit({x: 0, y: 0, z: 0}); 255 | rlConstraint.setAngularUpperLimit({x: 0, y: 0, z: 0}); 256 | 257 | // motor two is forward and backwards 258 | rlConstraint.enableAngularMotor(2); 259 | rrConstraint.enableAngularMotor(2); 260 | 261 | body.userData.rlConstraint = rlConstraint; 262 | body.userData.rrConstraint = rrConstraint; 263 | body.userData.flConstraint = flConstraint; 264 | body.userData.frConstraint = frConstraint; 265 | 266 | return body; 267 | } 268 | 269 | function createGround() { 270 | var width = groundWidth; 271 | var height = groundHeight; 272 | 273 | 274 | // Materials 275 | var mat = Physijs.createMaterial(new THREE.MeshPhongMaterial({ map: textureGround, color: 0x333333}), 0.6, 0); 276 | 277 | // Ground 278 | var geo = new THREE.BoxGeometry(width, 5, height); 279 | ground = new Physijs.BoxMesh(geo, mat, 0 /* mass */ ); 280 | 281 | mat = Physijs.createMaterial(new THREE.MeshPhongMaterial({ color: 0x111111}), 0.6, 0); 282 | var borderLeft = new Physijs.BoxMesh(new THREE.BoxGeometry(5, 20, height), mat, 0); 283 | borderLeft.position.x = -width / 2 - 1; 284 | ground.add(borderLeft); 285 | 286 | var borderRight = new Physijs.BoxMesh(new THREE.BoxGeometry(5, 20, height), mat, 0); 287 | borderRight.position.x = width / 2 + 1; 288 | ground.add(borderRight); 289 | 290 | var borderBottom = new Physijs.BoxMesh(new THREE.BoxGeometry(width, 20, 5), mat, 0); 291 | borderBottom.position.z = height / 2 + 1; 292 | ground.add(borderBottom); 293 | 294 | var borderTop = new Physijs.BoxMesh(new THREE.BoxGeometry(width, 20, 5), mat, 0); 295 | borderTop.position.z = -height / 2 - 1; 296 | ground.add(borderTop); 297 | 298 | 299 | 300 | for(let i = 0; i < points.length -1; i++) { 301 | var material = new THREE.MeshBasicMaterial( {map: textureWalls, color: 0x929293, clipIntersection: true} ); 302 | let a = points[i].l[0] - points[i+1].l[0]; 303 | let b = points[i+1].l[1] - points[i].l[1]; 304 | let dist = Math.sqrt(a*a + b*b); 305 | var geometry = new THREE.BoxGeometry(dist,30,5); 306 | let mesh = new Physijs.BoxMesh(geometry, material); 307 | mesh.position.x = (points[i].l[0] + points[i+1].l[0])/2 - groundWidth/2; 308 | mesh.position.z = (points[i].l[1] + points[i+1].l[1])/2 - groundHeight/2; 309 | mesh.position.y = 14; 310 | mesh.rotation.y = Math.atan2(b, a); 311 | 312 | ground.add(mesh); 313 | walls.push(mesh); 314 | 315 | let a2 = points[i].r[0] - points[i+1].r[0]; 316 | let b2 = points[i+1].r[1] - points[i].r[1]; 317 | let dist2 = Math.sqrt(a2*a2 + b2*b2); 318 | var geometry2 = new THREE.BoxGeometry(dist2,30,5); 319 | let mesh2 = new Physijs.BoxMesh(geometry2, material); 320 | mesh2.position.x = (points[i].r[0] + points[i+1].r[0])/2 - groundWidth/2; 321 | mesh2.position.z = (points[i].r[1] + points[i+1].r[1])/2 - groundHeight/2; 322 | mesh2.position.y = 14; 323 | mesh2.rotation.y = Math.atan2(b2, a2); 324 | 325 | ground.add(mesh2); 326 | walls.push(mesh2); 327 | } 328 | scene.add(ground); 329 | } 330 | 331 | function restart(crash = false) { 332 | 333 | let x = car.position.x + groundWidth/2; 334 | let z = car.position.z + groundHeight/2; 335 | let result = getClosestPointOnLines({x:x, y:z}, path); 336 | 337 | let dist = 0; 338 | for(let i = 1; i < result.i; i++) { 339 | let segDist = Math.sqrt(Math.pow(path[i-1].x-path[i].x, 2) + Math.pow(path[i-1].y-path[i].y, 2)); 340 | segDist *= segDist < 0 ? -1 : 1; 341 | dist += segDist; 342 | } 343 | 344 | 345 | let additionalDist = Math.sqrt(Math.pow(path[result.i - 1].x-result.x, 2) + Math.pow(path[result.i - 1].y-result.y, 2)); 346 | additionalDist *= additionalDist < 0 ? -1 : 1; 347 | dist += additionalDist; 348 | 349 | let score = dist;//Math.sqrt(Math.pow(car.position.x + groundWidth/2, 2) + Math.pow(car.position.z + groundHeight/2, 2)); 350 | population.population[playerCounter].score = score; 351 | if(document.getElementById("expCheckbox").checked) 352 | population.population[playerCounter].fitness = Math.pow(score, 2); 353 | else 354 | population.population[playerCounter].fitness = score; 355 | 356 | 357 | if(crash && document.getElementById("crashCheckbox").checked) { 358 | population.population[playerCounter].score *= 0.75; 359 | population.population[playerCounter].fitness *= 0.75; 360 | } 361 | 362 | if(document.getElementById("velocityCheckbox").checked) { 363 | if(mediumVelocity > 0) { 364 | population.population[playerCounter].score *= mediumVelocity/20; 365 | population.population[playerCounter].fitness *= mediumVelocity/20; 366 | } 367 | } 368 | 369 | 370 | if(population.population[playerCounter].fitness > population.bestFitness){ 371 | population.bestScore = population.population[playerCounter].score; 372 | population.bestFitness = population.population[playerCounter].fitness; 373 | population.bestPlayer = population.population[playerCounter].clone(); 374 | population.bestPlayer.brain.id = "BestGenome"; 375 | population.bestPlayer.brain.draw("bestSvgContainer"); 376 | } 377 | 378 | 379 | vision = []; 380 | deltaLifespan = 0; 381 | prevPosition = []; 382 | mediumVelocity = 0; 383 | sumCounter = 0; 384 | velocitySum = 0; 385 | console.log("Genome: " + playerCounter + " - Score: " + population.population[playerCounter].score); 386 | 387 | playerCounter++; 388 | if(playerCounter >= population.population.length){ 389 | population.naturalSelection(); 390 | playerCounter = 0; 391 | maxLifespan += 30; 392 | } 393 | 394 | while(scene.children.length > 0) 395 | scene.remove(scene.children[0]); 396 | 397 | for(let i = 0; i < constraints.length; i++) 398 | scene.removeConstraint(constraints[i]); 399 | 400 | scene.collisions = {}; 401 | constraints = []; 402 | walls = []; 403 | 404 | initScene(); 405 | }; 406 | 407 | render = function () { 408 | mediumVelocity = velocitySum / sumCounter; 409 | document.getElementById("genomeN").innerHTML = playerCounter; 410 | document.getElementById("generationN").innerHTML = population.generation; 411 | document.getElementById("bestFitness").innerHTML = population.bestFitness; 412 | document.getElementById("bestScore").innerHTML = population.bestScore; 413 | document.getElementById("mediumV").innerHTML = mediumVelocity; 414 | 415 | cameraFollow = document.getElementById("followCheckbox").checked; 416 | if(cameraFollow) { 417 | camera.position.set(car.position.x + Math.sin(car.rotation.y - Math.PI/2)*75, 60, car.position.z + Math.cos(car.rotation.y - Math.PI/2)*75); 418 | var pos = car.position.clone(); 419 | pos.y += 10; 420 | camera.lookAt(pos); 421 | } 422 | 423 | 424 | 425 | //------------------------------------------------------------------------------------------ 426 | //--------------------------------Collect Vision Data--------------------------------------- 427 | //------------------------------------------------------------------------------------------ 428 | 429 | vision = []; 430 | //Straight 431 | var position = new THREE.Vector3( car.position.x, 0, car.position.z); 432 | var direction = new THREE.Vector3(Math.sin(car.rotation.y + Math.PI/2), 0, Math.cos(car.rotation.y + Math.PI/2)); 433 | direction.normalize(); 434 | raycaster.set( position, direction); 435 | var intersects = raycaster.intersectObjects( walls ); 436 | var selectedObject = scene.getObjectByName("line"); 437 | if(selectedObject) 438 | scene.remove( selectedObject ); 439 | 440 | if(intersects.length > 0) { 441 | var material = new THREE.LineBasicMaterial( { color: 0xFF0000 } ); 442 | var geometry = new THREE.Geometry(); 443 | geometry.vertices.push(position); 444 | geometry.vertices.push(intersects[0].point); 445 | var line = new THREE.Line( geometry, material ); 446 | line.position.y = car.position.y; 447 | line.name = "line"; 448 | scene.add(line); 449 | 450 | vision.push(p5.prototype.map(intersects[0].distance, 0, raycaster.far, 1, 0)); 451 | } else{ 452 | vision.push(0); 453 | } 454 | 455 | 456 | //45 degree right 457 | direction = new THREE.Vector3(Math.sin(car.rotation.y + Math.PI/4), 0, Math.cos(car.rotation.y + Math.PI/4)); 458 | direction.normalize(); 459 | raycaster.set( position, direction); 460 | var intersects = raycaster.intersectObjects( walls ); 461 | var selectedObject = scene.getObjectByName("line2"); 462 | if(selectedObject) 463 | scene.remove( selectedObject ); 464 | 465 | if(intersects.length > 0) { 466 | var material = new THREE.LineBasicMaterial( { color: 0x00FF00 } ); 467 | var geometry = new THREE.Geometry(); 468 | geometry.vertices.push(position); 469 | geometry.vertices.push(intersects[0].point); 470 | var line = new THREE.Line( geometry, material ); 471 | line.position.y = car.position.y; 472 | line.name = "line2"; 473 | scene.add(line); 474 | 475 | vision.push(p5.prototype.map(intersects[0].distance, 0, raycaster.far, 1, 0)); 476 | } else{ 477 | vision.push(0); 478 | } 479 | 480 | 481 | //45 degree left 482 | direction = new THREE.Vector3(Math.sin(car.rotation.y + 3*Math.PI/4), 0, Math.cos(car.rotation.y + 3*Math.PI/4)); 483 | direction.normalize(); 484 | raycaster.set( position, direction); 485 | var intersects = raycaster.intersectObjects( walls ); 486 | var selectedObject = scene.getObjectByName("line3"); 487 | if(selectedObject) 488 | scene.remove( selectedObject ); 489 | 490 | if(intersects.length > 0) { 491 | var material = new THREE.LineBasicMaterial( { color: 0x0000ff } ); 492 | var geometry = new THREE.Geometry(); 493 | geometry.vertices.push(position); 494 | geometry.vertices.push(intersects[0].point); 495 | var line = new THREE.Line( geometry, material ); 496 | line.position.y = car.position.y; 497 | line.name = "line3"; 498 | scene.add(line); 499 | 500 | vision.push(p5.prototype.map(intersects[0].distance, 0, raycaster.far, 1, 0)); 501 | } else{ 502 | vision.push(0); 503 | } 504 | 505 | 506 | //------------------------------------------------------------------------------------------ 507 | //---------------------------------FeedForward & Move--------------------------------------- 508 | //------------------------------------------------------------------------------------------ 509 | 510 | population.population[playerCounter].vision = vision.slice(0, vision.length); 511 | population.population[playerCounter].think(); 512 | var decisions = population.population[playerCounter].decisions; 513 | 514 | decisions[0] *= 20; 515 | controls.velocity = decisions[0] < 20 && decisions[0] > -20 ? -decisions[0] : decisions[0] < 0 ? 20 : -20;//-decisions[0]*10; 516 | sumCounter ++; 517 | velocitySum += -controls.velocity; 518 | document.getElementById("currentV").innerHTML = -controls.velocity; 519 | controls.changeVelocity(); 520 | 521 | controls.wheelAngle = decisions[1] < .4 && decisions[1] > -.4 ? decisions[1] : decisions[1] < 0 ? -.4 : .4; 522 | document.getElementById("currentS").innerHTML = controls.wheelAngle; 523 | controls.changeOrientation(); 524 | 525 | renderer.render(scene, camera); 526 | scene.simulate(undefined, 2); 527 | requestAnimationFrame(render); 528 | 529 | 530 | //Overtime 531 | if(prevPosition.length < 2) 532 | prevPosition = [car.position.x, car.position.z]; 533 | else if(Math.sqrt(Math.pow(prevPosition[0] - car.position.x, 2) + Math.pow(prevPosition[1] - car.position.z, 2)) < 5) { 534 | if(deltaLifespan > 75) { 535 | deltaLifespan = 0; 536 | restart(); 537 | } 538 | deltaLifespan++; 539 | } else { 540 | deltaLifespan = 0; 541 | prevPosition = [car.position.x, car.position.z]; 542 | } 543 | }; 544 | 545 | 546 | //------------------------------------------------------------------------------------------ 547 | //----------------------------------------Start--------------------------------------------- 548 | //------------------------------------------------------------------------------------------ 549 | 550 | window.onload = () => { 551 | renderer = new THREE.WebGLRenderer({ antialias: false }); 552 | renderer.setSize(width, height); 553 | renderer.setClearColor(new THREE.Color(0x999999)); 554 | renderer.shadowMap.enabled = true; 555 | renderer.shadowMap.type = THREE.PCFSoftShadowMap; 556 | 557 | document.getElementById('viewport').appendChild(renderer.domElement); 558 | 559 | scene = new Physijs.Scene({reportSize: 10, fixedTimeStep: 1 / 60}); 560 | scene.setGravity(new THREE.Vector3(0, gravity, 0)); 561 | 562 | camera = new THREE.PerspectiveCamera(45, width/height, 1, 1000); 563 | scene.add(camera); 564 | 565 | orbit = new THREE.OrbitControls(camera, renderer.domElement); 566 | orbit.enableDamping = true; 567 | orbit.dampingFactor = 1; 568 | 569 | initScene(); 570 | render(); 571 | scene.simulate(); 572 | } 573 | -------------------------------------------------------------------------------- /Examples/TargetSeeking/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Target Seeking - NeatJs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 38 | 39 | 40 | 41 |
42 |
43 |
44 | 45 |
46 |
47 |

Wins per Color:

48 | Red: 49 |
50 | Green: 51 |
52 | Blue: 53 |
54 | Yellow: 55 |
56 | Current Generation: 57 |
58 | 59 |

Options:

60 | Speed: 1 61 |
62 | Moving Target: 63 |
64 | 65 |
66 |

Best Genome:

67 | Best Fitness: 68 |
69 |
70 |
71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Examples/TargetSeeking/player.js: -------------------------------------------------------------------------------- 1 | var maxScore = 0; 2 | var MAX_SPEED = 5; 3 | 4 | //The interface between our 5 | //NeuralNetwork and the game 6 | class Player{ 7 | constructor(squad){ 8 | this.brain = new Genome(genomeInputsN, genomeOutputN); 9 | this.fitness; 10 | this.score = 1; 11 | this.lifespan = 0; 12 | this.dead = false; 13 | this.decisions = []; //Current Output values 14 | this.vision = []; //Current input values 15 | 16 | 17 | //Game Stuff 18 | switch(squad) { 19 | case 0: 20 | this.x = 20; 21 | this.y = 20; 22 | break; 23 | case 1: 24 | this.x = 20; 25 | this.y = height - 20; 26 | break; 27 | case 2: 28 | this.x = width - 20; 29 | this.y = 20; 30 | break; 31 | case 3: 32 | this.x = width - 20; 33 | this.y = height - 20; 34 | break; 35 | } 36 | 37 | this.vx = 0; 38 | this.vy = 0; 39 | 40 | this.radius = 7; 41 | this.squad = squad; 42 | } 43 | 44 | clone() { //Returns a copy of this player 45 | let clone = new Player(this.squad); 46 | clone.brain = this.brain.clone(); 47 | return clone; 48 | } 49 | 50 | crossover(parent){ //Produce a child 51 | let child = new Player(this.squad); 52 | if(parent.fitness < this.fitness) 53 | child.brain = this.brain.crossover(parent.brain); 54 | else 55 | child.brain = parent.brain.crossover(this.brain); 56 | 57 | child.brain.mutate() 58 | return child; 59 | } 60 | 61 | 62 | //Game stuff 63 | look(){ 64 | //Look and normalize 65 | var dist = Math.sqrt(this.x, this.y, target.x, target.y) / Math.sqrt(width**2 + height**2); 66 | var targetAngle = this.angleToPoint(this.x, this.y, target.x, target.y) / Math.PI * 2; 67 | var vx = (this.vx + MAX_SPEED) / MAX_SPEED; 68 | var vy = (this.vy + MAX_SPEED) / MAX_SPEED; 69 | 70 | // NaN checking 71 | targetAngle = isNaN(targetAngle) ? 0 : targetAngle; 72 | dist = isNaN(dist) ? 0 : dist; 73 | this.vision = [vx, vy, dist, targetAngle]; 74 | } 75 | 76 | think(){ 77 | this.decisions = this.brain.feedForward(this.vision); 78 | } 79 | 80 | move(){ 81 | var moveAngle = this.decisions[0] * 2 * Math.PI; 82 | 83 | // Calculate next position 84 | let ax = Math.cos(moveAngle); 85 | let ay = Math.sin(moveAngle); 86 | this.vx += ax; 87 | this.vy += ay; 88 | 89 | // Limit speeds to maximum speed 90 | this.vx = this.vx > MAX_SPEED ? MAX_SPEED : this.vx < -MAX_SPEED ? -MAX_SPEED : this.vx; 91 | this.vy = this.vy > MAX_SPEED ? MAX_SPEED : this.vy < -MAX_SPEED ? -MAX_SPEED : this.vy; 92 | 93 | this.x += this.vx; 94 | this.y += this.vy; 95 | 96 | // Limit position to width and height 97 | this.x = this.x >= width ? width : this.x <= 0 ? 0 : this.x; 98 | this.y = this.y >= height ? height : this.y <= 0 ? 0 : this.y; 99 | 100 | //Change direction against walls 101 | if(this.x == 0 || this.x == width) this.vx = -this.vx; 102 | if(this.y == 0 || this.y == height) this.vy = -this.vy; 103 | } 104 | 105 | 106 | update(){ 107 | let d = dist(this.x, this.y, target.x, target.y); 108 | if(d < 100) 109 | this.score += (100 - d); 110 | 111 | if(this.score > maxScore) 112 | maxScore = this.score; 113 | 114 | 115 | if(this.lifespan > maxLifespan) 116 | this.dead = true; 117 | 118 | this.lifespan++; 119 | } 120 | 121 | show(){ 122 | var angle = this.angleToPoint(this.x, this.y, this.x + this.vx, this.y + this.vy) + Math.PI/2; 123 | var op = map(this.score, 0, maxScore, 25, 255); 124 | 125 | switch(this.squad) { 126 | case 0: 127 | fill(255, 0, 0, op); 128 | break; 129 | case 1: 130 | fill(0, 255, 0, op); 131 | break; 132 | case 2: 133 | fill(0, 0, 255, op); 134 | break; 135 | case 3: 136 | fill(255, 255, 0, op); 137 | break; 138 | } 139 | 140 | push(); 141 | translate(this.x, this.y); 142 | rotate(angle); 143 | noStroke(); 144 | triangle(-this.radius, this.radius, this.radius, this.radius, 0, -this.radius); 145 | pop(); 146 | } 147 | 148 | 149 | 150 | calculateFitness(){ //Fitness function : adapt it to the needs of the 151 | this.fitness = this.score; 152 | this.fitness /= this.brain.calculateWeight() * 0.5; 153 | } 154 | 155 | angleToPoint(x1, y1, x2, y2){ 156 | let d = dist(x1, y1, x2, y2); 157 | let dx = (x2-x1) / d; 158 | let dy = (y2-y1) / d; 159 | 160 | let a = Math.acos(dx); 161 | a = dy < 0 ? 2 * Math.PI - a : a; 162 | return a; 163 | } 164 | 165 | scanForPlayers(player){ 166 | let d = dist(this.x, this.y, player.x, player.y); 167 | if(d < 100) { 168 | return player; 169 | } 170 | 171 | return null; 172 | } 173 | } -------------------------------------------------------------------------------- /Examples/TargetSeeking/population.js: -------------------------------------------------------------------------------- 1 | let genomeInputsN = 4; 2 | let genomeOutputN = 1; 3 | let showBest = false; 4 | let bestPlayer; 5 | let bestFitness = 0; 6 | 7 | //The Population Class 8 | //Here is where the power of all the classes 9 | //comes together to destroy the game score records 10 | class Population{ 11 | constructor(size, squad){ 12 | this.population = []; 13 | 14 | this.generation = 0; 15 | this.matingPool = []; 16 | 17 | for(let i = 0; i < size; i++){ 18 | this.population.push(new Player(squad)); 19 | this.population[i].brain.generateNetwork(); 20 | this.population[i].brain.mutate(); 21 | } 22 | } 23 | 24 | updateAlive(show){ 25 | for(let i = 0; i < this.population.length; i++){ 26 | if(!this.population[i].dead){ 27 | this.population[i].look(); 28 | this.population[i].think(); 29 | this.population[i].move(); 30 | this.population[i].update(); 31 | 32 | if(show) 33 | this.population[i].show(); 34 | } 35 | } 36 | } 37 | 38 | done(){ 39 | for(let i = 0; i < this.population.length; i++){ 40 | if(!this.population[i].dead){ 41 | return false; 42 | } 43 | } 44 | 45 | return true; 46 | } 47 | 48 | naturalSelection(){ 49 | this.calculateFitness(); 50 | 51 | let averageSum = this.getAverageScore(); 52 | console.log(averageSum); 53 | let children = []; 54 | 55 | this.fillMatingPool(); 56 | for(let i = 0; i < this.population.length; i++){ 57 | let parent1 = this.selectPlayer(); 58 | let parent2 = this.selectPlayer(); 59 | if(parent1.fitness > parent2.fitness) 60 | children.push(parent1.crossover(parent2)); 61 | else 62 | children.push(parent2.crossover(parent1)); 63 | } 64 | 65 | 66 | this.population.splice(0, this.population.length); 67 | this.population = children.slice(0); 68 | this.generation++; 69 | this.population.forEach((element) => { 70 | element.brain.generateNetwork(); 71 | }); 72 | 73 | console.log("Generation " + this.generation); 74 | //console.log(this); 75 | } 76 | 77 | calculateFitness(){ 78 | let currentMax = 0; 79 | this.population.forEach((element) => { 80 | element.calculateFitness(); 81 | if(element.fitness > bestFitness){ 82 | bestFitness = element.fitness; 83 | bestPlayer = element.clone(); 84 | bestPlayer.brain.id = "BestGenome"; 85 | bestPlayer.brain.draw(250, 250); 86 | } 87 | 88 | if(element.fitness > currentMax) 89 | currentMax = element.fitness; 90 | }); 91 | 92 | //Normalize 93 | this.population.forEach((element, elementN) => { 94 | element.fitness /= currentMax; 95 | }); 96 | } 97 | 98 | fillMatingPool(){ 99 | this.matingPool.splice(0, this.matingPool.length); 100 | this.population.forEach((element, elementN) => { 101 | let n = element.fitness * 100; 102 | for(let i = 0; i < n; i++) 103 | this.matingPool.push(elementN); 104 | }); 105 | } 106 | 107 | selectPlayer(){ 108 | let rand = Math.floor(Math.random() * this.matingPool.length); 109 | return this.population[this.matingPool[rand]]; 110 | } 111 | 112 | getAverageScore(){ 113 | let avSum = 0; 114 | this.population.forEach((element) => { 115 | avSum += element.score; 116 | }); 117 | 118 | return avSum / this.population.length; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Examples/TargetSeeking/sketch.js: -------------------------------------------------------------------------------- 1 | var cycles; 2 | var population1, population2, population3, population4; 3 | var move = false; 4 | var maxLifespan = 250; 5 | var target, targetVel, angle = 0; 6 | var p1 = 0, p2 = 0, p3 = 0, p4 = 0; 7 | 8 | 9 | function setup() { 10 | let canvas = createCanvas(600, 600); 11 | canvas.parent('canvascontainer'); 12 | 13 | population1 = new Population(20, 0); 14 | population2 = new Population(20, 1); 15 | population3 = new Population(20, 2); 16 | population4 = new Population(20, 3); 17 | 18 | 19 | target = createVector(width/2, height/2); 20 | targetVel = createVector(0, 0); 21 | 22 | population1.updateAlive(); 23 | } 24 | 25 | function draw() { 26 | cycles = document.getElementById('speedSlider').value; 27 | document.getElementById('speed').innerHTML = cycles; 28 | move = document.getElementById('moveCheckbox').checked; 29 | document.getElementById('generationN').innerHTML = population1.generation; 30 | document.getElementById('bestFitness').innerHTML = bestFitness; 31 | 32 | if(cycles != 0) { 33 | background(61); 34 | stroke(255); 35 | noFill(); 36 | ellipse(target.x, target.y, 200); 37 | ellipse(target.x, target.y, 2); 38 | } 39 | 40 | 41 | for(let i = 0; i < cycles; i++) { 42 | 43 | if(!population1.done()) 44 | population1.updateAlive(i==0); 45 | 46 | 47 | if(!population2.done()) 48 | population2.updateAlive(i==0); 49 | 50 | if(!population3.done()) 51 | population3.updateAlive(i==0); 52 | 53 | if(!population4.done()) 54 | population4.updateAlive(i==0); 55 | else { 56 | target = createVector(width/2, height/2); 57 | angle = Math.random() * 360; 58 | 59 | if(population1.generation % 20 == 0) 60 | maxLifespan += 10; 61 | 62 | let max = Math.max(population1.getAverageScore(), population2.getAverageScore(), population3.getAverageScore(), population4.getAverageScore()) 63 | if(max == population1.getAverageScore()) 64 | p1++; 65 | else if(max == population2.getAverageScore()) 66 | p2++; 67 | else if(max == population3.getAverageScore()) 68 | p3++; 69 | else if(max == population4.getAverageScore()) 70 | p4++; 71 | 72 | document.getElementById('red').innerHTML = p1; 73 | document.getElementById('green').innerHTML = p2; 74 | document.getElementById('blue').innerHTML = p3; 75 | document.getElementById('yellow').innerHTML = p4; 76 | 77 | population1.naturalSelection(); 78 | population2.naturalSelection(); 79 | population3.naturalSelection(); 80 | population4.naturalSelection(); 81 | } 82 | 83 | if(move) { 84 | angle += 0.00025; 85 | angle = angle%360; 86 | target.x = width/2 + Math.cos(angle * 180/Math.PI) * 150; 87 | target.y = height/2 + Math.sin(angle * 180/Math.PI) * 100; 88 | } 89 | } 90 | } 91 | 92 | function mouseReleased() { 93 | if(mouseX < width && mouseX > 0 && mouseY < height && mouseY > 0) { 94 | target.x = mouseX; 95 | target.y = mouseY; 96 | document.getElementById('moveCheckbox').checked = false; 97 | } 98 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NeatJS 2 | A JavaScript implementation of the Neat Algorithm.
3 | Genome Drawing done using D3.js 4 | 5 | # Examples 6 | #### Classification 7 | Using an evolutionary process we eventually get a network that can identify whenever a point is on one side or the other of the line. 8 | [View it in action.](https://gabrieltavernini.github.io/NeatJS/Examples/Classification/) 9 | 10 | 11 | #### Pole Balancing 12 | Pole balancing is a control benchmark historically used in engineering. It involves a pole affixed to a cart via a joint which allows movement along a single axis. The cart is able to move along a track of fixed length. Using the Neat Algorithm, we can evolve a network to control the cart. [Here it is!](https://gabrieltavernini.github.io/NeatJS/Examples/PoleBalancing/) 13 | 14 | 15 | #### Target Seeking 16 | In this example, we have four different populations, and each population evolves independently and at the end of each generation the best population on average score gets a point. The purpose of each player is to stay in the circle as long as possible. [Check it out!](https://gabrieltavernini.github.io/NeatJS/Examples/TargetSeeking/) 17 | 18 | 19 | #### Self Driving Car 20 | Applying the NEAT algorithm to a 3D car trying to get along a course, we can clearly see how Neural Networks evolve generation after generation improving their abilities to get along the track. You can even tweak different parameters to customize the fitness function. [Try it!](https://gabrieltavernini.github.io/NeatJS/Examples/SelfDriving/) 21 | 22 | 23 | #### Atari Asteroids 24 | A population of 150 spaceships evolving to beat the old Atari Asteroids Game. Spaceships can see the distance to the closest asteroid in 16 directions evenly spaced around them and can move forward, turn right or left and shoot. [Try it!](https://gabrieltavernini.github.io/NeatJS/Examples/Asteroids/) 25 | 26 | 27 | #### Duck Platformer - Made by TranHuy2k2 28 | AI duck population trying to survive a meteors disaster. Learn with NEAT algorithm, the ducks have to determine the direction to dodge all the meteors from the sky. A duck has two actions to decide: move left or right. From multiple generations, the neural network will be evolved to best adapt to the gameplay. May the best duck survive! [Take a look!](https://github.com/zit-software/zit-neat) 29 | -------------------------------------------------------------------------------- /Screenshots/Classification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GabrielTavernini/NeatJS/4ce41c680778386129f4129db099f2da37b5ba55/Screenshots/Classification.png -------------------------------------------------------------------------------- /Screenshots/PoleBalancing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GabrielTavernini/NeatJS/4ce41c680778386129f4129db099f2da37b5ba55/Screenshots/PoleBalancing.png -------------------------------------------------------------------------------- /Screenshots/TargetSeeking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GabrielTavernini/NeatJS/4ce41c680778386129f4129db099f2da37b5ba55/Screenshots/TargetSeeking.png -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | url: "https://GabrielTavernini.github.io" 3 | baseurl: "/NeatJS" 4 | include: [./src, ./Examples] 5 | -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% if site.google_analytics %} 6 | 7 | 13 | {% endif %} 14 | 15 | 16 | {% seo %} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 36 | 37 |
38 | {{ content }} 39 | 40 | 46 |
47 | 48 | 49 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GabrielTavernini/NeatJS/4ce41c680778386129f4129db099f2da37b5ba55/favicon.ico -------------------------------------------------------------------------------- /src/connection.js: -------------------------------------------------------------------------------- 1 | //The Connection Class 2 | //Is where all the weights are stored 3 | //Mostly used for a cleaner and more readable code. 4 | class Connection { 5 | constructor(from, to, weight){ 6 | this.fromNode = from; //type: Node 7 | this.toNode = to; //type: Node 8 | this.weight = weight; //type: Number 9 | this.enabled = true; 10 | } 11 | 12 | mutateWeight(){ //Randomly mutate the weight of this connection 13 | let rand = Math.random(); 14 | if (rand < 0.05) //5% chance of being assigned a new random value 15 | this.weight = Math.random() * 2 - 1; 16 | else //95% chance of being uniformly perturbed 17 | this.weight += randomGaussian() / 50; 18 | } 19 | 20 | clone(){ //Returns a copy of this connection 21 | let clone = new Connection(this.fromNode, this.toNode, this.weight); 22 | clone.enabled = this.enabled; 23 | return clone; 24 | } 25 | 26 | getInnovationNumber(){ //Using https://en.wikipedia.org/wiki/Pairing_function#Cantor_pairing_function 27 | return (1/2)*(this.fromNode.number + this.toNode.number)*(this.fromNode.number + this.toNode.number + 1) + this.toNode.number; 28 | } 29 | } -------------------------------------------------------------------------------- /src/genome.js: -------------------------------------------------------------------------------- 1 | //The Genome Class 2 | //Well.. this is the main class 3 | //This is where all the magic appends 4 | class Genome { 5 | constructor(inp, out, id, offSpring = false) { 6 | this.inputs = inp; //Number of inputs 7 | this.outputs = out; //Number of outputs 8 | this.id = id; //Genome id -> used for the drawing 9 | this.layers = 2; 10 | this.nextNode = 0; 11 | 12 | this.nodes = []; 13 | this.connections = []; 14 | 15 | if(!offSpring) { //This is not an offspring genome generate a fullyConnected net 16 | for (let i = 0; i < this.inputs; i++) { 17 | this.nodes.push(new Node(this.nextNode, 0)); 18 | this.nextNode++; 19 | } 20 | 21 | for (let i = 0; i < this.outputs; i++) { 22 | let node = new Node(this.nextNode, 1, true); 23 | this.nodes.push(node); 24 | this.nextNode++; 25 | } 26 | 27 | 28 | for (let i = 0; i < this.inputs; i++) { 29 | for (let j = this.inputs; j < this.outputs + this.inputs; j++) { 30 | let weight = Math.random() * this.inputs * Math.sqrt(2 / this.inputs); 31 | this.connections.push(new Connection(this.nodes[i], this.nodes[j], weight)); 32 | } 33 | } 34 | } 35 | } 36 | 37 | //Network Core 38 | generateNetwork() { 39 | //Clear all outputConnections in the nodes 40 | this.nodes.forEach((node) => { 41 | node.outputConnections.splice(0, node.outputConnections.length); 42 | }); 43 | 44 | //Add the connections to the Nodes 45 | this.connections.forEach((conn) => { 46 | conn.fromNode.outputConnections.push(conn); 47 | }); 48 | 49 | //Prepare for feed forward 50 | this.sortByLayer(); 51 | } 52 | 53 | feedForward(inputValues) { 54 | this.generateNetwork(); //Connect all up 55 | 56 | //Clear old inputs 57 | this.nodes.forEach((node) => { node.inputSum = 0; }); 58 | 59 | //asin new inputs 60 | for (let i = 0; i < this.inputs; i++) 61 | this.nodes[i].outputValue = inputValues[i]; 62 | 63 | //Engage all nodes and Extract the results from the outputs 64 | let result = []; 65 | this.nodes.forEach((node) => { 66 | node.engage(); 67 | 68 | if (node.output) 69 | result.push(node.outputValue); 70 | }); 71 | return result; 72 | } 73 | 74 | 75 | //Crossover 76 | crossover(partner) { 77 | //TODO: find a good way to generate unique ids 78 | let offSpring = new Genome(this.inputs, this.outputs, 0, true); //Child genome 79 | offSpring.nextNode = this.nextNode; 80 | 81 | 82 | //Take all nodes from this parent - output node activation 50%-50% 83 | for(let i = 0; i < this.nodes.length; i++){ 84 | let node = this.nodes[i].clone(); 85 | if(node.output) { 86 | let partnerNode = partner.nodes[partner.getNode(node.number)]; 87 | if(Math.random() > 0.5) { 88 | node.activationFunction = partnerNode.activationFunction; 89 | node.bias = partnerNode.bias; 90 | } 91 | } 92 | offSpring.nodes.push(node); 93 | } 94 | 95 | //Randomly take connections from this or the partner network 96 | let maxLayer = 0; 97 | for(let i = 0; i < this.connections.length; i++) { 98 | let index = this.commonConnection(this.connections[i].getInnovationNumber(), partner.connections); 99 | 100 | if(index != -1) { //There is a commonConnection 101 | let conn = Math.random() > 0.5 ? this.connections[i].clone() : partner.connections[index].clone(); 102 | 103 | //Reassign nodes 104 | let fromNode = offSpring.nodes[offSpring.getNode(conn.fromNode.number)]; 105 | let toNode = offSpring.nodes[offSpring.getNode(conn.toNode.number)]; 106 | conn.fromNode = fromNode; 107 | conn.toNode = toNode; 108 | 109 | //Add this connection to the child 110 | if(fromNode && toNode) 111 | offSpring.connections.push(conn); 112 | } 113 | else { //No common connection -> take from this 114 | let conn = this.connections[i].clone(); 115 | 116 | //Reassign nodes 117 | let fromNode = offSpring.nodes[offSpring.getNode(conn.fromNode.number)]; 118 | let toNode = offSpring.nodes[offSpring.getNode(conn.toNode.number)]; 119 | conn.fromNode = fromNode; 120 | conn.toNode = toNode; 121 | 122 | //Add this connection to the child 123 | if(fromNode && toNode) 124 | offSpring.connections.push(conn); 125 | } 126 | } 127 | 128 | offSpring.layers = this.layers; 129 | return offSpring; 130 | } 131 | 132 | 133 | 134 | //Mutation Stuff 135 | mutate() { 136 | //console.log("Mutation..."); 137 | let mut; 138 | 139 | if(Math.random() < 0.8) { //80% 140 | //MOD Connections 141 | mut = "ModConn"; 142 | //let i = Math.floor(Math.random() * this.connections.length); 143 | //this.connections[i].mutateWeight(); 144 | for (var i = 0; i < this.connections.length; i++) { 145 | this.connections[i].mutateWeight(); 146 | } 147 | } 148 | 149 | if(Math.random() < 0.5) { //50% 150 | //MOD Bias 151 | mut = "ModBias"; 152 | //let i = Math.floor(Math.random() * this.nodes.length); 153 | //this.nodes[i].mutateBias(); 154 | for (var i = 0; i < this.nodes.length; i++) { 155 | this.nodes[i].mutateBias(); 156 | } 157 | } 158 | 159 | if(Math.random() < 0.1) { //10% 160 | //MOD Node 161 | mut = "ModAct"; 162 | let i = Math.floor(Math.random() * this.nodes.length); 163 | this.nodes[i].mutateActivation(); 164 | } 165 | 166 | if(Math.random() < 0.05) { //5% 167 | //ADD Connections 168 | mut = "AddConn"; 169 | this.addConnection(); 170 | } 171 | 172 | if(Math.random() < 0.01) { //1% 173 | //ADD Node 174 | mut = "AddNode"; 175 | this.addNode(); 176 | } 177 | } 178 | 179 | addNode() { //Add a node to the network 180 | //Get a random connection to replace with a node 181 | let connectionIndex = Math.floor(Math.random() * this.connections.length); 182 | let pickedConnection = this.connections[connectionIndex]; 183 | pickedConnection.enabled = false; 184 | this.connections.splice(connectionIndex, 1); //Delete the connection 185 | 186 | //Create the new node 187 | let newNode = new Node(this.nextNode, pickedConnection.fromNode.layer + 1); 188 | this.nodes.forEach((node) => { //Shift all nodes layer value 189 | if (node.layer > pickedConnection.fromNode.layer) 190 | node.layer++; 191 | }); 192 | 193 | //New connections 194 | let newConnection1 = new Connection(pickedConnection.fromNode, newNode, 1); 195 | let newConnection2 = new Connection(newNode, pickedConnection.toNode, pickedConnection.weight); 196 | 197 | this.layers++; 198 | this.connections.push(newConnection1); //Add connection 199 | this.connections.push(newConnection2); //Add connection 200 | this.nodes.push(newNode); //Add node 201 | this.nextNode++; 202 | } 203 | 204 | addConnection() { //Add a connection to the network 205 | if (this.fullyConnected()) 206 | return; //Cannot add connections if it's fullyConnected 207 | 208 | //Choose to nodes to connect 209 | let node1 = Math.floor(Math.random() * this.nodes.length); 210 | let node2 = Math.floor(Math.random() * this.nodes.length); 211 | 212 | //Search for two valid nodes 213 | while (this.nodes[node1].layer == this.nodes[node2].layer 214 | || this.nodesConnected(this.nodes[node1], this.nodes[node2])) { 215 | node1 = Math.floor(Math.random() * this.nodes.length); 216 | node2 = Math.floor(Math.random() * this.nodes.length); 217 | } 218 | 219 | //Switch nodes based on their layer 220 | if (this.nodes[node1].layer > this.nodes[node2].layer) { 221 | let temp = node1; 222 | node1 = node2; 223 | node2 = temp; 224 | } 225 | 226 | //add the connection 227 | let newConnection = new Connection(this.nodes[node1], this.nodes[node2], Math.random() * this.inputs * Math.sqrt(2 / this.inputs)); 228 | this.connections.push(newConnection); 229 | } 230 | 231 | 232 | 233 | //Utilities 234 | commonConnection(innN, connections) { 235 | //Search through all connections to check for 236 | //one with the correct Innovation Number 237 | for(let i = 0; i < connections.length; i++){ 238 | if(innN == connections[i].getInnovationNumber()) 239 | return i; 240 | } 241 | 242 | //Found nothing 243 | return -1; 244 | } 245 | 246 | nodesConnected(node1, node2) { 247 | //Search if there is a connection between node1 & node2 248 | for (let i = 0; i < this.connections.length; i++) { 249 | let conn = this.connections[i]; 250 | if ((conn.fromNode == node1 && conn.toNode == node2) 251 | || (conn.fromNode == node2 && conn.toNode == node1)) { 252 | return true; 253 | } 254 | }; 255 | 256 | return false; 257 | } 258 | 259 | fullyConnected() { 260 | //check if the network is fully connected 261 | let maxConnections = 0; 262 | let nodesPerLayer = []; 263 | 264 | //Calculate all possible connections 265 | this.nodes.forEach((node) => { 266 | if (nodesPerLayer[node.layer] != undefined) 267 | nodesPerLayer[node.layer]++; 268 | else 269 | nodesPerLayer[node.layer] = 1; 270 | }); 271 | 272 | for (let i = 0; i < this.layers - 1; i++) 273 | for (let j = i + 1; j < this.layers; j++) 274 | maxConnections += nodesPerLayer[i] * nodesPerLayer[j]; 275 | 276 | //Compare 277 | return maxConnections == this.connections.length; 278 | } 279 | 280 | sortByLayer(){ 281 | //Sort all nodes by layer 282 | this.nodes.sort((a, b) => { 283 | return a.layer - b.layer; 284 | }); 285 | } 286 | 287 | clone() { //Returns a copy of this genome 288 | let clone = new Genome(this.inputs, this.outputs, this.id); 289 | clone.nodes = this.nodes.slice(0, this.nodes.length); 290 | clone.connections = this.connections.slice(0, this.connections.length); 291 | 292 | return clone; 293 | } 294 | 295 | getNode(x){ //Returns the index of a node with that Number 296 | for(let i = 0; i < this.nodes.length; i++) 297 | if(this.nodes[i].number == x) 298 | return i; 299 | 300 | return -1; 301 | } 302 | 303 | calculateWeight() { //Computational weight of the network 304 | return this.connections.length + this.nodes.length; 305 | } 306 | 307 | draw(width = 400, height = 400, container = "svgContainer") { //Draw the genome to a svg 308 | var element = document.getElementById(this.id); 309 | if (element) 310 | element.parentNode.removeChild(element); 311 | 312 | var svg = d3.select("body").append("svg") 313 | .attr("width", width) 314 | .attr("height", height) 315 | .attr("id", this.id); 316 | 317 | 318 | var force = d3.layout.force() 319 | .gravity(.05) 320 | .distance(100) 321 | .charge(-100) 322 | .size([width, height]); 323 | 324 | 325 | let connections = []; 326 | this.connections.forEach(conn => { 327 | connections.push({ source: this.getNode(conn.fromNode.number), target: this.getNode(conn.toNode.number), weight: conn.weight, enabled: conn.enabled }); 328 | }); 329 | 330 | let nodes = []; 331 | this.nodes.forEach(originalNode => { 332 | let node = originalNode.clone(); 333 | if(node.layer == 0) { 334 | node.fixed = true; 335 | node.y = height - (height * 0.2); 336 | node.x = ((width/this.inputs) * node.number) + (width/this.inputs)/2; 337 | } 338 | 339 | if(node.output) { 340 | node.fixed = true; 341 | node.y = (height * 0.2); 342 | node.x = ((width/this.outputs) * (node.number - this.inputs)) + (width/this.outputs)/2; 343 | } 344 | 345 | nodes.push(node); 346 | }); 347 | 348 | force.nodes(nodes) 349 | .links(connections) 350 | .start(); 351 | 352 | var link = svg.selectAll(".link") 353 | .data(connections) 354 | .enter().append("line") 355 | .attr("class", "link") 356 | .style("stroke-width", function (d) { return d.enabled ? (d.weight > 0 ? 0.3 + d.weight : 0.3 + d.weight*-1) : 0 }) 357 | .style("stroke", function (d) { return d.weight > 0 ? "#0f0" : "#f00"; }); 358 | 359 | var node = svg.selectAll(".node") 360 | .data(nodes) 361 | .enter().append("g") 362 | .attr("class", "node") 363 | .call(force.drag); 364 | 365 | node.append("circle") 366 | .attr("r", "5") 367 | .attr("fill", function (d) { return d.layer == 0 ? "#00f" : d.output ? "#f00" : "#000" }); 368 | 369 | node.append("text") 370 | .attr("dx", 12) 371 | .attr("dy", ".35em") 372 | .text(function(d) { return d.number + (d.layer > 0 ? "(" + activationsNames[d.activationFunction] + ")" : null) }); 373 | 374 | force.on("tick", function () { 375 | link.attr("x1", function (d) { return d.source.x; }) 376 | .attr("y1", function (d) { return d.source.y; }) 377 | .attr("x2", function (d) { return d.target.x; }) 378 | .attr("y2", function (d) { return d.target.y; }); 379 | 380 | node.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; }); 381 | }); 382 | 383 | var element = document.getElementById(this.id); 384 | document.getElementById(container).append(element); 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | Speed: 1 20 |
21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /src/node.js: -------------------------------------------------------------------------------- 1 | var activationsNames = ["Sigmoid", "Identity", "Step", "Tanh", "ReLu"]; //Used in the svg drawing 2 | 3 | //The Node Class 4 | //This is where math appends 5 | class Node { 6 | constructor(num, lay, isOutput) { 7 | this.number = num; 8 | this.layer = lay; 9 | this.activationFunction = Math.floor(Math.random() * 5); //Number between 0 and 4 10 | this.bias = Math.random() * 2 - 1; 11 | this.output = isOutput || false; //is this node an Output node? 12 | 13 | this.inputSum = 0; 14 | this.outputValue = 0; 15 | this.outputConnections = []; 16 | } 17 | 18 | engage() { //Pass down the network the calculated output value 19 | if (this.layer != 0) //No activation function on input nodes 20 | this.outputValue = this.activation(this.inputSum + this.bias); 21 | 22 | 23 | this.outputConnections.forEach((conn) => { 24 | if (conn.enabled) //Do not pass value if connection is disabled 25 | conn.toNode.inputSum += conn.weight * this.outputValue; //Weighted output sum 26 | }); 27 | } 28 | 29 | mutateBias() { //Randomly mutate the bias of this node 30 | let rand = Math.random(); 31 | if (rand < 0.05) //5% chance of being assigned a new random value 32 | this.bias = Math.random() * 2 - 1; 33 | else //95% chance of being uniformly perturbed 34 | this.bias += randomGaussian() / 50; 35 | } 36 | 37 | mutateActivation() { //Randomly choose a new activationFunction 38 | this.activationFunction = Math.floor(Math.random() * 5); //Number between 0 and 4 39 | } 40 | 41 | isConnectedTo(node) { //Check if two nodes are connected 42 | if (node.layer == this.layer) //nodes in the same layer cannot be connected 43 | return false; 44 | 45 | 46 | if (node.layer < this.layer) { //Check parameter node connections 47 | node.outputConnections.forEach((conn) => { 48 | if (conn.toNode == this) //Is Node connected to This? 49 | return true; 50 | }); 51 | } else { //Check this node connections 52 | this.outputConnections.forEach((conn) => { 53 | if (conn.toNode == node) //Is This connected to Node? 54 | return true; 55 | }); 56 | } 57 | 58 | return false; 59 | } 60 | 61 | clone() { //Returns a copy of this node 62 | let node = new Node(this.number, this.layer, this.output); 63 | node.bias = this.bias; //Same bias 64 | node.activationFunction = this.activationFunction; //Same activationFunction 65 | return node; 66 | } 67 | 68 | activation(x) { //All the possible activation Functions 69 | switch (this.activationFunction) { 70 | case 0: //Sigmoid 71 | return 1 / (1 + Math.pow(Math.E, -4.9 * x)); 72 | break; 73 | case 1: //Identity 74 | return x; 75 | break; 76 | case 2: //Step 77 | return x > 0 ? 1 : 0; 78 | break; 79 | case 3: //Tanh 80 | return Math.tanh(x); 81 | break; 82 | case 4: //ReLu 83 | return x < 0 ? 0 : x; 84 | break; 85 | default: //Sigmoid 86 | return 1 / (1 + Math.pow(Math.E, -4.9 * x)); 87 | break; 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /src/player.js: -------------------------------------------------------------------------------- 1 | //The Player Class 2 | //The interface between our 3 | //NeuralNetwork and the game 4 | class Player{ 5 | constructor(id){ 6 | this.brain = new Genome(genomeInputsN, genomeOutputN, id); 7 | this.fitness; 8 | 9 | this.score = 1; 10 | this.lifespan = 0; 11 | this.dead = false; 12 | this.decisions = []; //Current Output values 13 | this.vision = []; //Current input values 14 | } 15 | 16 | clone() { //Returns a copy of this player 17 | let clone = new Player(); 18 | clone.brain = this.brain.clone(); 19 | return clone; 20 | } 21 | 22 | crossover(parent){ //Produce a child 23 | let child = new Player(); 24 | if(parent.fitness < this.fitness) 25 | child.brain = this.brain.crossover(parent.brain); 26 | else 27 | child.brain = parent.brain.crossover(this.brain); 28 | 29 | child.brain.mutate() 30 | return child; 31 | } 32 | 33 | 34 | //Game stuff 35 | look(){ 36 | this.vision = [points[this.lifespan].x, points[this.lifespan].y]; 37 | this.correctVal = points[this.lifespan].type; 38 | } 39 | 40 | think(){ 41 | this.decisions = this.brain.feedForward(this.vision); 42 | } 43 | 44 | move(){ 45 | let maxIndex = 0; 46 | for(let i = 0; i < this.decisions.length; i++) 47 | if(this.decisions[i] > this.decisions[maxIndex]) 48 | maxIndex = i; 49 | 50 | this.val = this.decisions[maxIndex] >= 0 ? 1 : 0; 51 | } 52 | 53 | update(){ 54 | if(this.correctVal == this.val) { 55 | this.score++; 56 | } 57 | 58 | this.lifespan++; 59 | if(this.lifespan >= points.length) 60 | this.dead = true; 61 | } 62 | 63 | show(){ 64 | push(); 65 | if(this.correctVal == this.val) { 66 | if(this.correctVal == 1) 67 | fill(0, 255, 0); 68 | 69 | if(this.correctVal == 0) 70 | fill(0, 0, 255); 71 | 72 | ellipse(points[this.lifespan - 1].x * width, points[this.lifespan - 1].y * height, 6) 73 | } else { 74 | fill(255, 0, 0); 75 | ellipse(points[this.lifespan - 1].x * width, points[this.lifespan - 1].y * height, 6) 76 | } 77 | pop(); 78 | } 79 | 80 | calculateFitness(){ //Fitness function : adapt it to the needs of the 81 | this.fitness = this.score; 82 | this.fitness /= this.brain.calculateWeight(); 83 | } 84 | } -------------------------------------------------------------------------------- /src/population.js: -------------------------------------------------------------------------------- 1 | let genomeInputsN = 2; 2 | let genomeOutputN = 1; 3 | let showBest = true; 4 | 5 | //The Population Class 6 | //Here is where the power of all the classes 7 | //comes together to destroy the game score records 8 | class Population{ 9 | constructor(size){ 10 | this.population = []; 11 | this.bestPlayer; 12 | this.bestFitness = 0; 13 | 14 | this.generation = 0; 15 | this.matingPool = []; 16 | 17 | for(let i = 0; i < size; i++){ 18 | this.population.push(new Player(i)); 19 | this.population[i].brain.generateNetwork(); 20 | this.population[i].brain.mutate(); 21 | } 22 | } 23 | 24 | updateAlive(){ 25 | for(let i = 0; i < this.population.length; i++){ 26 | if(!this.population[i].dead){ 27 | this.population[i].look(); 28 | this.population[i].think(); 29 | this.population[i].move(); 30 | this.population[i].update(); 31 | //this.population[i].show(); 32 | } 33 | } 34 | 35 | if(showBest && this.bestPlayer && !this.bestPlayer.dead) { 36 | this.bestPlayer.look(); 37 | this.bestPlayer.think(); 38 | this.bestPlayer.move(); 39 | this.bestPlayer.update(); 40 | this.bestPlayer.show(); 41 | } 42 | } 43 | 44 | done(){ 45 | for(let i = 0; i < this.population.length; i++){ 46 | if(!this.population[i].dead){ 47 | return false; 48 | } 49 | } 50 | 51 | return true; 52 | } 53 | 54 | naturalSelection(){ 55 | this.calculateFitness(); 56 | 57 | let averageSum = this.getAverageScore(); 58 | console.log(averageSum); 59 | let children = []; 60 | 61 | this.fillMatingPool(); 62 | for(let i = 0; i < this.population.length; i++){ 63 | let parent1 = this.selectPlayer(); 64 | let parent2 = this.selectPlayer(); 65 | if(parent1.fitness > parent2.fitness) 66 | children.push(parent1.crossover(parent2)); 67 | else 68 | children.push(parent2.crossover(parent1)); 69 | } 70 | 71 | 72 | this.population.splice(0, this.population.length); 73 | this.population = children.slice(0); 74 | this.generation++; 75 | this.population.forEach((element) => { 76 | element.brain.generateNetwork(); 77 | }); 78 | 79 | console.log("Generation " + this.generation); 80 | //console.log(this); 81 | 82 | this.bestPlayer.lifespan = 0; 83 | this.bestPlayer.dead = false; 84 | this.bestPlayer.score = 1; 85 | } 86 | 87 | calculateFitness(){ 88 | let currentMax = 0; 89 | this.population.forEach((element) => { 90 | element.calculateFitness(); 91 | if(element.fitness > this.bestFitness){ 92 | this.bestFitness = element.fitness; 93 | this.bestPlayer = element.clone(); 94 | this.bestPlayer.brain.id = "BestGenome"; 95 | this.bestPlayer.brain.draw(); 96 | } 97 | 98 | if(element.fitness > currentMax) 99 | currentMax = element.fitness; 100 | }); 101 | 102 | //Normalize 103 | this.population.forEach((element, elementN) => { 104 | element.fitness /= currentMax; 105 | }); 106 | } 107 | 108 | fillMatingPool(){ 109 | this.matingPool.splice(0, this.matingPool.length); 110 | this.population.forEach((element, elementN) => { 111 | let n = element.fitness * 100; 112 | for(let i = 0; i < n; i++) 113 | this.matingPool.push(elementN); 114 | }); 115 | } 116 | 117 | selectPlayer(){ 118 | let rand = Math.floor(Math.random() * this.matingPool.length); 119 | return this.population[this.matingPool[rand]]; 120 | } 121 | 122 | getAverageScore(){ 123 | let avSum = 0; 124 | this.population.forEach((element) => { 125 | avSum += element.score; 126 | }); 127 | 128 | return avSum / this.population.length; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/sketch.js: -------------------------------------------------------------------------------- 1 | var population; 2 | var cycles; 3 | var points = []; 4 | var myFunction = function(x) { 5 | return height- x+ 150; 6 | }; 7 | 8 | function setup() { 9 | let canvas = createCanvas(400, 400); 10 | canvas.parent('canvascontainer'); 11 | background(61); 12 | 13 | //Initialize the population 14 | population = new Population(50); 15 | 16 | //Generate and Draw points 17 | for(let i = 0; i < 200; i++) { 18 | let y = Math.random(), x = Math.random(); 19 | let type = y * height > myFunction(x * width) ? 1 : 0; 20 | points.push({x: x, y: y, type: type}); 21 | 22 | fill(255); 23 | ellipse(points[i].x * width, points[i].y * height, 10) 24 | } 25 | 26 | //Draw separation line 27 | for(let i = 0; i < width; i += 10) { 28 | fill(50); 29 | ellipse(i, myFunction(i), 10) 30 | } 31 | } 32 | 33 | function draw() { 34 | cycles = select('#speedSlider').value(); 35 | select('#speed').html(cycles); 36 | 37 | for(let i = 0; i < cycles; i++) 38 | if(!population.done()) 39 | population.updateAlive(); 40 | else 41 | population.naturalSelection(); 42 | } --------------------------------------------------------------------------------