diff --git a/EnemyBoid.js b/EnemyBoid.js new file mode 100644 index 0000000..8eedfb2 --- /dev/null +++ b/EnemyBoid.js @@ -0,0 +1,187 @@ +class EnemyBoid { + constructor(x, y) { + this.position = createVector(x, y); + this.velocity = createVector(0, 0); + + this.trail = []; + for (let i = 0; i < 100; i++) { + this.trail.push(this.position.copy()); + } + } + + update(trail = false, update = true) { + if (!update) { + return; + } + + // Separation + let close_dx = 0; + let close_dy = 0; + + for (let boid of enemy_boids) { + if (boid != this) { + if (dist(this.position.x, this.position.y, boid.position.x, boid.position.y) < avoidance_range) { + close_dx += this.position.x - boid.position.x; + close_dy += this.position.y - boid.position.y; + } + } + } + + this.velocity.add(createVector(close_dx, close_dy).normalize().mult(avoidance_constant)); + + + // Alignment + let avg_dx = 0; + let avg_dy = 0; + let total_neighbours = 0; + + for (let boid of enemy_boids) { + if (boid != this) { + if (dist(this.position.x, this.position.y, boid.position.x, boid.position.y) < visible_range) { + avg_dx += boid.velocity.x; + avg_dy += boid.velocity.y; + + total_neighbours++; + } + } + } + + if (total_neighbours > 0) { + avg_dx /= total_neighbours; + avg_dy /= total_neighbours; + } + + this.velocity.add(createVector(avg_dx, avg_dy).normalize().mult(alignment_constant)); + + + // Cohesion + total_neighbours = 0; + let avg_x = 0; + let avg_y = 0; + + for (let boid of enemy_boids) { + if (boid != this) { + if (dist(this.position.x, this.position.y, boid.position.x, boid.position.y) < visible_range) { + avg_x += boid.position.x; + avg_y += boid.position.y; + + total_neighbours++; + } + } + } + + if (total_neighbours > 0) { + avg_x /= total_neighbours; + avg_y /= total_neighbours; + } + + this.velocity.add(createVector(avg_x - this.position.x, avg_y - this.position.y).normalize().mult(cohesion_constant)); + + + // POI Repulsion + close_dx = 0; + close_dy = 0; + + for (let poi of pointsOfInterest) { + if (dist(this.position.x, this.position.y, poi.position.x, poi.position.y) < poi_protection_range) { + close_dx += this.position.x - poi.position.x; + close_dy += this.position.y - poi.position.y; + } + } + + this.velocity.add(createVector(close_dx, close_dy).normalize().mult(poi_avoidance_constant)); + + + // Enemy Boid Attack + let friendly_boid_dx = 0; + let friendly_boid_dy = 0; + for (let poi of friendly_boids) { + if (dist(this.position.x, this.position.y, poi.position.x, poi.position.y) < visible_range) { + friendly_boid_dx += poi.position.x - this.position.x; + friendly_boid_dy += poi.position.y - this.position.y; + } + } + + this.velocity.add(createVector(friendly_boid_dx, friendly_boid_dy).normalize().mult(enemy_boid_attack_constant)); + + + // Turn around if it's going out of bounds + if (this.position.x < margin) { + this.velocity.x += turn_constant; + } else if (this.position.x > width - margin) { + this.velocity.x -= turn_constant; + } + + if (this.position.y < margin) { + this.velocity.y += turn_constant; + } else if (this.position.y > height - margin) { + this.velocity.y -= turn_constant; + } + + + // Limit speed + if (this.velocity.mag() > max_speed) { + this.velocity.normalize().mult(max_speed); + } else if (this.velocity.mag() < min_speed) { + this.velocity.normalize().mult(min_speed); + } + + // Update the trail + if (trail) { + this.trail.shift(); + this.trail.push(this.position.copy()); + } + + // Move the boid + this.position.add(this.velocity); + } + + draw(trail = false, shape = "Triangle", direction = false, range = false, avoid = false) { + // Draw the boid as a sphere with a line indicating its direction + if (shape == "Circle") { + stroke(255, 0, 0); + strokeWeight(15); + point(this.position.x, this.position.y); + } else { + // Draw the boid as a triangle + strokeWeight(0); + fill(255, 0, 0); + let angle = this.velocity.heading(); + let a = createVector(0, -30).rotate(angle + PI / 2).add(this.position); + let b = createVector(10, 0).rotate(angle + PI / 2).add(this.position); + let c = createVector(-10, 0).rotate(angle + PI / 2).add(this.position); + triangle(a.x, a.y, b.x, b.y, c.x, c.y); + } + + if (direction) { + stroke(0, 0, 255); + strokeWeight(2); + line(this.position.x, this.position.y, this.position.x + (this.velocity.x * 5), this.position.y + (this.velocity.y * 5)); + } + + // Add a trail behind the boid + if (trail) { + for (let i = 0; i < this.trail.length; i++) { + let alpha = map(i, 0, this.trail.length, 0, 255); + fill(255, 255, 255, alpha); + stroke(255, 255, 255, alpha); + strokeWeight(5); + point(this.trail[i].x, this.trail[i].y); + } + } + + if (range) { + noFill(); + stroke(0, 255, 0, 50); + strokeWeight(2); + ellipse(this.position.x, this.position.y, visible_range * 2); + } + + if (avoid) { + noFill(); + stroke(255, 0, 0, 50); + strokeWeight(2); + ellipse(this.position.x, this.position.y, avoidance_range * 2); + } + } +} \ No newline at end of file diff --git a/EnemyBoidOptimised.js b/EnemyBoidOptimised.js new file mode 100644 index 0000000..0b83081 --- /dev/null +++ b/EnemyBoidOptimised.js @@ -0,0 +1,154 @@ +class EnemyBoidOptimised { + constructor(x, y) { + this.position = createVector(x, y); + this.velocity = createVector(0, 0); + + this.trail = []; + for (let i = 0; i < 100; i++) { + this.trail.push(this.position.copy()); + } + } + + update(trail = false, update = true) { + if (!update) { + return; + } + + let close_dx = 0; + let close_dy = 0; + let avg_dx = 0; + let avg_dy = 0; + let avg_x = 0; + let avg_y = 0; + let total_neighbours = 0; + let poi_avoid_dx = 0; + let poi_avoid_dy = 0; + let friendly_boid_dx = 0; + let friendly_boid_dy = 0; + + for (let boid of enemy_boids) { + if (boid != this) { + if (dist(this.position.x, this.position.y, boid.position.x, boid.position.y) < avoidance_range) { + close_dx += this.position.x - boid.position.x; + close_dy += this.position.y - boid.position.y; + } + + if (dist(this.position.x, this.position.y, boid.position.x, boid.position.y) < visible_range) { + avg_dx += boid.velocity.x; + avg_dy += boid.velocity.y; + + avg_x += boid.position.x; + avg_y += boid.position.y; + + total_neighbours++; + } + } + } + + for (let poi of pointsOfInterest) { + if (dist(this.position.x, this.position.y, poi.position.x, poi.position.y) < poi_protection_range) { + poi_avoid_dx += this.position.x - poi.position.x; + poi_avoid_dy += this.position.y - poi.position.y; + } + } + + for (let poi of friendly_boids) { + if (dist(this.position.x, this.position.y, poi.position.x, poi.position.y) < visible_range) { + friendly_boid_dx += poi.position.x - this.position.x; + friendly_boid_dy += poi.position.y - this.position.y; + } + } + + if (total_neighbours > 0) { + avg_dx /= total_neighbours; + avg_dy /= total_neighbours; + + avg_x /= total_neighbours; + avg_y /= total_neighbours; + } + + this.velocity.add(createVector(close_dx, close_dy).normalize().mult(avoidance_constant)); + this.velocity.add(createVector(avg_dx, avg_dy).normalize().mult(alignment_constant)); + this.velocity.add(createVector(avg_x - this.position.x, avg_y - this.position.y).normalize().mult(cohesion_constant)); + this.velocity.add(createVector(poi_avoid_dx, poi_avoid_dy).normalize().mult(poi_avoidance_constant)); + this.velocity.add(createVector(friendly_boid_dx, friendly_boid_dy).normalize().mult(enemy_boid_attack_constant)); + + // Turn around if it's going out of bounds + if (this.position.x < margin) { + this.velocity.x += turn_constant; + } else if (this.position.x > width - margin) { + this.velocity.x -= turn_constant; + } + + if (this.position.y < margin) { + this.velocity.y += turn_constant; + } else if (this.position.y > height - margin) { + this.velocity.y -= turn_constant; + } + + // Limit speed + if (this.velocity.mag() > max_speed) { + this.velocity.normalize().mult(max_speed); + } else if (this.velocity.mag() < min_speed) { + this.velocity.normalize().mult(min_speed); + } + + // Update the trail + if (trail) { + this.trail.shift(); + this.trail.push(this.position.copy()); + } + + // Move the boid + this.position.add(this.velocity); + } + + draw(trail = false, shape = "Triangle", direction = false, range = false, avoid = false) { + // Draw the boid as a sphere with a line indicating its direction + if (shape == "Circle") { + stroke(255, 0, 0); + strokeWeight(15); + point(this.position.x, this.position.y); + } else { + // Draw the boid as a triangle + strokeWeight(0); + fill(255, 0, 0); + let angle = this.velocity.heading(); + let a = createVector(0, -30).rotate(angle + PI / 2).add(this.position); + let b = createVector(10, 0).rotate(angle + PI / 2).add(this.position); + let c = createVector(-10, 0).rotate(angle + PI / 2).add(this.position); + triangle(a.x, a.y, b.x, b.y, c.x, c.y); + } + + if (direction) { + stroke(0, 0, 255); + strokeWeight(2); + line(this.position.x, this.position.y, this.position.x + (this.velocity.x * 5), this.position.y + (this.velocity.y * 5)); + } + + // Add a trail behind the boid + if (trail) { + for (let i = 0; i < this.trail.length; i++) { + let alpha = map(i, 0, this.trail.length, 0, 255); + fill(255, 255, 255, alpha); + stroke(255, 255, 255, alpha); + strokeWeight(5); + point(this.trail[i].x, this.trail[i].y); + } + } + + if (range) { + noFill(); + stroke(0, 255, 0, 50); + strokeWeight(2); + ellipse(this.position.x, this.position.y, visible_range * 2); + } + + if (avoid) { + noFill(); + stroke(255, 0, 0, 50); + strokeWeight(2); + ellipse(this.position.x, this.position.y, avoidance_range * 2); + } + } +} \ No newline at end of file diff --git a/FriendlyBoid.js b/FriendlyBoid.js new file mode 100644 index 0000000..64e79ee --- /dev/null +++ b/FriendlyBoid.js @@ -0,0 +1,200 @@ +class FriendlyBoid { + constructor(x, y) { + this.position = createVector(x, y); + this.velocity = createVector(0, 0); + + this.trail = []; + for (let i = 0; i < 100; i++) { + this.trail.push(this.position.copy()); + } + } + + update(trail = false, update = true) { + if (!update) { + return; + } + + // Separation + let close_dx = 0; + let close_dy = 0; + + for (let boid of friendly_boids) { + if (boid != this) { + if (dist(this.position.x, this.position.y, boid.position.x, boid.position.y) < avoidance_range) { + close_dx += this.position.x - boid.position.x; + close_dy += this.position.y - boid.position.y; + } + } + } + + this.velocity.add(createVector(close_dx, close_dy).normalize().mult(avoidance_constant)); + + + // Alignment + let avg_dx = 0; + let avg_dy = 0; + let total_neighbours = 0; + + for (let boid of friendly_boids) { + if (boid != this) { + if (dist(this.position.x, this.position.y, boid.position.x, boid.position.y) < visible_range) { + avg_dx += boid.velocity.x; + avg_dy += boid.velocity.y; + + total_neighbours++; + } + } + } + + if (total_neighbours > 0) { + avg_dx /= total_neighbours; + avg_dy /= total_neighbours; + } + + this.velocity.add(createVector(avg_dx, avg_dy).normalize().mult(alignment_constant)); + + + // Cohesion + total_neighbours = 0; + let avg_x = 0; + let avg_y = 0; + + for (let boid of friendly_boids) { + if (boid != this) { + if (dist(this.position.x, this.position.y, boid.position.x, boid.position.y) < visible_range) { + avg_x += boid.position.x; + avg_y += boid.position.y; + + total_neighbours++; + } + } + } + + if (total_neighbours > 0) { + avg_x /= total_neighbours; + avg_y /= total_neighbours; + } + + this.velocity.add(createVector(avg_x - this.position.x, avg_y - this.position.y).normalize().mult(cohesion_constant)); + + + // POI Attraction + let poi_dx = 0; + let poi_dy = 0; + for (let poi of pointsOfInterest) { + if (dist(this.position.x, this.position.y, poi.position.x, poi.position.y) < poi_attraction_range) { + poi_dx += poi.position.x - this.position.x; + poi_dy += poi.position.y - this.position.y; + } + } + + this.velocity.add(createVector(poi_dx, poi_dy).normalize().mult(interest_constant)); + + // POI Repulsion + close_dx = 0; + close_dy = 0; + + for (let poi of pointsOfInterest) { + if (dist(this.position.x, this.position.y, poi.position.x, poi.position.y) < poi_protection_range) { + close_dx += this.position.x - poi.position.x; + close_dy += this.position.y - poi.position.y; + } + } + + this.velocity.add(createVector(close_dx, close_dy).normalize().mult(poi_avoidance_constant)); + + + // Enemy Boid Avoidance + close_dx = 0; + close_dy = 0; + + for (let boid of enemy_boids) { + if (dist(this.position.x, this.position.y, boid.position.x, boid.position.y) < enemy_avoidance_range) { + close_dx += (this.position.x - boid.position.x); + close_dy += (this.position.y - boid.position.y); + } + } + + this.velocity.add(createVector(close_dx, close_dy).normalize().mult(enemy_avoidance_constant)); + + + // Turn around if it's going out of bounds + if (this.position.x < margin) { + this.velocity.x += turn_constant; + } else if (this.position.x > width - margin) { + this.velocity.x -= turn_constant; + } + + if (this.position.y < margin) { + this.velocity.y += turn_constant; + } else if (this.position.y > height - margin) { + this.velocity.y -= turn_constant; + } + + + // Limit speed + if (this.velocity.mag() > max_speed) { + this.velocity.normalize().mult(max_speed); + } else if (this.velocity.mag() < min_speed) { + this.velocity.normalize().mult(min_speed); + } + + // Update the trail + if (trail) { + this.trail.shift(); + this.trail.push(this.position.copy()); + } + + // Move the boid + this.position.add(this.velocity); + } + + draw(trail = false, shape = "Triangle", direction = false, range = false, avoid = false) { + // Draw the boid as a sphere with a line indicating its direction + if (shape == "Circle") { + stroke(0, 255, 0); + strokeWeight(15); + point(this.position.x, this.position.y); + } else { + // Draw the boid as a triangle + strokeWeight(0); + fill(0, 255, 0); + let angle = this.velocity.heading(); + let a = createVector(0, -30).rotate(angle + PI / 2).add(this.position); + let b = createVector(10, 0).rotate(angle + PI / 2).add(this.position); + let c = createVector(-10, 0).rotate(angle + PI / 2).add(this.position); + triangle(a.x, a.y, b.x, b.y, c.x, c.y); + } + + if (direction) { + stroke(0, 0, 255); + strokeWeight(2); + line(this.position.x, this.position.y, this.position.x + (this.velocity.x * 5), this.position.y + (this.velocity.y * 5)); + } + + // Add a trail behind the boid + if (trail) { + for (let i = 0; i < this.trail.length; i++) { + let alpha = map(i, 0, this.trail.length, 0, 255); + fill(255, 255, 255, alpha); + stroke(255, 255, 255, alpha); + strokeWeight(5); + point(this.trail[i].x, this.trail[i].y); + } + } + + if (range) { + noFill(); + stroke(0, 255, 0, 50); + strokeWeight(2); + ellipse(this.position.x, this.position.y, visible_range * 2); + } + + if (avoid) { + noFill(); + stroke(255, 0, 0, 50); + strokeWeight(2); + ellipse(this.position.x, this.position.y, avoidance_range * 2); + } + } +} \ No newline at end of file diff --git a/FriendlyBoidOptimised.js b/FriendlyBoidOptimised.js new file mode 100644 index 0000000..3e91021 --- /dev/null +++ b/FriendlyBoidOptimised.js @@ -0,0 +1,161 @@ +class FriendlyBoidOptimised { + constructor(x, y) { + this.position = createVector(x, y); + this.velocity = createVector(0, 0); + + this.trail = []; + for (let i = 0; i < 100; i++) { + this.trail.push(this.position.copy()); + } + } + + update(trail = false, update = true) { + if (!update) { + return; + } + + let close_dx = 0; + let close_dy = 0; + let avg_dx = 0; + let avg_dy = 0; + let avg_x = 0; + let avg_y = 0; + let total_neighbours = 0; + let poi_avoid_dx = 0; + let poi_avoid_dy = 0; + let poi_attract_dx = 0; + let poi_attract_dy = 0; + let enemy_close_dx = 0; + let enemy_close_dy = 0; + + for (let boid of friendly_boids) { + if (boid != this) { + if (dist(this.position.x, this.position.y, boid.position.x, boid.position.y) < avoidance_range) { + close_dx += this.position.x - boid.position.x; + close_dy += this.position.y - boid.position.y; + } + + if (dist(this.position.x, this.position.y, boid.position.x, boid.position.y) < visible_range) { + avg_dx += boid.velocity.x; + avg_dy += boid.velocity.y; + + avg_x += boid.position.x; + avg_y += boid.position.y; + + total_neighbours++; + } + } + } + + for (let poi of pointsOfInterest) { + if (dist(this.position.x, this.position.y, poi.position.x, poi.position.y) < poi_attraction_range) { + poi_attract_dx += poi.position.x - this.position.x; + poi_attract_dy += poi.position.y - this.position.y; + } + if (dist(this.position.x, this.position.y, poi.position.x, poi.position.y) < poi_protection_range) { + poi_avoid_dx += this.position.x - poi.position.x; + poi_avoid_dy += this.position.y - poi.position.y; + } + } + + for (let boid of enemy_boids) { + if (dist(this.position.x, this.position.y, boid.position.x, boid.position.y) < enemy_avoidance_range) { + enemy_close_dx += (this.position.x - boid.position.x); + enemy_close_dy += (this.position.y - boid.position.y); + } + } + + if (total_neighbours > 0) { + avg_dx /= total_neighbours; + avg_dy /= total_neighbours; + + avg_x /= total_neighbours; + avg_y /= total_neighbours; + } + + this.velocity.add(createVector(close_dx, close_dy).normalize().mult(avoidance_constant)); + this.velocity.add(createVector(avg_dx, avg_dy).normalize().mult(alignment_constant)); + this.velocity.add(createVector(avg_x - this.position.x, avg_y - this.position.y).normalize().mult(cohesion_constant)); + this.velocity.add(createVector(poi_attract_dx, poi_attract_dy).normalize().mult(interest_constant)); + this.velocity.add(createVector(poi_avoid_dx, poi_avoid_dy).normalize().mult(poi_avoidance_constant)); + this.velocity.add(createVector(enemy_close_dx, enemy_close_dy).normalize().mult(enemy_avoidance_constant)); + + // Turn around if it's going out of bounds + if (this.position.x < margin) { + this.velocity.x += turn_constant; + } else if (this.position.x > width - margin) { + this.velocity.x -= turn_constant; + } + + if (this.position.y < margin) { + this.velocity.y += turn_constant; + } else if (this.position.y > height - margin) { + this.velocity.y -= turn_constant; + } + + // Limit speed + if (this.velocity.mag() > max_speed) { + this.velocity.normalize().mult(max_speed); + } else if (this.velocity.mag() < min_speed) { + this.velocity.normalize().mult(min_speed); + } + + // Update the trail + if (trail) { + this.trail.shift(); + this.trail.push(this.position.copy()); + } + + // Move the boid + this.position.add(this.velocity); + } + + draw(trail = false, shape = "Triangle", direction = false, range = false, avoid = false) { + // Draw the boid as a sphere with a line indicating its direction + if (shape == "Circle") { + stroke(0, 255, 0); + strokeWeight(15); + point(this.position.x, this.position.y); + } else { + // Draw the boid as a triangle + strokeWeight(0); + fill(0, 255, 0); + let angle = this.velocity.heading(); + let a = createVector(0, -30).rotate(angle + PI / 2).add(this.position); + let b = createVector(10, 0).rotate(angle + PI / 2).add(this.position); + let c = createVector(-10, 0).rotate(angle + PI / 2).add(this.position); + triangle(a.x, a.y, b.x, b.y, c.x, c.y); + } + + if (direction) { + stroke(0, 0, 255); + strokeWeight(2); + line(this.position.x, this.position.y, this.position.x + (this.velocity.x * 5), this.position.y + (this.velocity.y * 5)); + } + + // Add a trail behind the boid + if (trail) { + for (let i = 0; i < this.trail.length; i++) { + let alpha = map(i, 0, this.trail.length, 0, 255); + fill(255, 255, 255, alpha); + stroke(255, 255, 255, alpha); + strokeWeight(5); + point(this.trail[i].x, this.trail[i].y); + } + } + + if (range) { + noFill(); + stroke(0, 255, 0, 50); + strokeWeight(2); + ellipse(this.position.x, this.position.y, visible_range * 2); + } + + if (avoid) { + noFill(); + stroke(255, 0, 0, 50); + strokeWeight(2); + ellipse(this.position.x, this.position.y, avoidance_range * 2); + } + } +} \ No newline at end of file diff --git a/PointOfInterest.js b/PointOfInterest.js new file mode 100644 index 0000000..bfc9bc6 --- /dev/null +++ b/PointOfInterest.js @@ -0,0 +1,25 @@ +class PointOfInterest { + constructor(x, y) { + this.position = createVector(x, y); + } + + draw(protect_range = false, attract_range = false) { + stroke(255, 255, 0); + strokeWeight(10); + point(this.position.x, this.position.y); + + if (protect_range) { + noFill(); + stroke(255, 255, 0, 100); + strokeWeight(2); + ellipse(this.position.x, this.position.y, poi_protection_range * 2); + } + + if (attract_range) { + noFill(); + stroke(255, 255, 0, 100); + strokeWeight(2); + ellipse(this.position.x, this.position.y, poi_attraction_range * 2); + } + } +} \ No newline at end of file diff --git a/index.html b/index.html index 45cdefb..195a972 100644 --- a/index.html +++ b/index.html @@ -14,5 +14,10 @@ + + + + + diff --git a/sketch.js b/sketch.js index 79d548f..3a76664 100644 --- a/sketch.js +++ b/sketch.js @@ -1,5 +1,206 @@ +// START VISUAL SETTINGS +let settings_dock = "left"; // Options: left, bottom +// END VISUAL SETTINGS + +let friendly_boids = []; +let enemy_boids = [] +let pointsOfInterest = []; +let texts = []; +let options = []; +let is_updating = true; +let canvas_position = 0; +let settings_displacement = 0; + +if (settings_dock == 'left') { + setting_displacement = 0; + canvas_position = 400; +} else if (settings_dock == 'bottom') { + setting_displacement = screen.availHeight; + canvas_position = 0; +} + +let friendly_boid_count = 75; +let enemy_boid_count = 10; +let enemy_boid_attack_constant = 0.5; +let enemy_avoidance_constant = 0.5; +let enemy_avoidance_range = 100; +let avoidance_constant = 0.05; +let alignment_constant = 0.05; +let cohesion_constant = 0.005; +let interest_constant = 0.1; +let poi_avoidance_constant = 0.5; +let turn_constant = 0.3; +let margin = 100; +let avoidance_range = 15; +let visible_range = 40; +let poi_attraction_range = 500; +let poi_protection_range = 50; +let max_speed = 5; +let min_speed = 3; + function setup() { + // Position the canvas to the right of the settings + let canvas = createCanvas(screen.availWidth - canvas_position, screen.availHeight); + canvas.position(canvas_position, 0); + + for (let i = 0; i < friendly_boid_count; i++) { + // friendly_boids.push(new FriendlyBoid(random(width), random(height))); + friendly_boids.push(new FriendlyBoidOptimised(random(width), random(height))); + } + + for (let i = 0; i < enemy_boid_count; i++) { + // enemy_boids.push(new EnemyBoid(random(width), random(height))); + enemy_boids.push(new EnemyBoidOptimised(random(width), random(height))); + } + + title = createP("

Boid settings

"); + title.position(10, -25 + setting_displacement); + + texts.push(createP("Friendly boid count:")); + options.push(createSlider(0, 250, friendly_boid_count, 1)); + texts.push(createP("Enemy boid count:")); + options.push(createSlider(0, 100, enemy_boid_count, 1)); + texts.push(createP("Enemy boid attack:")); + options.push(createSlider(0, 5, enemy_boid_attack_constant, 0.25)); + texts.push(createP("Enemy avoidance:")); + options.push(createSlider(0, 2, enemy_avoidance_constant, 0.1)); + texts.push(createP("Enemy avoidance range:")); + options.push(createSlider(0, 200, enemy_avoidance_range, 10)); + texts.push(createP("Friendly avoidance:")); + options.push(createSlider(0, 1, avoidance_constant, 0.01)); + texts.push(createP("Friendly avoidance range:")); + options.push(createSlider(0, 100, avoidance_range, 5)); + texts.push(createP("Show avoidance range:")); + options.push(createCheckbox('', false)); + texts.push(createP("Alignment:")); + options.push(createSlider(0, 1, alignment_constant, 0.01)); + texts.push(createP("Cohesion:")); + options.push(createSlider(0, 0.1, cohesion_constant, 0.001)); + texts.push(createP("Visible range:")); + options.push(createSlider(0, 100, visible_range, 5)); + texts.push(createP("Show visible range:")); + options.push(createCheckbox('', false)); + texts.push(createP("Max speed:")); + options.push(createSlider(0, 25, max_speed, 1)); + texts.push(createP("Min speed:")); + options.push(createSlider(0, 25, min_speed, 1)); + texts.push(createP("Margin:")); + options.push(createSlider(0, 200, margin, 10)); + texts.push(createP("Show margin:")); + options.push(createCheckbox('', false)); + texts.push(createP("POI attraction range:")); + options.push(createSlider(0, 1000, poi_attraction_range, 5)); + texts.push(createP("Show POI attraction range:")); + options.push(createCheckbox('', false)); + texts.push(createP("POI protection range:")); + options.push(createSlider(0, 500, poi_protection_range, 5)); + texts.push(createP("Show POI protection range:")); + options.push(createCheckbox('', false)); + texts.push(createP("Show trail:")); + options.push(createCheckbox('', false)); + texts.push(createP("Show velocity:")); + options.push(createCheckbox('', false)); + texts.push(createP("Boid shape:")); + options.push(createSelect()); + texts.push(createP("Use the + or - keys to add and remove POIs.")); + + x_pos = 10; + y_pos = 50 + setting_displacement; + for (let text of texts) { + text.position(x_pos, y_pos); + y_pos += 25; + } + + x_pos = 190; + y_pos = 65 + setting_displacement; + for (let option of options) { + option.position(x_pos, y_pos); + option.size(200); + y_pos += 25; + } + + options[22].option("Triangle"); + options[22].option("Circle"); + + frameRate(60); } + function draw() { + friendly_boid_count = options[0].value(); + enemy_boid_count = options[1].value(); + enemy_boid_attack_constant = options[2].value(); + enemy_avoidance_constant = options[3].value(); + enemy_avoidance_range = options[4].value(); + avoidance_constant = options[5].value(); + avoidance_range = options[6].value(); + alignment_constant = options[8].value(); + cohesion_constant = options[9].value(); + visible_range = options[10].value(); + max_speed = options[12].value(); + min_speed = options[13].value(); + poi_attraction_range = options[16].value(); + poi_protection_range = options[18].value(); + + if (friendly_boids.length < friendly_boid_count) { + // friendly_boids.push(new FriendlyBoid(random(width), random(height))); + friendly_boids.push(new FriendlyBoidOptimised(random(width), random(height))); + } else if (friendly_boids.length > friendly_boid_count) { + friendly_boids.pop(); + } + + if (enemy_boids.length < enemy_boid_count) { + // enemy_boids.push(new EnemyBoid(random(width), random(height))); + enemy_boids.push(new EnemyBoidOptimised(random(width), random(height))); + } else if (enemy_boids.length > enemy_boid_count) { + enemy_boids.pop(); + } + background(150); + + for (let boid of friendly_boids) { + boid.update(trail = options[20].checked(), update = is_updating); + boid.draw(trail = options[20].checked(), shape = options[22].value(), direction = options[21].checked(), range = options[11].checked(), avoid = options[7].checked()); + } + + for (let boid of enemy_boids) { + boid.update(trail = options[20].checked(), update = is_updating); + boid.draw(trail = options[20].checked(), shape = options[22].value(), direction = options[21].checked(), range = options[11].checked(), avoid = options[7].checked()); + } + + for (let poi of pointsOfInterest) { + poi.draw(protect_range = options[19].checked(), attract_range = options[17].checked()); + } + + // Draw the margins + if (options[15].checked()) { + drawMargins(); + } +} + +function keyPressed() { + // Check if it was a right click + if (key == '=') { + pointsOfInterest.push(new PointOfInterest(mouseX, mouseY)); + } else if (key == '-') { + for (let i = pointsOfInterest.length - 1; i >= 0; i--) { + if (dist(mouseX, mouseY, pointsOfInterest[i].position.x, pointsOfInterest[i].position.y) < 10) { + pointsOfInterest.splice(i, 1); + } + } + } else if (key == ' ') { + if (is_updating) { + is_updating = false; + } else { + is_updating = true; + } + } } + +function drawMargins() { + margin = options[14].value(); + + stroke(150, 0, 0, 50); + strokeWeight(2); + noFill(); + rect(margin, margin, width - 2 * margin, height - 2 * margin); +} \ No newline at end of file diff --git a/style.css b/style.css index ed06f08..5f0de92 100644 --- a/style.css +++ b/style.css @@ -1,6 +1,7 @@ html, body { margin: 0; padding: 0; + overflow: hidden; } canvas {