Movement flag.

master
Ben Burlingham 8 years ago
parent c5b2c33ed8
commit 844e890605
  1. 17
      index.html
  2. 7
      js/animation.js
  3. 1
      js/animation1b.js
  4. 1
      js/animation2b.js
  5. 11
      js/animation3a.js
  6. 9
      js/animation3b.js
  7. 35
      js/arc.js
  8. 18
      js/bundle.js
  9. 2
      js/controls.js
  10. 15
      js/index.js
  11. 30
      js/particle.js

@ -18,7 +18,7 @@
<li>Move in organic, natural arcs, with no sudden pivots or swerves</li>
<li>Support large swarms of particles</li>
<li>Be able to evade obstacles</li>
<li>Exhibit flocking behavior</li>
<li>Simulate cohesive flocking behavior</li>
</ul>
</blockquote>
@ -67,19 +67,26 @@
<div class='outerContainer' id='2b'></div>
<p>
The last goal was to play around with flocking behaviors: cohesion, separation, and alignment.
Here's each of the three, with the vision grid visualized:
<h4>Exploration 3: Cohesive Flocking Behavior</h4>
</p>
<p>
<h4>Exploration 3: Flocking patterns</h4>
Using the vision grid and the circular geometry, particles determine and follow a leader. At the start of the animation,
there are no leaders. When particles see each other, one of them is chosen as a leader. The others follow. If other particles
see a leader, they follow it.
</p>
<p>
Each particle recalls its previous location, and compares it to their current location to see if they're moving
towards the leader or not. Each frame iterates the particle closer to its leader. The leader(s) continue
moving freely.
</p>
<div class='outerContainer' id='3a'></div>
<p>
The exploration is now complete: arc-based movement, independent AIs, grid-based vision,
following patterns of cohesion, separation, alignment, with hazards, on a large scale:
hazards, and cohesive flocking behavior, on a large scale:
</p>
<div class='outerContainer' id='3b'></div>

@ -3,14 +3,15 @@ import Grid from './grid';
import Particle from './particle';
import Controls from './controls';
import Random from './random';
import { CONTROLS, ENTITIES } from './enums';
import { BEHAVIOR, CONTROLS, ENTITIES } from './enums';
function Animation(observables, id, showHazards) {
function Animation(observables, id, behavior) {
this.id = id;
this.observables = observables;
this.particles = [];
this.grid = new Grid();
this.fpsInterval = null;
this.behavior = behavior || BEHAVIOR.FREE;
this.container = document.createElement('div');
this.container.className = 'animationContainer';
@ -38,7 +39,7 @@ Animation.prototype.subscribeCount = function(count) {
}
while (this.particles.length < count) {
const p = new Particle(this.container, bounds, this.grid, this.observables);
const p = new Particle(this.container, bounds, this.grid, this.observables, this.behavior);
this.particles.push(p);
}
}

@ -5,6 +5,7 @@ export default function(destroy$) {
const id = '1b';
const config = {
id,
count: 200,
maxCount: 1000
};

@ -5,6 +5,7 @@ export default function(destroy$) {
const id = '2b';
const config = {
id,
count: 200,
maxCount: 1000
};

@ -1,18 +1,15 @@
import Animation from './animation';
import Controls from './controls';
import { BEHAVIOR } from './enums';
export default function(destroy$) {
const id = '3a';
const config = {
id,
count: 5,
maxCount: 1000,
showAlignmentControl: true,
showCohesionControl: true,
showSeparationControl: true,
// showVisionGridControl: true
maxCount: 10,
showVisionGridControl: true
};
const observables = Controls(destroy$, config);
new Animation(observables, id);
new Animation(observables, id, BEHAVIOR.COHESION);
}

@ -1,16 +1,15 @@
import Animation from './animation';
import Controls from './controls';
import { BEHAVIOR } from './enums';
export default function(destroy$) {
const id = '3b';
const config = {
id,
maxCount: 10,
showAlignmentControl: true,
showCohesionControl: true,
showSeparationControl: true
count: 200,
maxCount: 1000
};
const observables = Controls(destroy$, config);
new Animation(observables, id).addHazards();
new Animation(observables, id, BEHAVIOR.COHESION);
}

@ -1,6 +1,11 @@
import { ENTITIES, RAD } from './enums';
import Random from './random';
// "How much of movement is in the correct direction"
const rigidity = 0.9;
// "How close to the leader is enough"
const sensitivity = 30;
const Arc = {
create: function(bounds, grid) {
let arc = {
@ -50,7 +55,6 @@ const Arc = {
arc.endX = arc.centerX + arc.radius * arc.cosTheta;
arc.endY = arc.centerY - arc.radius * arc.sinTheta;
// Overflow.
arc = Arc.overflow(arc, bounds);
return arc;
@ -119,13 +123,7 @@ const Arc = {
return arc;
},
match: function(arc, arcToMatch) {
},
follow: function(arc, arcToFollow) {
arc = (arc.clockwise !== arcToFollow.clockwise ? Arc.reverse(arc) : arc);
const prevD = Math.pow(
Math.pow(arcToFollow.endX - arc.prevEndX, 2) +
Math.pow(arcToFollow.endY - arc.prevEndY, 2)
@ -136,23 +134,26 @@ const Arc = {
Math.pow(arcToFollow.endY - arc.endY, 2)
, 0.5);
// "How much of movement is in the correct direction"
const ratio = (prevD - currD) / arc.speed;
// TODO adjust speed
// TODO turn in the correct direction
const rigidityCoeff = (prevD - currD) / arc.speed;
if (currD < 20) {
// if (Math.abs(arc.centerX - arcToFollow.centerX) < 10 && Math.abs(arc.centerY - arcToFollow.centerX) < 10) {
if (currD < sensitivity) {
arc = (arc.clockwise !== arcToFollow.clockwise ? Arc.reverse(arc) : arc);
arc = Arc.changeRadius(arc, arcToFollow.radius);
if (arc.speed > arcToFollow.speed) {
arc = Arc.changeSpeed(arc, arc.speed - 1);
} if (arc.speed < arcToFollow.speed) {
arc = Arc.changeSpeed(arc, arc.speed + 1);
}
} else if (ratio < 0.8) {
// arc = (ratio < 0 ? Arc.reverse(arc) : arc);
} else if (rigidityCoeff < rigidity) {
arc = Arc.changeRadius(arc, 20);
if (arc.speed > arcToFollow.speed - 1) {
arc = Arc.changeSpeed(arc, arc.speed - 1);
}
} else {
arc = Arc.changeRadius(arc, 400);
arc = Arc.changeRadius(arc, 4000);
if (arc.speed < (arcToFollow.speed + 2)) {
arc = Arc.changeSpeed(arc, arc.speed + 1);

File diff suppressed because one or more lines are too long

2
js/controls.js vendored

@ -119,7 +119,7 @@ function createSpeedControl(container) {
const slider = document.createElement('input');
slider.type = 'range';
slider.min = 1;
slider.max = 15;
slider.max = 10;
slider.value = 4;
slider.className = 'controls-range-input';

@ -14,6 +14,10 @@ require('../css/controls.scss');
window.addEventListener('load', () => {
const destroy$ = new Rx.BehaviorSubject(null);
window.addEventListener('blur', () => {
destroy$.next('all');
});
Animation1a(destroy$);
Animation1b(destroy$);
Animation2a(destroy$);
@ -23,7 +27,6 @@ window.addEventListener('load', () => {
});
// TODO remove bottom padding from Disqus
// TODO fix "hangup" small radius evade bug
// TODO sort out particle nextframe
// TODO abs positioning on controls elements so order doesn't matter
// TODO BehaviorSubject listener on bounds change
@ -31,9 +34,11 @@ window.addEventListener('load', () => {
// TODO grid touches
// TODO start with n particles doesn't update slider
// TODO leader not quite right, if 2 particles, sometimes ignored
// TODO Randomize leaders every 30 sec
// TODO ANIM1ab free movement
// INTERESTING CONTROLS:
// sensitivity
// rigidity
// show leaders
// TODO ANIM3a streamline updateLeader
// TODO ANIM3b separation
// TODO ANIM3c alignment
// TURN THE CORRECT DIRECTION - HUGE EFFICIENCY INCREASE

@ -5,9 +5,9 @@ import Random from './random';
// ===== Constructor =====
function Particle(parent, bounds, globalGrid, observables) {
function Particle(parent, bounds, globalGrid, observables, behavior) {
this.config = {
behavior: BEHAVIOR.COHESION,
behavior: behavior || BEHAVIOR.FREE,
bounds,
color: Random.color(),
gridSize: 5,
@ -35,7 +35,7 @@ function Particle(parent, bounds, globalGrid, observables) {
parent.appendChild(this.nodes.container);
this.leader = null;
this.isLeader = false;
this.leaderTime = 0;
this.arc = Arc.create(bounds, this.grids.global);
this.arc.length = 3;
@ -89,7 +89,7 @@ Particle.prototype.remove = function() {
delete this.nodes;
}
Particle.prototype.subscribeNextFrame = function() {
Particle.prototype.subscribeNextFrame = function(n) {
this.grids.global.deletePoint({
x: this.arc.endX,
y: this.arc.endY,
@ -108,7 +108,7 @@ Particle.prototype.subscribeNextFrame = function() {
const { hazards, particles } = look(this.arc, this.grids);
if (hazards.length > 0) {
// this.arc = Arc.evade(this.arc);
this.arc = Arc.evade(this.arc);
}
this.updateLeader(particles);
@ -119,8 +119,8 @@ Particle.prototype.subscribeNextFrame = function() {
type: ENTITIES.PARTICLE
}, this);
repaintContainer(this.nodes.container, this.arc);
repaintBody(this.nodes.body, this.arc, this.isLeader);
repaintContainer(this.nodes.container, this.arc, this.leaderTime);
repaintBody(this.nodes.body, this.arc, this.leaderTime);
repaintCircle(this.nodes.circle, this.arc);
repaintVisionGrid(this.nodes.visionGrid, this.arc, this.grids);
}
@ -162,10 +162,10 @@ Particle.prototype.updateLeader = function(particles) {
const candidates = particles
.filter(v => v.leader ? (v.leader.id !== this.id) : true);
const leader = candidates.find(v => v.isLeader) || candidates[0];
const leader = candidates.find(v => (v.leaderTime > 0)) || candidates[0];
if (leader !== undefined) {
leader.isLeader = true;
leader.leaderTime = 1;
this.leader = leader;
}
}
@ -183,8 +183,8 @@ Particle.prototype.updateLeader = function(particles) {
this.leader = this.leader.leader;
}
if (this.isLeader) {
this.isLeader = false;
if (this.leaderTime > 0) {
this.leaderTime = 0;
}
// Beware of circular leadership, where a leader sees its tail.
@ -298,19 +298,21 @@ function updateVisionGrid(arc, config, grids) {
}
// ===== DOM RENDERING =====
function repaintContainer(node, arc) {
function repaintContainer(node, arc, leaderTime) {
node.style.left = `${arc.endX}px`;
node.style.top = `${arc.endY}px`;
(leaderTime > 0) ? node.style.zIndex = '2000' : node.style.zIndex = '2';
}
function repaintBody(node, arc, isLeader) {
function repaintBody(node, arc, leaderTime) {
const rad = arc.clockwise
? RAD.t180 - arc.theta
: RAD.t360 - arc.theta;
node.style.transform = `rotate(${rad + RAD.t45}rad)`;
isLeader ? node.style.outline = '1px solid red' : node.style.outline = '';
(leaderTime > 0) ? node.style.border = '3px dotted #fff' : node.style.border = '';
}
function repaintCircle(node, arc) {

Loading…
Cancel
Save