Movement circle and vision grid moved into particle container.

master
Ben Burlingham 8 years ago
parent aae9a8fe90
commit 5edcdea48f
  1. 59
      css/particle.scss
  2. 28
      css/style.css
  3. 35
      index.html
  4. 49
      js/animation1a.js
  5. 75
      js/animation1b.js
  6. 89
      js/animation2.js
  7. 93
      js/animation2a.js
  8. 20737
      js/bundle.js
  9. 47
      js/controls.js
  10. 3
      js/enums.js
  11. 10
      js/index.js
  12. 196
      js/particle.js
  13. 2
      package.json

@ -1,64 +1,45 @@
.particle {
$side: 20px;
$particleS: 20px;
$visionS: 100px;
// background: url('../res/seahorse.svg') no-repeat center center #aaa;
background-color: #555;
background-size: $side $side;
border-radius: $side / 2;
color: #fff;
font-size: 11px;
height: $side;
line-height: $side;
margin: #{-$side / 2} 0 0 #{-$side / 2};
.particle-container {
position: absolute;
text-align: center;
width: $side;
z-index: 1;
}
// &.has-vision::after {
// border: 50px solid;
// border-color: lightgreen transparent transparent turquoise;
// border-radius: 50px;
// content: ' ';
// height: 0;
// left: -50px;
// margin: #{$side / 2} 0 0 #{$side / 2};
// opacity: 0.5;
// position: absolute;
// transform: rotate(45deg);
// top: -50px;
// width: 0;
// z-index: 0;
// }
.particle-body {
background-color: #555;
background-size: $particleS $particleS;
border-radius: 0 ($particleS / 2) ($particleS / 2) ($particleS / 2);
height: $particleS;
margin: #{-$particleS / 2} 0 0 #{-$particleS / 2};
position: relative;
width: $particleS;
z-index: 2;
}
.particle-movement-circle {
border: 3px dotted darkturquoise;
margin-left: 10px;
margin-top: 10px;
position: absolute;
z-index: 0;
}
.particle-vision-grid {
height: 100px;
// margin-top: 50px;
// margin-left: 50px;
.particle-vision {
// background: #ddd;
height: $visionS;
left: -$visionS / 2;
position: absolute;
transform-origin: left top;
width: 100px;
z-index: 99;
top: -$visionS / 2;
width: $visionS;
z-index: 1;
}
.particle-vision-dot {
$s: 2px;
background: red;
// border-radius: 2px;
height: $s;
position: absolute;
width: $s;
z-index: 99;
&.removed {
// background: purple;

@ -30,40 +30,38 @@ body {
position: relative;
width: 100%;
z-index: 0; }
.particle {
.particle-container {
position: absolute;
z-index: 1; }
.particle-body {
background-color: #555;
background-size: 20px 20px;
border-radius: 10px;
color: #fff;
font-size: 11px;
border-radius: 0 10px 10px 10px;
height: 20px;
line-height: 20px;
margin: -10px 0 0 -10px;
position: absolute;
text-align: center;
position: relative;
width: 20px;
z-index: 1; }
z-index: 2; }
.particle-movement-circle {
border: 3px dotted darkturquoise;
margin-left: 10px;
margin-top: 10px;
position: absolute;
z-index: 0; }
.particle-vision-grid {
.particle-vision {
height: 100px;
left: -50px;
position: absolute;
transform-origin: left top;
top: -50px;
width: 100px;
z-index: 99; }
z-index: 1; }
.particle-vision-dot {
background: red;
height: 2px;
position: absolute;
width: 2px;
z-index: 99; }
width: 2px; }
.particle-vision-dot.removed {
background: green; }
.particle-vision-dot.touching {

@ -35,8 +35,8 @@
</p>
<div class='outerContainer'>
<div id="animation1" class='animationContainer'></div>
<div id="controls1" class='controlsContainer'></div>
<div id="animation1a" class='animationContainer'></div>
<div id="controls1a" class='controlsContainer'></div>
</div>
<p>
@ -44,34 +44,23 @@
</p>
<div class='outerContainer'>
<div id="animation2" class='animationContainer'></div>
<div id="controls2" class='controlsContainer'></div>
<div id="animation1b" class='animationContainer'></div>
<div id="controls1b" class='controlsContainer'></div>
</div>
<p>
<h4>Exploration 2: Grid-based vision</h4>
</p>
<p>
The next goal was to explore a grid-based vision system. Each particle owns a vision grid, which is compared
against the global grid to detect collisions. Here's a small scale system, with the vision grid visualized:
</p>
<!--
// Another tricky part was the wall avoidance and vision grid.
The ultimate goal is to simulate particles that move in swarms and calculate their
position independently, similar to flocks of birds.
They should take into account disturbances, walls, individual initiative, and landing areas.
<h2>Animation 2</h2>
Goal: Scare a seahorse with a click. Move her to a safe distance away.
Key points:
- interval() used to create FPS
- last() used with takeWhile() to only update state store at end
- scan() re-emits on each emission. reduce() only emits after last emission (take()).
- CustomEvent can bridge streams
- State store passed between streams
- Random movement vector, collision detection -->
<div class='outerContainer'>
<div id="animation2a" class='animationContainer'></div>
<div id="controls2a" class='controlsContainer'></div>
</div>
<script src='js/bundle.js'></script>
</body>

@ -4,19 +4,16 @@ import Store from './store';
import Controls from './controls';
import { CONTROLS } from './enums';
function Animation1() {
function Animation1a() {
this.options = {
animating: false,
count: 1,
maxCount: 25,
randomizeRadius: true,
randomizeRotation: true,
showMovementCircle: false,
// showVisionGrid: false,
randomize: true,
showMovementCircle: true,
speed: 4
};
this.container = document.getElementById('animation1');
this.container = document.getElementById('animation1a');
this.bounds = this.container.getBoundingClientRect();
this.movementCircleCtrl = createMovementCircleControl();
@ -24,7 +21,7 @@ function Animation1() {
this.particles = [];
const controls = new Controls(
document.getElementById('controls1'),
document.getElementById('controls1a'),
this.options,
[this.movementCircleCtrl]
);
@ -36,7 +33,6 @@ function Animation1() {
eventStack$.subscribe(this.subscriber.bind(this));
this.updateAnimating(this.options.animating);
this.updateCount(this.options.count);
this.updateMovementCircle(this.options.showMovementCircle);
};
@ -59,22 +55,21 @@ function createMovementCircleControl() {
return label;
}
Animation1.prototype.subscriber = function({ key, value }) {
Animation1a.prototype.subscriber = function({ key, value }) {
switch(key) {
case CONTROLS.ANIMATING: this.updateAnimating(value); break;
case CONTROLS.COUNT: this.updateCount(value); break;
case CONTROLS.MOVEMENT_CIRCLE: this.updateMovementCircle(value); break;
case CONTROLS.RADIUS: this.updateRadius(value); break;
case CONTROLS.ROTATION: this.updateRotation(value); break;
case CONTROLS.RANDOMIZE: this.updateRandomize(value); break;
case CONTROLS.SPEED: this.updateSpeed(value); break;
}
}
Animation1.prototype.nextFrame = function() {
Animation1a.prototype.nextFrame = function() {
this.particles.forEach(p => p.nextFrame());
}
Animation1.prototype.updateAnimating = function(isAnimating) {
Animation1a.prototype.updateAnimating = function(isAnimating) {
this.options.animating = isAnimating;
if (isAnimating) {
@ -85,10 +80,9 @@ Animation1.prototype.updateAnimating = function(isAnimating) {
}
}
Animation1.prototype.updateCount = function(count) {
Animation1a.prototype.updateCount = function(count) {
while (this.particles.length >= count) {
const p = this.particles.pop();
this.container.removeChild(p.node);
delete this.particles.pop().remove();
}
while (this.particles.length < count) {
@ -97,23 +91,20 @@ Animation1.prototype.updateCount = function(count) {
}
}
Animation1.prototype.updateMovementCircle = function(value) {
Animation1a.prototype.updateMovementCircle = function(value) {
this.options.showMovementCircle = value;
this.movementCircleCtrl.querySelector('[type=checkbox]').checked = value;
this.particles.forEach(p => p.updateOptions({ showMovementCircle: value }));
}
Animation1.prototype.updateRadius = function(value) {
this.randomRadiusCtrl.querySelector('[type=checkbox]').checked = value;
this.particles.forEach(p => p.updateOptions({ randomizeRadius: value }));
Animation1a.prototype.updateRandomize = function(value) {
this.options.randomize = value;
this.particles.forEach(p => p.updateOptions({ randomize: value }));
}
Animation1.prototype.updateRotation = function(value) {
this.randomRotationCtrl.querySelector('[type=checkbox]').checked = value;
this.particles.forEach(p => p.updateOptions({ randomizeRotation: value }));
Animation1a.prototype.updateSpeed = function(value) {
this.options.speed = value;
this.particles.forEach(p => p.updateOptions({ speed: value }));
}
Animation1.prototype.updateSpeed = function(speed) {
this.particles.forEach(p => p.updateOptions({ speed }));
}
export default Animation1;
export default Animation1a;

@ -0,0 +1,75 @@
import Rx, { Observable } from 'rxjs';
import Particle from './particle';
import Store from './store';
import Controls from './controls';
import { CONTROLS } from './enums';
function Animation1b() {
this.options = {
count: 1,
maxCount: 2000,
randomize: false,
speed: 8
};
this.container = document.getElementById('animation1b');
this.bounds = this.container.getBoundingClientRect();
this.particles = [];
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.RANDOMIZE: this.updateRandomize(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) {
while (this.particles.length >= count) {
delete this.particles.pop().remove();
}
while (this.particles.length < count) {
const p = new Particle(this.container, this.bounds, this.options);
this.particles.push(p);
}
}
Animation1b.prototype.updateRandomize = function(value) {
this.options.randomize = value;
this.particles.forEach(p => p.updateOptions({ randomize: value }));
}
Animation1b.prototype.updateSpeed = function(value) {
this.options.speed = value;
this.particles.forEach(p => p.updateOptions({ speed: value }));
}
export default Animation1b;

@ -1,89 +0,0 @@
import Rx, { Observable } from 'rxjs';
import Particle from './particle';
import Store from './store';
import Controls from './controls';
import { CONTROLS } from './enums';
function Animation2() {
this.options = {
animating: false,
count: 1,
maxCount: 2000,
speed: 4
};
this.container = document.getElementById('animation2');
this.bounds = this.container.getBoundingClientRect();
this.particles = [];
const controls = new Controls(
document.getElementById('controls2'),
this.options
);
controls.mount().subscribe(this.subscriber.bind(this));
this.updateAnimating(this.options.animating);
this.updateCount(this.options.count);
// TODO X dimension modified by core UI
// TODO ANIM3 Show vision grid (including touches!)
// TODO ANIM3 regen vision grid is option updated
// TODO ANIM3 cleanup vision grid
// TODO ANIM3 Pallet avoidance
// TODO ANIM4 Flocking
};
Animation2.prototype.subscriber = function({ key, value }) {
switch(key) {
case CONTROLS.ANIMATING: this.updateAnimating(value); break;
case CONTROLS.COUNT: this.updateCount(value); break;
case CONTROLS.RADIUS: this.updateRadius(value); break;
case CONTROLS.ROTATION: this.updateRotation(value); break;
case CONTROLS.SPEED: this.updateSpeed(value); break;
}
}
Animation2.prototype.nextFrame = function() {
this.particles.forEach(p => p.nextFrame());
}
Animation2.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));
}
}
Animation2.prototype.updateCount = function(count) {
while (this.particles.length >= count) {
const p = this.particles.pop();
this.container.removeChild(p.node);
}
while (this.particles.length < count) {
const p = new Particle(this.container, this.bounds, this.options);
this.particles.push(p);
}
}
Animation2.prototype.updateRadius = function(value) {
this.particles.forEach(p => p.updateOptions({ randomizeRadius: value }));
}
Animation2.prototype.updateRotation = function(value) {
this.particles.forEach(p => p.updateOptions({ randomizeRotation: value }));
}
Animation2.prototype.updateSpeed = function(speed) {
this.particles.forEach(p => p.updateOptions({ speed }));
}
export default Animation2;

@ -0,0 +1,93 @@
import Rx, { Observable } from 'rxjs';
import Particle from './particle';
import Store from './store';
import Controls from './controls';
import { CONTROLS } from './enums';
function Animation2a() {
this.options = {
count: 1,
maxCount: 10,
randomize: true,
showVisionGrid: true,
showMovementCircle: false,
speed: 4
};
this.container = document.getElementById('animation2a');
this.bounds = this.container.getBoundingClientRect();
this.particles = [];
const controls = new Controls(
document.getElementById('controls2a'),
this.options
);
controls.mount().subscribe(this.subscriber.bind(this));
this.updateAnimating(this.options.animating);
this.updateCount(this.options.count);
// TODO X dimension modified by core UI
// TODO remove bottom padding from Disqus
// TODO perf - cache trig or perform operations
// TODO move animating into controls
// TODO particle evade
// TODO ANIM2 put pallettes in animation
// TODO ANIM2a Show vision grid (including touches!)
// TODO ANIM2b Scale vision grid to 1000? particles
// TODO ANIM4 Pallet avoidance
// TODO ANIM4 Lots of particles
// TODO ANIM5 Flocking
};
Animation2a.prototype.subscriber = function({ key, value }) {
switch(key) {
case CONTROLS.ANIMATING: this.updateAnimating(value); break;
case CONTROLS.COUNT: this.updateCount(value); break;
case CONTROLS.RANDOMIZE: this.updateRandomize(value); break;
case CONTROLS.SPEED: this.updateSpeed(value); break;
}
}
Animation2a.prototype.nextFrame = function() {
this.particles.forEach(p => p.nextFrame());
}
Animation2a.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));
}
}
Animation2a.prototype.updateCount = function(count) {
while (this.particles.length >= count) {
delete this.particles.pop().remove();
}
while (this.particles.length < count) {
const p = new Particle(this.container, this.bounds, this.options);
this.particles.push(p);
}
}
Animation2a.prototype.updateRandomize = function(value) {
this.options.randomize = value;
this.particles.forEach(p => p.updateOptions({ randomize: value }));
}
Animation2a.prototype.updateSpeed = function(value) {
this.options.speed = value;
this.particles.forEach(p => p.updateOptions({ speed: value }));
}
export default Animation2a;

File diff suppressed because one or more lines are too long

47
js/controls.js vendored

@ -1,19 +1,17 @@
import Rx, { Observable } from 'rxjs';
import { CONTROLS } from './enums';
function Controls(container, { animating, count, maxCount, randomizeRadius, randomizeRotation, speed }, customNodes) {
function Controls(container, { animating, count, maxCount, randomize, speed }, customNodes) {
this.nodes = {
animating: createAnimatingControl(animating),
count: createCountControl(count, maxCount),
radius: createRandomRadiusControl(randomizeRadius),
rotation: createRandomRotationControl(randomizeRotation),
randomize: createRandomizeControl(randomize),
speed: createSpeedControl(speed),
}
container.appendChild(this.nodes.count);
container.appendChild(this.nodes.speed);
container.appendChild(this.nodes.radius);
container.appendChild(this.nodes.rotation);
container.appendChild(this.nodes.randomize);
if (customNodes !== undefined) {
customNodes.forEach(node => { container.appendChild(node); });
@ -23,8 +21,7 @@ function Controls(container, { animating, count, maxCount, randomizeRadius, rand
this.updateOptions({ key: CONTROLS.ANIMATING, value: animating });
this.updateOptions({ key: CONTROLS.COUNT, value: count });
this.updateOptions({ key: CONTROLS.RADIUS, value: randomizeRadius });
this.updateOptions({ key: CONTROLS.ROTATION, value: randomizeRotation });
this.updateOptions({ key: CONTROLS.RANDOMIZE, value: randomize });
this.updateOptions({ key: CONTROLS.SPEED, value: speed });
}
@ -52,11 +49,8 @@ Controls.prototype.mount = function(customNodes) {
const count$ = Rx.Observable.fromEvent(this.nodes.count, 'input')
.map(evt => ({ key: CONTROLS.COUNT, value: evt.target.value * 1 }));
const radius$ = Rx.Observable.fromEvent(this.nodes.radius, 'change')
.map(evt => ({ key: CONTROLS.RADIUS, value: evt.target.checked }));
const rotation$ = Rx.Observable.fromEvent(this.nodes.rotation, 'change')
.map(evt => ({ key: CONTROLS.ROTATION, value: evt.target.checked }));
const randomize$ = Rx.Observable.fromEvent(this.nodes.randomize, 'change')
.map(evt => ({ key: CONTROLS.RANDOMIZE, value: evt.target.checked }));
const speed$ = Rx.Observable.fromEvent(this.nodes.speed, 'input')
.map(evt => ({ key: CONTROLS.SPEED, value: evt.target.value * 1 }));
@ -64,8 +58,7 @@ Controls.prototype.mount = function(customNodes) {
const eventStack$ = Rx.Observable.merge(
animating$,
count$,
radius$,
rotation$,
randomize$,
speed$
);
@ -113,12 +106,12 @@ function createCountControl(value, max) {
return label;
}
function createRandomRadiusControl(value) {
function createRandomizeControl(value) {
const label = document.createElement('label');
label.className = 'controls-checkbox';
const text = document.createElement('span');
text.innerHTML = 'Random radii';
text.innerHTML = 'Randomize movement';
text.className = 'controls-checkbox-text';
const checkbox = document.createElement('input');
@ -132,27 +125,7 @@ function createRandomRadiusControl(value) {
return label;
}
function createRandomRotationControl(value) {
const label = document.createElement('label');
label.className = 'controls-checkbox';
const text = document.createElement('span');
text.innerHTML = 'Random rotation';
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 createSpeedControl = function(value) {
function createSpeedControl(value) {
const label = document.createElement('label');
label.className = 'controls-range';

@ -10,8 +10,7 @@ const CONTROLS = {
ANIMATING: 'animating',
COUNT: 'count',
MOVEMENT_CIRCLE: 'movementCircle',
RADIUS: 'radius',
ROTATION: 'rotation',
RANDOMIZE: 'randomize',
SPEED: 'speed'
};

@ -1,12 +1,14 @@
import Rx, { Observable } from 'rxjs';
import Controls from './controls';
import Animation1 from './animation1';
import Animation2 from './animation2';
import Animation1a from './animation1a';
import Animation1b from './animation1b';
import Animation2a from './animation2a';
require('../css/reset.scss');
require('../css/index.scss');
require('../css/particle.scss');
require('../css/controls.scss');
new Animation1();
new Animation2();
new Animation1a();
new Animation1b();
new Animation2a();

@ -1,5 +1,4 @@
import Rx, { Observable } from 'rxjs';
// import DOM from './dom';
import { RAD } from './enums';
import Store from './store';
@ -11,32 +10,14 @@ const random = {
/* ===== Constructor ===== */
function Particle(container, bounds, options) {
const color = random.color();
this.container = container;
this.bounds = bounds;
// this.visionGridPoints = calculateVisionGridPoints();
this.node = document.createElement('div');
this.node.className = 'particle has-vision';
this.node.style.backgroundColor = color;
this.container.appendChild(this.node);
// this.visionGridPoints.forEach(point => {
// this.container.appendChild(point.div);
// });
function Particle(parent, bounds, options) {
this.options = Object.assign({
randomizeRadius: false,
randomizeRotation: false,
randomize: false,
showMovementCircle: false,
showVIsionGrid: false,
showVisionGrid: false,
speed: 4
}, options);
this.arc = {
r: random.num(100, 200),
t: random.num(RAD.t90, RAD.t360),
@ -46,26 +27,43 @@ function Particle(container, bounds, options) {
this.particle = {
clockwise: random.bool(),
color,
color: random.color(),
x: 0,
y: 0
}
this.visionGridPoints = [];
this.interval = random.num(RAD.t90, RAD.t360);
this.bounds = bounds;
this.parent = parent;
this.container = document.createElement('div');
this.container.className = 'particle-container';
this.body = document.createElement('div');
this.body.className = 'particle-body';
this.body.style.backgroundColor = this.particle.color;
this.container.appendChild(this.body);
this.parent.appendChild(this.container);
this.updateOptions(this.options);
this.nextFrame();
};
Particle.prototype.remove = function() {
this.parent.removeChild(this.container);
return this;
}
Particle.prototype.nextFrame = function() {
this.move();
repaintParticle(this.arc, this.node, this.particle);
repaintContainer(this.container, this.particle);
repaintBody(this.arc, this.body, this.particle);
repaintCircle(this.arc, this.circle, this.particle);
// if (this.options.showVisionGrid === true) {
// repaintVisionGrid(this.arc, this.particle, this.visionGridPoints);
// }
};
repaintVision(this.arc, this.vision, this.particle);
}
Particle.prototype.updateBounds = function(bounds) {
this.bounds = bounds;
@ -78,13 +76,35 @@ Particle.prototype.updateOptions = function(options) {
this.circle = document.createElement('div');
this.circle.className = 'particle-movement-circle';
this.circle.style.borderColor = this.particle.color;
this.node.appendChild(this.circle);
this.container.appendChild(this.circle);
}
if (options.showMovementCircle === false && this.circle !== undefined) {
this.circle.parentNode.removeChild(this.circle);
this.container.removeChild(this.circle);
delete this.circle;
}
if (options.showVisionGrid === true && this.vision === undefined) {
this.vision = document.createElement('div');
this.vision.className = 'particle-vision';
this.container.appendChild(this.vision);
this.visionGridPoints = calculateVisionGridPoints(this.particle);
this.visionGridPoints.forEach(point => {
this.vision.appendChild(point.div);
});
}
if (options.showVisionGrid === false && this.vision !== undefined) {
// this.container.removeChild(this.vision);
// delete this.vision;
//
// this.visionGridPoints.forEach(point => {
// this.vision.removeChild(point.div);
// });
// this.visionGridPoints = [];
}
}
Particle.prototype.move = function() {
@ -92,10 +112,10 @@ Particle.prototype.move = function() {
if (this.interval <= 0) {
this.interval = random.num(RAD.t90, RAD.t360);
if (this.options.randomizeRadius === true) {
if (this.options.randomize === true) {
this.arc = moveArc(this.arc, random.num(100, 200));
if (random.bool(0.8) && this.options.randomizeRotation === true) {
if (random.bool(0.8)) {
this.particle.clockwise = !this.particle.clockwise;
this.arc = changeDirection(this.arc);
}
@ -111,7 +131,6 @@ Particle.prototype.move = function() {
this.particle.x = this.arc.x + this.arc.r * Math.cos(this.arc.t);
this.particle.y = this.arc.y - this.arc.r * Math.sin(this.arc.t);
this.node.setAttribute('data-arc', JSON.stringify(this.arc))
// Overflow.
if (this.particle.x < 0) {
@ -151,98 +170,77 @@ function changeDirection(arc) {
return arc;
}
function calculateVisionGridPoints() {
function calculateVisionGridPoints(particle) {
const gridSize = 5;
const visionRadius = 50;
const squareGrid = [];
const r0 = Math.pow(visionRadius, 2);
const r1 = Math.pow(visionRadius - gridSize, 2);
const points = [];
for (let x = -visionRadius; x <= visionRadius; x += gridSize) {
for (let y = -visionRadius; y <= visionRadius; y += gridSize) {
let alpha = Math.atan(y / x);
if (x === 0 && y === 0) {
alpha = 0;
} else if (x === 0 && y < 0) {
alpha = RAD.t270;
} else if (y === 0 && x < 0) {
alpha = RAD.t180;
} else if (x === 0 && y > 0) {
alpha = RAD.t90;
} else if (x < 0 && y < 0) {
alpha = alpha + RAD.t180;
} else if (x <= 0) {
alpha = RAD.t180 + alpha;
} else if (y < 0) {
alpha = RAD.t360 + alpha;
// Half of triangle
if (x > -y) {
continue;
}
squareGrid.push({ x, y, alpha });
}
}
// Vision band
const p = Math.pow(x, 2) + Math.pow(y, 2);
if (p > r0 || p < r1) {
continue;
}
const r0 = Math.pow(visionRadius, 2);
const r1 = Math.pow(visionRadius - gridSize, 2);
const div = document.createElement('div');
div.className = 'particle-vision-dot';
div.style.backgroundColor = particle.color;
div.style.left = `${x + visionRadius}px`;
div.style.top = `${y + visionRadius}px`;
return squareGrid.reduce((acc, point) => {
const p = Math.pow(point.x, 2) + Math.pow(point.y, 2);
if (p > r0 || p < r1) {
return acc;
points.push({ x, y, div });
}
}
const div = document.createElement('div');
div.className = 'particle-vision-dot';
return points;
}
acc.push(Object.assign(point, { div }));
return acc;
}, []);
function repaintContainer(containerNode, particle) {
containerNode.style.left = `${particle.x}px`;
containerNode.style.top = `${particle.y}px`;
}
function repaintParticle(arc, node, particle) {
function repaintBody(arc, bodyNode, particle) {
const rad = particle.clockwise
? RAD.t180 - arc.t
: RAD.t360 - arc.t;
node.style.left = `${particle.x}px`;
node.style.top = `${particle.y}px`;
bodyNode.style.transform = `rotate(${rad + RAD.t45}rad)`;
}
function repaintCircle(arc, node, particle) {
if (node === undefined) {
function repaintVision(arc, visionNode, particle) {
if (visionNode === undefined) {
return;
}
node.style.width = `${2 * arc.r}px`;
node.style.height = `${2 * arc.r}px`;
node.style.left = `${-particle.x - arc.r + arc.x}px`;
node.style.top = `${-particle.y - arc.r + arc.y}px`;
const rad = particle.clockwise
? RAD.t180 - arc.t
: RAD.t360 - arc.t;
node.style.borderRadius = `${arc.r}px`;
visionNode.style.transform = `rotate(${rad + RAD.t45}rad)`;
}
function repaintVisionGrid(arc, particle, points) {
const r0 = Math.min(arc.t, arc.t - RAD.t180);
const r1 = Math.max(arc.t, arc.t + RAD.t180);
const gridX = particle.x - particle.x % 5;
const gridY = particle.y - particle.y % 5;
points.forEach(({ x, y, alpha, div }, i) => {
if (alpha >= 0 && alpha <= r0) {
div.style.display = (particle.clockwise ? 'none' : 'block');
// div.className = (clockwise ? 'anim3-dot removed' : 'anim3-dot');
} else if (alpha >= arc.t && alpha <= r1) {
div.style.display = (particle.clockwise ? 'none' : 'block');
// div.className = (clockwise ? 'anim3-dot removed' : 'anim3-dot');
} else {
div.style.display = (particle.clockwise ? 'block' : 'none');
// div.className = (clockwise ? 'anim3-dot' : 'anim3-dot removed');
}
function repaintCircle(arc, circleNode) {
if (circleNode === undefined) {
return;
}
div.style.left = `${x + gridX}px`;
div.style.top = `${-y + gridY}px`;
});
}
circleNode.style.width = `${2 * arc.r}px`;
circleNode.style.height = `${2 * arc.r}px`;
circleNode.style.left = `-${arc.r + arc.r * Math.cos(arc.t)}px`;
circleNode.style.top = `-${arc.r - arc.r * Math.sin(arc.t)}px`;
circleNode.style.borderRadius = `${arc.r}px`;
}
export default Particle;

@ -4,7 +4,7 @@
"description": "Particle management using streams.",
"main": "webpack.config.js",
"scripts": {
"dev": "webpack --watch"
"dev": "webpack --watch -d"
},
"repository": {
"type": "git",

Loading…
Cancel
Save