Progress on global animation management.

master
Ben Burlingham 8 years ago
parent b861b78fba
commit ea7210309f
  1. 16
      index.html
  2. 105
      js/animation.js
  3. 142
      js/animation1a.js
  4. 72
      js/animation1b.js
  5. 31
      js/arc.js
  6. 1560
      js/bundle.js
  7. 191
      js/controls.js
  8. 2
      js/enums.js
  9. 30
      js/index.js
  10. 108
      js/particle.js
  11. 6
      js/random.js

@ -15,7 +15,7 @@
Explore the RxJs API by managing moving particle systems. The systems should: Explore the RxJs API by managing moving particle systems. The systems should:
<ul> <ul>
<li>Use self-aware AI, not a global AI</li> <li>Use self-aware AI, not a global AI</li>
<li>Have particle movement that feels calm and natural</li> <li>Move in organic, natural arcs, with no sudden pivots or swerves</li>
<li>Support large swarms of particles</li> <li>Support large swarms of particles</li>
<li>Be able to evade obstacles</li> <li>Be able to evade obstacles</li>
<li>Exhibit flocking behavior</li> <li>Exhibit flocking behavior</li>
@ -35,21 +35,15 @@
groups. The current design uses circular paths that allow smooth changes in both direction and rotation. groups. The current design uses circular paths that allow smooth changes in both direction and rotation.
</p> </p>
<div class='outerContainer'> <div class='outerContainer' id='1a'></div>
<div id="animation1a" class='animationContainer'></div>
<div id="controls1a" class='controlsContainer'></div>
</div>
<p> <p>
This method can handle quite a few particles. This method can handle quite a few particles.
</p> </p>
<div class='outerContainer'> <div class='outerContainer' id='1b'></div>
<div id="animation1b" class='animationContainer'></div>
<div id="controls1b" class='controlsContainer'></div>
</div>
<p> <!-- <p>
<h4>Exploration 2: Grid-based vision</h4> <h4>Exploration 2: Grid-based vision</h4>
</p> </p>
@ -79,7 +73,7 @@
<div class='outerContainer'> <div class='outerContainer'>
<div id="animation3a" class='animationContainer'></div> <div id="animation3a" class='animationContainer'></div>
<div id="controls3a" class='controlsContainer'></div> <div id="controls3a" class='controlsContainer'></div>
</div> </div> -->
<script src='js/bundle.js'></script> <script src='js/bundle.js'></script>
<script src='/core/js/ui.js'></script> <script src='/core/js/ui.js'></script>

@ -4,30 +4,61 @@ import Particle from './particle';
import Controls from './controls'; import Controls from './controls';
import { CONTROLS, ENTITIES } from './enums'; import { CONTROLS, ENTITIES } from './enums';
function Animation() { function Animation(observables, id) {
this.id = id;
// TODO remove bottom padding from Disqus this.observables = observables;
// TODO fix "hangup" small radius evade bug this.particles = [];
// TODO don't load simulation until requested
// TODO sort out particle nextframe this.container = document.createElement('div');
this.container.className = 'animationContainer';
// TODO ANIM1ab free movement document.getElementById(id).appendChild(this.container);
// TODO ANIM3a streamline updateLeader this.observables.animating$.subscribe(this.updateAnimating.bind(this));
// TODO ANIM3b separation this.observables.count$.skip(1).subscribe(this.updateCount.bind(this));
// TODO ANIM3c alignment
}; // observables.circle$, PROBABLY WON'T NEED THESE, WILL BE In PARTICLE
// observables.randomize$,
Animation.prototype.destroy = function() { // observables.speed$
// this.observables.count$.next(99);
//<div class='animationContainer'></div>
// console.warn("Mounting Animation", this.id);
// console.warn('updateAnimating in Animation', isAnimating)
// this.isAnimating = isAnimating;
// if (isAnimating) {
// const fps$ = Rx.Observable.interval(1000 / 32)
// .takeWhile(_ => this.isAnimating);
//
// fps$.subscribe(this.nextFrame.bind(this));
// }
// this.container = container;
// this.particles = [];
// this.isAnimating = false;
// this.config = config;
// this.grid = new Grid();
// this.updateAnimating(false);
// this.updateCircle(config.circleControl);
// this.updateSpeed(config.speed);
// this.updateRandomize(this.config.randomizeControl);
// Must be last, after configs all set up and container is fully rendered.
// this.updateCount(this.config.count);
} }
Animation.prototype.subscriber = function({ key, value }) { Animation.prototype.updateAnimating = function(isAnimating) {
switch(key) { if (isAnimating === false) {
case CONTROLS.ANIMATING: this.updateAnimating(value); break; return;
case CONTROLS.COUNT: this.updateCount(value); break;
case CONTROLS.SPEED: this.updateSpeed(value); break;
} }
const fps$ = Rx.Observable.interval(1000 / 32)
.takeUntil(this.observables.animating$);
fps$.subscribe(this.nextFrame.bind(this));
} }
Animation.prototype.nextFrame = function() { Animation.prototype.nextFrame = function() {
@ -37,41 +68,39 @@ Animation.prototype.nextFrame = function() {
p.nextFrame(); p.nextFrame();
this.grid.deletePoint({ x: prevX, y: prevY, type: ENTITIES.PARTICLE }); // this.grid.deletePoint({ x: prevX, y: prevY, type: ENTITIES.PARTICLE });
this.grid.setPoint({ x: p.arc.endX, y: p.arc.endY, type: ENTITIES.PARTICLE }, p); // this.grid.setPoint({ x: p.arc.endX, y: p.arc.endY, type: ENTITIES.PARTICLE }, p);
}); });
} }
Animation.prototype.updateAnimating = function(isAnimating) {
this.options.animating = isAnimating;
if (isAnimating) {
const fps$ = Rx.Observable.interval(1000 / 32)
.takeWhile(_ => this.options.animating);
fps$.subscribe(this.nextFrame.bind(this));
}
}
Animation.prototype.updateCount = function(count) { Animation.prototype.updateCount = function(count) {
const bounds = this.container.getBoundingClientRect(); const bounds = this.container.getBoundingClientRect();
while (this.particles.length > count) { while (this.particles.length > count) {
const p = this.particles.pop(); const p = this.particles.pop();
this.grid.deletePoint({ x: p.arc.endX, y: p.arc.endY, type: ENTITIES.PARTICLE });
p.remove(); p.remove();
} }
while (this.particles.length < count) { while (this.particles.length < count) {
const p = new Particle(this.container, bounds, this.options, this.grid); const p = new Particle(this.container, bounds, this.config, this.grid);
this.grid.setPoint({ x: p.arc.endX, y: p.arc.endY, type: ENTITIES.PARTICLE }, p);
this.particles.push(p); this.particles.push(p);
} }
} }
Animation.prototype.updateSpeed = function(value) { Animation.prototype.updateSpeed = function(value) {
this.options.speed = value; // Options must be stored; they are passed to new particles.
this.config.speed = value;
this.particles.forEach(p => p.updateConfig({ speed: value })); this.particles.forEach(p => p.updateConfig({ speed: value }));
} }
Animation.prototype.updateCircle = function(value) {
this.config.showMovementCircle = value;
this.particles.forEach(p => p.updateConfig({ showMovementCircle: value }));
}
Animation.prototype.updateRandomize = function(value) {
this.config.randomize = value;
this.particles.forEach(p => p.updateConfig({ randomize: value }));
}
export default Animation; export default Animation;

@ -1,136 +1,16 @@
import Rx, { Observable } from 'rxjs'; import Animation from './animation';
import Grid from './grid';
import Particle from './particle';
import Controls from './controls'; import Controls from './controls';
import { CONTROLS } from './enums';
function Animation1a() { export default function(destroy$) {
this.options = { const id = '1a';
count: 1,
maxCount: 10,
randomize: true,
showMovementCircle: true,
speed: 4
};
this.container = document.getElementById('animation1a');
this.particles = [];
this.grid = new Grid();
this.movementCircleCtrl = createMovementCircleControl();
this.randomizeCtrl = createRandomizeControl();
const controls = new Controls(
document.getElementById('controls1a'),
this.options,
[this.movementCircleCtrl, this.randomizeCtrl]
);
const circle$ = Rx.Observable.fromEvent(this.movementCircleCtrl, 'change')
.map(evt => ({ key: CONTROLS.MOVEMENT_CIRCLE, value: evt.target.checked }));
const randomize$ = Rx.Observable.fromEvent(this.randomizeCtrl, 'change')
.map(evt => ({ key: CONTROLS.RANDOMIZE, value: evt.target.checked }));
const eventStack$ = controls.mount().merge(circle$, randomize$);
eventStack$.subscribe(this.subscriber.bind(this));
this.updateCount(this.options.count);
this.updateMovementCircle(this.options.showMovementCircle);
this.updateRandomize(this.options.randomize);
};
function createMovementCircleControl() {
const label = document.createElement('label');
label.className = 'controls-checkbox';
const text = document.createElement('span');
text.innerHTML = 'Show movement circle';
text.className = 'controls-checkbox-text';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'controls-checkbox-input';
label.appendChild(checkbox);
label.appendChild(text);
return label;
}
function createRandomizeControl(value) {
const label = document.createElement('label');
label.className = 'controls-checkbox';
const text = document.createElement('span');
text.innerHTML = 'Randomize movement';
text.className = 'controls-checkbox-text';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'controls-checkbox-input';
checkbox.checked = value;
label.appendChild(checkbox);
label.appendChild(text);
return label; const config = {
} id,
max: 10,
Animation1a.prototype.subscriber = function({ key, value }) { showCircleControl: true,
switch(key) { showRandomizeControl: true,
case CONTROLS.ANIMATING: this.updateAnimating(value); break; };
case CONTROLS.COUNT: this.updateCount(value); break;
case CONTROLS.MOVEMENT_CIRCLE: this.updateMovementCircle(value); break;
case CONTROLS.RANDOMIZE: this.updateRandomize(value); break;
case CONTROLS.SPEED: this.updateSpeed(value); break;
}
}
Animation1a.prototype.nextFrame = function() {
this.particles.forEach(p => p.nextFrame());
}
Animation1a.prototype.updateAnimating = function(isAnimating) {
this.options.animating = isAnimating;
if (isAnimating) {
const fps$ = Rx.Observable.interval(1000 / 32)
.takeWhile(_ => this.options.animating);
fps$.subscribe(this.nextFrame.bind(this));
}
}
Animation1a.prototype.updateCount = function(count) {
const bounds = this.container.getBoundingClientRect();
while (this.particles.length > count) {
delete this.particles.pop().remove();
}
while (this.particles.length < count) {
const p = new Particle(this.container, bounds, this.options, this.grid);
this.particles.push(p);
}
}
Animation1a.prototype.updateMovementCircle = function(value) {
this.options.showMovementCircle = value;
this.movementCircleCtrl.querySelector('[type=checkbox]').checked = value;
this.particles.forEach(p => p.updateConfig({ showMovementCircle: value }));
}
Animation1a.prototype.updateRandomize = function(value) {
this.options.randomize = value;
this.randomizeCtrl.querySelector('[type=checkbox]').checked = value;
this.particles.forEach(p => p.updateConfig({ randomize: value }));
}
Animation1a.prototype.updateSpeed = function(value) { const observables = Controls(destroy$, config);
this.options.speed = value; new Animation(observables, id);
this.particles.forEach(p => p.updateConfig({ speed: value }));
} }
export default Animation1a;

@ -1,69 +1,13 @@
import Rx, { Observable } from 'rxjs'; import Animation from './animation';
import Grid from './grid';
import Particle from './particle';
import Controls from './controls'; import Controls from './controls';
import { CONTROLS } from './enums';
function Animation1b() { export default function(destroy$) {
this.options = { const id = '1b';
count: 1, const config = {
maxCount: 1000, id,
speed: 8 max: 10
}; };
this.container = document.getElementById('animation1b'); const observables = Controls(destroy$, config);
this.particles = []; new Animation(observables, id);
this.grid = new Grid();
const controls = new Controls(
document.getElementById('controls1b'),
this.options
);
controls.mount().subscribe(this.subscriber.bind(this));
this.updateCount(this.options.count);
};
Animation1b.prototype.subscriber = function({ key, value }) {
switch(key) {
case CONTROLS.ANIMATING: this.updateAnimating(value); break;
case CONTROLS.COUNT: this.updateCount(value); break;
case CONTROLS.SPEED: this.updateSpeed(value); break;
}
} }
Animation1b.prototype.nextFrame = function() {
this.particles.forEach(p => p.nextFrame());
}
Animation1b.prototype.updateAnimating = function(isAnimating) {
this.options.animating = isAnimating;
if (isAnimating) {
const fps$ = Rx.Observable.interval(1000 / 32)
.takeWhile(_ => this.options.animating);
fps$.subscribe(this.nextFrame.bind(this));
}
}
Animation1b.prototype.updateCount = function(count) {
const bounds = this.container.getBoundingClientRect();
while (this.particles.length > count) {
delete this.particles.pop().remove();
}
while (this.particles.length < count) {
const p = new Particle(this.container, bounds, this.options, this.grid);
this.particles.push(p);
}
}
Animation1b.prototype.updateSpeed = function(value) {
this.options.speed = value;
this.particles.forEach(p => p.updateConfig({ speed: value }));
}
export default Animation1b;

@ -25,9 +25,9 @@ const Arc = {
arc = Arc.overflow(arc, bounds); arc = Arc.overflow(arc, bounds);
// If starting in a hazard, recurse. // If starting in a hazard, recurse.
if (grid.getPoint({ x: arc.endX, y: arc.endY, type: ENTITIES.HAZARD })) { // if (grid.getPoint({ x: arc.endX, y: arc.endY, type: ENTITIES.HAZARD })) {
arc = Arc.create(bounds, grid); // arc = Arc.create(bounds, grid);
} // }
return arc; return arc;
}, },
@ -128,18 +128,19 @@ const Arc = {
}, },
goto: function (arc, x, y, speed) { goto: function (arc, x, y, speed) {
const prevD = Math.pow(Math.pow(x - arc.prevEndX, 2) + Math.pow(y - arc.prevEndY, 2), 0.5); // WORKING!
const currD = Math.pow(Math.pow(x - arc.endX, 2) + Math.pow(y - arc.endY, 2), 0.5); // const prevD = Math.pow(Math.pow(x - arc.prevEndX, 2) + Math.pow(y - arc.prevEndY, 2), 0.5);
const ratio = (prevD - currD) / speed; // const currD = Math.pow(Math.pow(x - arc.endX, 2) + Math.pow(y - arc.endY, 2), 0.5);
// const ratio = (prevD - currD) / speed;
if (currD < 10) { //
throw new Error(`Arc end of (${arc.endX},${arc.endY}) is within 50px of (${x},${y})`); // if (currD < 10) {
} else if (ratio < 0.8) { // throw new Error(`Arc end of (${arc.endX},${arc.endY}) is within 50px of (${x},${y})`);
// arc = (ratio < 0 ? Arc.reverse(arc) : arc); // } else if (ratio < 0.8) {
arc = Arc.changeRadius(arc, 20); // // arc = (ratio < 0 ? Arc.reverse(arc) : arc);
} else { // arc = Arc.changeRadius(arc, 20);
arc = Arc.changeRadius(arc, 400); // } else {
} // arc = Arc.changeRadius(arc, 400);
// }
return arc; return arc;
}, },

File diff suppressed because one or more lines are too long

191
js/controls.js vendored

@ -1,122 +1,187 @@
import Rx, { Observable } from 'rxjs'; import Rx, { Observable } from 'rxjs';
import { CONTROLS } from './enums'; import { CONTROLS } from './enums';
function Controls(container, { animating, count, maxCount, speed }, customNodes) { export default function(destroy$, {
this.nodes = { count = 1,
animating: createAnimatingControl(animating), id,
count: createCountControl(count, maxCount), maxCount = 10,
speed: createSpeedControl(speed), showCircleControl = false,
showRandomizeControl = false
}) {
const container = document.createElement('div');
container.className = 'controlsContainer';
container.id = `controls${id}`;
document.getElementById(id).appendChild(container);
const observables = {
count$: createCountControl(container, 0, maxCount),
speed$: createSpeedControl(container),
circle$: showCircleControl ? createCircleControl(container) : undefined,
randomize$: showRandomizeControl ? createRandomizeControl(container) : undefined,
animating$: createAnimatingControl(container),
};
observables.animating$.subscribe((isAnimating) => {
if (isAnimating === true) {
destroy$.next(id);
if (observables.count$.getValue() === 0) {
observables.count$.next(count);
} }
container.appendChild(this.nodes.count);
container.appendChild(this.nodes.speed);
if (customNodes !== undefined) {
customNodes.forEach(node => { container.appendChild(node); });
}
container.appendChild(this.nodes.animating);
this.updateOptions({ key: CONTROLS.ANIMATING, value: animating });
this.updateOptions({ key: CONTROLS.COUNT, value: count });
this.updateOptions({ key: CONTROLS.SPEED, value: speed });
}
Controls.prototype.updateOptions = function({ key, value}) {
if (key === CONTROLS.COUNT) {
this.nodes.count.querySelector('span').innerHTML =
(value == 1) ? '1 particle' : `${value} particles`;
} }
});
if (key === CONTROLS.SPEED) { destroy$.subscribe((sourceId) => {
this.nodes.speed.querySelector('span').innerHTML = if (id !== sourceId) {
`Speed: ${value}`; observables.animating$.next(false);
observables.count$.next(0);
} }
});
if (key === CONTROLS.ANIMATING) { return observables;
this.nodes.animating.querySelector('span').innerHTML =
(value ? '&#9724; Stop' : '&#9654; Start');
}
} }
Controls.prototype.mount = function(customNodes) {
const animating$ = Rx.Observable.fromEvent(this.nodes.animating, 'change')
.map(evt => ({ key: CONTROLS.ANIMATING, value: evt.target.checked }));
const count$ = Rx.Observable.fromEvent(this.nodes.count, 'input')
.map(evt => ({ key: CONTROLS.COUNT, value: evt.target.value * 1 }));
const speed$ = Rx.Observable.fromEvent(this.nodes.speed, 'input')
.map(evt => ({ key: CONTROLS.SPEED, value: evt.target.value * 1 }));
const eventStack$ = Rx.Observable.merge(
animating$,
count$,
speed$
);
eventStack$.subscribe(this.updateOptions.bind(this));
return eventStack$;
}
function createAnimatingControl(value) { function createAnimatingControl(container) {
const label = document.createElement('label'); const label = document.createElement('label');
label.className = 'controls-label'; label.className = 'controls-label';
label.className = 'controls-animating'; label.className = 'controls-animating';
const text = document.createElement('span'); const text = document.createElement('span');
text.innerHTML = '...';
const checkbox = document.createElement('input'); const checkbox = document.createElement('input');
checkbox.type = 'checkbox'; checkbox.type = 'checkbox';
checkbox.checked = value; checkbox.checked = false;
label.appendChild(checkbox); label.appendChild(checkbox);
label.appendChild(text); label.appendChild(text);
return label; container.appendChild(label);
const animating$ = new Rx.BehaviorSubject(false);
label.addEventListener('change', (evt) => {
animating$.next(evt.target.checked);
});
animating$.subscribe((isAnimating) => {
text.innerHTML = (isAnimating ? '&#9724; Stop' : '&#9654; Start');
checkbox.checked = isAnimating;
});
return animating$;
} }
function createCountControl(value, max) { function createCountControl(container, initialValue, max) {
const label = document.createElement('label'); const label = document.createElement('label');
label.className = 'controls-range'; label.className = 'controls-range';
const text = document.createElement('span'); const text = document.createElement('span');
text.innerHTML = '...'; text.innerHTML = (initialValue == 1) ? '1 particle' : `${initialValue} particles`;
text.className = 'controls-range-text'; text.className = 'controls-range-text';
const slider = document.createElement('input'); const slider = document.createElement('input');
slider.type = 'range'; slider.type = 'range';
slider.min = 1; slider.min = 1;
slider.max = max; slider.max = max;
slider.value = value; slider.value = initialValue;
slider.className = 'controls-range-input'; slider.className = 'controls-range-input';
label.appendChild(text); label.appendChild(text);
label.appendChild(slider); label.appendChild(slider);
return label; container.appendChild(label);
const count$ = new Rx.BehaviorSubject(initialValue);
label.addEventListener('input', (evt) => {
count$.next(evt.target.value * 1);
});
count$.subscribe((value) => {
text.innerHTML = (value == 1) ? '1 particle' : `${value} particles`;
});
return count$;
} }
function createSpeedControl(value) { function createSpeedControl(container) {
const label = document.createElement('label'); const label = document.createElement('label');
label.className = 'controls-range'; label.className = 'controls-range';
const text = document.createElement('span'); const text = document.createElement('span');
text.className = 'controls-range-text'; text.className = 'controls-range-text';
text.innerHTML = 'Speed: 0';
const slider = document.createElement('input'); const slider = document.createElement('input');
slider.type = 'range'; slider.type = 'range';
slider.min = 1; slider.min = 1;
slider.max = 15; slider.max = 15;
slider.value = value; slider.value = 4;
slider.className = 'controls-range-input'; slider.className = 'controls-range-input';
label.appendChild(text); label.appendChild(text);
label.appendChild(slider); label.appendChild(slider);
return label; container.appendChild(label);
const speed$ = new Rx.BehaviorSubject(slider.value);
label.addEventListener('input', (evt) => {
speed$.next(evt.target.value * 1);
});
speed$.subscribe((value) => {
text.innerHTML = `Speed: ${value}`;
});
return speed$;
} }
export default Controls; function createCircleControl(container) {
const label = document.createElement('label');
label.className = 'controls-checkbox';
const text = document.createElement('span');
text.innerHTML = 'Show movement circle';
text.className = 'controls-checkbox-text';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'controls-checkbox-input';
checkbox.checked = true;
label.appendChild(checkbox);
label.appendChild(text);
container.appendChild(label);
const circle$ = Rx.Observable.fromEvent(label, 'change')
.map(evt => evt.target.checked);
return circle$;
}
function createRandomizeControl(container) {
const label = document.createElement('label');
label.className = 'controls-checkbox';
const text = document.createElement('span');
text.innerHTML = 'Randomize movement';
text.className = 'controls-checkbox-text';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'controls-checkbox-input';
checkbox.checked = true;
label.appendChild(checkbox);
label.appendChild(text);
container.appendChild(label);
const circle$ = Rx.Observable.fromEvent(label, 'change')
.map(evt => evt.target.checked);
return randomize$;
}

@ -8,8 +8,8 @@ const RAD = {
const CONTROLS = { const CONTROLS = {
ANIMATING: 'animating', ANIMATING: 'animating',
CIRCLE: 'circle',
COUNT: 'count', COUNT: 'count',
MOVEMENT_CIRCLE: 'movementCircle',
RANDOMIZE: 'randomize', RANDOMIZE: 'randomize',
SPEED: 'speed' SPEED: 'speed'
}; };

@ -1,10 +1,10 @@
import Rx, { Observable } from 'rxjs'; import Rx, { Observable } from 'rxjs';
import Controls from './controls';
import Animation1a from './animation1a'; import Animation1a from './animation1a';
import Animation1b from './animation1b'; import Animation1b from './animation1b';
import Animation2a from './animation2a'; // import Container1b from './container1b';
import Animation2b from './animation2b'; // import Container2a from './container2a';
import Animation3a from './animation3a'; // import Container2b from './container2b';
// import Container3a from './container3a';
require('../css/reset.scss'); require('../css/reset.scss');
require('../css/index.scss'); require('../css/index.scss');
@ -12,9 +12,21 @@ require('../css/particle.scss');
require('../css/controls.scss'); require('../css/controls.scss');
window.addEventListener('load', () => { window.addEventListener('load', () => {
new Animation1a(); const destroy$ = new Rx.BehaviorSubject(null);
// new Animation1b();
// new Animation2a(); Animation1a(destroy$);
// new Animation2b(); Animation1b(destroy$);
// new Animation3a();
}); });
// TODO remove bottom padding from Disqus
// TODO fix "hangup" small radius evade bug
// TODO don't load simulation until requested
// TODO sort out particle nextframe
// TODO subscriber on bounds change
// TODO abs positioning on controls elements so order doesn't matter
// TODO ANIM1ab free movement
// TODO ANIM3a streamline updateLeader
// TODO ANIM3b separation
// TODO ANIM3c alignment

@ -3,20 +3,24 @@ import { BEHAVIOR, ENTITIES, RAD } from './enums';
import Arc from './arc'; import Arc from './arc';
import Random from './random'; import Random from './random';
// ===== Constructor ===== const baseConfig = {
function Particle(parent, bounds, config, globalGrid) {
this.config = Object.assign({}, {
behavior: BEHAVIOR.COHESION, behavior: BEHAVIOR.COHESION,
bounds, bounds: {},
color: Random.color(), color: 'red',
gridSize: 5, gridSize: 5,
randomize: true, randomize: true,
showMovementCircle: false, showArc: false,
showVisionGrid: false, showVision: false,
speed: 4, speed: 4,
visionRadius: 50 visionRadius: 50
}, config); };
// ===== Constructor =====
function Particle(parent, bounds, config, globalGrid) {
this.config = Object.assign({}, baseConfig, config);
this.config.color = Random.color();
// this.config.bounds = bounds;
this.grids = { this.grids = {
global: globalGrid || {}, global: globalGrid || {},
@ -29,7 +33,6 @@ function Particle(parent, bounds, config, globalGrid) {
body: createBodyNode(this.config), body: createBodyNode(this.config),
circle: undefined, circle: undefined,
container: createContainerNode(this.config, this.id), container: createContainerNode(this.config, this.id),
parent,
visionGrid: undefined, visionGrid: undefined,
}; };
@ -40,40 +43,35 @@ function Particle(parent, bounds, config, globalGrid) {
this.isLeader = false; this.isLeader = false;
this.arc = Arc.create(bounds, this.grids.global); this.arc = Arc.create(bounds, this.grids.global);
console.error('starting', this.arc) // this.updateConfig(this.config);
this.updateConfig(this.config); // this.grids.global.setPoint({ x: p.arc.endX, y: p.arc.endY, type: ENTITIES.PARTICLE }, p);
this.nextFrame(globalGrid); // this.nextFrame(globalGrid);
const point = document.createElement('div');
point.style.height = '10px'
point.style.width = '10px'
point.style.background = 'red'
point.style.position = 'absolute'
point.style.left = '200px'
point.style.top = '200px'
this.nodes.parent.appendChild(point);
}; };
// ===== PROTOTYPE ===== // ===== PROTOTYPE =====
Particle.prototype.remove = function() { Particle.prototype.remove = function() {
this.nodes.parent.removeChild(this.nodes.container); // this.grids.globals.deletePoint({ x: p.arc.endX, y: p.arc.endY, type: ENTITIES.PARTICLE });
const parent = this.nodes.container.parentNode;
parent.removeChild(this.nodes.container);
delete this.nodes; delete this.nodes;
return this; return this;
} }
Particle.prototype.nextFrame = function() { Particle.prototype.nextFrame = function() {
this.arc = Arc.goto(this.arc, 200, 200, this.config.speed) // this.arc = Arc.goto(this.arc, 200, 200, this.config.speed)
this.arc = Arc.step(this.arc, this.config.bounds, this.config.speed); this.arc = Arc.step(this.arc, this.config.bounds, this.config.speed);
// if (this.leader !== null) { if (this.leader !== null) {
// this.arc = Arc.follow(this.arc, this.leader.arc); this.arc = Arc.follow(this.arc, this.leader.arc);
// } else if (this.arc.length <= 0 && this.config.randomize) { } else if (this.arc.length <= 0 && this.config.randomize) {
// this.arc = Arc.randomize(this.arc); this.arc = Arc.randomize(this.arc);
// } }
//
// this.grids.vision = updateVisionGrid(this.arc, this.config, this.grids); // this.grids.vision = updateVisionGrid(this.arc, this.config, this.grids);
// const { hazards, particles } = look(this.arc, this.grids); // const { hazards, particles } = look(this.arc, this.grids);
// //
@ -89,28 +87,32 @@ Particle.prototype.nextFrame = function() {
repaintVisionGrid(this.nodes.visionGrid, this.arc, this.grids); repaintVisionGrid(this.nodes.visionGrid, this.arc, this.grids);
} }
Particle.prototype.updateConfig = function(config) { Particle.prototype.subscriber = function({ key, value }) {
Object.assign(this.config, config);
const { showMovementCircle, showVisionGrid } = this.config;
if (showMovementCircle === true && this.nodes.circle === undefined) {
this.nodes.circle = createCircleNode(config);
this.nodes.container.appendChild(this.nodes.circle);
}
if (showMovementCircle === false && this.nodes.circle !== undefined) {
this.nodes.container.removeChild(this.nodes.circle);
delete this.nodes.circle;
}
if (showVisionGrid === true && this.nodes.visionGrid === undefined) { }
this.nodes.visionGrid = createVisionGridNodes(this.config, this.grids, this.nodes);
}
if (showVisionGrid === false && this.nodes.visionGrid !== undefined) { Particle.prototype.updateConfig = function(config) {
delete this.nodex.visionGrid; // Object.assign(this.config, config);
} //
// const { showArc, showVision } = this.config;
//
// if (showArc === true && this.nodes.circle === undefined) {
// this.nodes.circle = createCircleNode(this.config);
// this.nodes.container.appendChild(this.nodes.circle);
// }
//
// if (showArc === false && this.nodes.circle !== undefined) {
// this.nodes.container.removeChild(this.nodes.circle);
// delete this.nodes.circle;
// }
//
// if (showVision === true && this.nodes.visionGrid === undefined) {
// this.nodes.visionGrid = createVisionGridNodes(this.config, this.grids, this.nodes);
// }
//
// if (showVision === false && this.nodes.visionGrid !== undefined) {
// delete this.nodex.visionGrid;
// }
} }
Particle.prototype.updateLeader = function(particles) { Particle.prototype.updateLeader = function(particles) {
@ -184,7 +186,7 @@ function createBodyNode(config) {
} }
function createCircleNode(config) { function createCircleNode(config) {
if (config.showMovementCircle === false) { if (config.showArc === false) {
return undefined; return undefined;
} }
@ -234,7 +236,7 @@ function createVisionGrid(config) {
} }
function createVisionGridNodes(config, grids, nodes) { function createVisionGridNodes(config, grids, nodes) {
if (config.showVisionGrid === false) { if (config.showVision === false) {
return undefined; return undefined;
} }

@ -1,10 +1,6 @@
const random = { const random = {
bool: (weight) => Math.random() < (weight || 0.5), bool: (weight) => Math.random() < (weight || 0.5),
color: () => `rgb( color: () => `rgb(${Math.floor(Math.random() * 230)}, ${Math.floor(Math.random() * 230)}, ${Math.floor(Math.random() * 230)})`,
${Math.floor(Math.random() * 230)},
${Math.floor(Math.random() * 230)},
${Math.floor(Math.random() * 230)}
)`,
id: () => String.fromCharCode( id: () => String.fromCharCode(
random.num(65, 90), random.num(97, 122), random.num(97, 122), random.num(65, 90), random.num(97, 122), random.num(97, 122),
random.num(97, 122), random.num(97, 122), random.num(97, 122) random.num(97, 122), random.num(97, 122), random.num(97, 122)

Loading…
Cancel
Save