You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
246 lines
7.0 KiB
246 lines
7.0 KiB
import Rx, { Observable } from 'rxjs';
|
|
import { RAD } from './enums';
|
|
import Store from './store';
|
|
|
|
const random = {
|
|
bool: (weight) => Math.random() < (weight || 0.5),
|
|
num: (min, max) => min + Math.round(Math.random() * max),
|
|
color: () => `rgb(${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)})`
|
|
}
|
|
|
|
/* ===== Constructor ===== */
|
|
|
|
function Particle(parent, bounds, options) {
|
|
this.options = Object.assign({
|
|
randomize: false,
|
|
showMovementCircle: false,
|
|
showVisionGrid: false,
|
|
speed: 4
|
|
}, options);
|
|
|
|
this.arc = {
|
|
r: random.num(100, 200),
|
|
t: random.num(RAD.t90, RAD.t360),
|
|
x: random.num(0, bounds.width),
|
|
y: random.num(0, bounds.height)
|
|
}
|
|
|
|
this.particle = {
|
|
clockwise: random.bool(),
|
|
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();
|
|
repaintContainer(this.container, this.particle);
|
|
repaintBody(this.arc, this.body, this.particle);
|
|
repaintCircle(this.arc, this.circle, this.particle);
|
|
repaintVision(this.arc, this.vision, this.particle);
|
|
}
|
|
|
|
Particle.prototype.updateBounds = function(bounds) {
|
|
this.bounds = bounds;
|
|
}
|
|
|
|
Particle.prototype.updateOptions = function(options) {
|
|
Object.assign(this.options, options);
|
|
|
|
if (options.showMovementCircle === true && this.circle === undefined) {
|
|
this.circle = document.createElement('div');
|
|
this.circle.className = 'particle-movement-circle';
|
|
this.circle.style.borderColor = this.particle.color;
|
|
this.container.appendChild(this.circle);
|
|
}
|
|
|
|
if (options.showMovementCircle === false && this.circle !== undefined) {
|
|
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() {
|
|
// Randomly change radius and rotation direction.
|
|
if (this.interval <= 0) {
|
|
this.interval = random.num(RAD.t90, RAD.t360);
|
|
|
|
if (this.options.randomize === true) {
|
|
this.arc = moveArc(this.arc, random.num(100, 200));
|
|
|
|
if (random.bool(0.8)) {
|
|
this.particle.clockwise = !this.particle.clockwise;
|
|
this.arc = changeDirection(this.arc);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure constant velocity and theta between 0 and 2π.
|
|
const delta = this.options.speed / this.arc.r;
|
|
this.interval -= delta;
|
|
|
|
this.arc.t += (this.particle.clockwise ? -delta : +delta);
|
|
this.arc.t = (this.arc.t > 0 ? this.arc.t % RAD.t360 : RAD.t360 + this.arc.t);
|
|
|
|
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);
|
|
|
|
// Overflow.
|
|
if (this.particle.x < 0) {
|
|
this.particle.x += this.bounds.width;
|
|
this.arc.x += this.bounds.width
|
|
} else if (this.particle.x > this.bounds.width) {
|
|
this.particle.x -= this.bounds.width;
|
|
this.arc.x -= this.bounds.width
|
|
}
|
|
|
|
if (this.particle.y < 0) {
|
|
this.particle.y += this.bounds.height; // TODO size of area
|
|
this.arc.y += this.bounds.height
|
|
} else if (this.particle.y > this.bounds.height) {
|
|
this.particle.y -= this.bounds.height;
|
|
this.arc.y -= this.bounds.height
|
|
}
|
|
}
|
|
|
|
function moveArc(arc, newRadius) {
|
|
const r0 = arc.r;
|
|
const r1 = newRadius;
|
|
|
|
// Moves arc center to new radius while keeping theta constant.
|
|
arc.x -= (r1 - r0) * Math.cos(arc.t);
|
|
arc.y += (r1 - r0) * Math.sin(arc.t);
|
|
arc.r = r1;
|
|
|
|
return arc;
|
|
}
|
|
|
|
function changeDirection(arc) {
|
|
arc.t = (arc.t + RAD.t180) % RAD.t360;
|
|
arc.x -= (2 * arc.r) * Math.cos(arc.t);
|
|
arc.y += (2 * arc.r) * Math.sin(arc.t);
|
|
|
|
return arc;
|
|
}
|
|
|
|
function calculateVisionGridPoints(particle) {
|
|
const gridSize = 5;
|
|
const visionRadius = 50;
|
|
|
|
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) {
|
|
// Half of triangle
|
|
if (x > -y) {
|
|
continue;
|
|
}
|
|
|
|
// Vision band
|
|
const p = Math.pow(x, 2) + Math.pow(y, 2);
|
|
if (p > r0 || p < r1) {
|
|
continue;
|
|
}
|
|
|
|
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`;
|
|
|
|
points.push({ x, y, div });
|
|
}
|
|
}
|
|
|
|
return points;
|
|
}
|
|
|
|
function repaintContainer(containerNode, particle) {
|
|
containerNode.style.left = `${particle.x}px`;
|
|
containerNode.style.top = `${particle.y}px`;
|
|
}
|
|
|
|
function repaintBody(arc, bodyNode, particle) {
|
|
const rad = particle.clockwise
|
|
? RAD.t180 - arc.t
|
|
: RAD.t360 - arc.t;
|
|
|
|
bodyNode.style.transform = `rotate(${rad + RAD.t45}rad)`;
|
|
}
|
|
|
|
function repaintVision(arc, visionNode, particle) {
|
|
if (visionNode === undefined) {
|
|
return;
|
|
}
|
|
|
|
const rad = particle.clockwise
|
|
? RAD.t180 - arc.t
|
|
: RAD.t360 - arc.t;
|
|
|
|
visionNode.style.transform = `rotate(${rad + RAD.t45}rad)`;
|
|
}
|
|
|
|
function repaintCircle(arc, circleNode) {
|
|
if (circleNode === undefined) {
|
|
return;
|
|
}
|
|
|
|
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;
|
|
|