Purifying particle functions.

master
Ben Burlingham 8 years ago
parent d8280b1a5b
commit 82ca9182b2
  1. 25
      css/index.scss
  2. 21
      css/style.css
  3. 1
      index.html
  4. 24
      js/animation2a.js
  5. 6
      js/bundle.js
  6. 4
      js/index.js
  7. 382
      js/particle.js

@ -29,18 +29,29 @@
}
.hazard {
// background: url('../res/palette.svg');
// background-size: 167px 100px;
background: red;
background: #f9f8f0;
position: absolute;
&::after {
content: '\26A0';
font-size: 40px;
height: 40px;
line-height: 40px;
margin-left: -20px;
margin-top: -20px;
left: 50%;
opacity: 0.3;
position: absolute;
top: 50%;
width: 40px;
}
}
.hazard-dot {
background: yellow;
height: 2px;
margin: 1px 0 0 1px;
background: red;
height: 1px;
position: absolute;
width: 2px;
width: 1px;
}
// .highlight {

@ -32,15 +32,26 @@ body {
z-index: 0; }
.hazard {
background: red;
background: #f9f8f0;
position: absolute; }
.hazard::after {
content: '\26A0';
font-size: 40px;
height: 40px;
line-height: 40px;
margin-left: -20px;
margin-top: -20px;
left: 50%;
opacity: 0.3;
position: absolute;
top: 50%;
width: 40px; }
.hazard-dot {
background: yellow;
height: 2px;
margin: 1px 0 0 1px;
background: red;
height: 1px;
position: absolute;
width: 2px; }
width: 1px; }
.particle-container {
position: absolute;
z-index: 1; }

@ -18,6 +18,7 @@
<li>Have particle movement that feels calm and natural</li>
<li>Support large swarms of particles</li>
<li>Be able to evade obstacles</li>
<li>Exhibit flocking behavior</li>
</ul>
</blockquote>

@ -9,8 +9,8 @@ function Animation2a() {
count: 1,
maxCount: 10,
randomize: true,
showMovementCircle: true,
showVisionGrid: true,
showMovementCircle: false,
speed: 4
};
@ -30,20 +30,17 @@ function Animation2a() {
this.updateAnimating(this.options.animating);
this.updateCount(this.options.count);
// TODO X dimension modified by core UI
// TODO X dimension modified by core UI, maybe recalc grid in animation start?
// TODO remove bottom padding from Disqus
// TODO perf - cache trig or perform operations
// TODO move animating into controls
// TODO no randomize control except anim1
// TODO particle gridzie and vision rad into options
// TODO ANIM2 put hazards in animation
// TODO ANIM2 particle evade
// TODO ANIM2 style hazards
// TODO ANIM2a randomize hazards
// TODO ANIM2a Vision grid touches
// TODO ANIM2a randomize hazards
// TODO ANIM2b Scale vision grid to 1000? particles
// TODO ANIM3 Flocking
// TODO ANIM3 flocking
};
Animation2a.prototype.subscriber = function({ key, value }) {
@ -76,19 +73,19 @@ Animation2a.prototype.updateCount = function(count) {
}
while (this.particles.length < count) {
const p = new Particle(this.container, this.bounds, this.options);
const p = new Particle(this.container, this.bounds, this.options, this.globalGrid);
this.particles.push(p);
}
}
Animation2a.prototype.updateRandomize = function(value) {
this.options.randomize = value;
this.particles.forEach(p => p.updateOptions({ randomize: value }));
this.particles.forEach(p => p.updateConfig({ randomize: value }));
}
Animation2a.prototype.updateSpeed = function(value) {
this.options.speed = value;
this.particles.forEach(p => p.updateOptions({ speed: value }));
this.particles.forEach(p => p.updateConfig({ speed: value }));
}
function createGlobalGrid(container, bounds) {
@ -96,9 +93,8 @@ function createGlobalGrid(container, bounds) {
const gridSize = 5;
const hazards = [
{ x: 100, y: 100, w: 100, h: 100 },
{ x: 200, y: 200, w: 100, h: 100 },
{ x: 600, y: 200, w: 100, h: 100 },
{ x: 100, y: 100, w: 200, h: 200 },
{ x: 600, y: 200, w: 200, h: 200 },
];
return hazards.reduce((acc, { x, y, w, h }) => {

File diff suppressed because one or more lines are too long

@ -9,6 +9,6 @@ require('../css/index.scss');
require('../css/particle.scss');
require('../css/controls.scss');
new Animation1a();
new Animation1b();
// new Animation1a();
// new Animation1b();
new Animation2a();

@ -4,182 +4,268 @@ import Store from './store';
const random = {
bool: (weight) => Math.random() < (weight || 0.5),
color: () => `rgb(${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)})`,
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 ===== */
// ===== Constructor =====
function Particle(parent, bounds, options) {
this.options = Object.assign({
function Particle(parent, bounds, config, globalGrid) {
this.config = Object.assign({
bounds,
color: random.color(),
gridSize: 5,
randomize: false,
showMovementCircle: false,
showVisionGrid: false,
speed: 4
}, options);
speed: 4,
visionRadius: 50
}, config);
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.grids = {
global: globalGrid,
vision: calculateVisionGrid(this.config)
};
this.particle = {
this.arc = {
centerX: random.num(0, bounds.width),
centerY: random.num(0, bounds.height),
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);
endX: 0,
endY: 0,
length: random.num(RAD.t90, RAD.t360),
radius: random.num(100, 200),
theta: random.num(RAD.t90, RAD.t360),
};
this.nodes = {
body: createBodyNode(this.config),
circle: undefined,
container: createContainerNode(this.config),
parent,
vision: undefined,
visionGrid: undefined,
};
this.nodes.container.appendChild(this.nodes.body);
parent.appendChild(this.nodes.container);
this.updateConfig(this.config);
this.nextFrame();
};
// ===== PROTOTYPE =====
Particle.prototype.remove = function() {
this.parent.removeChild(this.container);
this.nodes.parent.removeChild(this.nodes.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);
}
// this.visionGrid = updateVisionGrid(this.visionGrid, this.globalGrid, this.particle);
this.arc = updateArc(this.arc, this.config);
Particle.prototype.updateBounds = function(bounds) {
this.bounds = bounds;
repaintContainer(this.nodes.container, this.arc);
repaintBody(this.nodes.body, this.arc);
repaintCircle(this.nodes.circle, this.arc);
// repaintVision(this.arc, this.nodes.vision, this.visionGrid, this.visionGridDivs);
}
Particle.prototype.updateOptions = function(options) {
Object.assign(this.options, options);
Particle.prototype.updateConfig = function(config) {
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.vision === undefined) {
this.nodes.vision = createVisionNode(config, this.grids);
// visionNode.appendChild(div);
this.nodes.container.appendChild(this.nodes.vision);
}
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 (showMovementCircle === false && this.nodes.circle !== undefined) {
this.nodes.container.removeChild(this.nodes.vision);
delete this.nodes.vision;
}
if (options.showMovementCircle === false && this.circle !== undefined) {
this.container.removeChild(this.circle);
delete this.circle;
// if (config.showVisionGrid === false && this.nodes.vision !== undefined) {
// // this.nodes.container.removeChild(this.vision);
// // delete this.vision;
//
// // this.visionGrid.forEach(point => {
// // this.vision.removeChild(point.div);
// // });
// // this.visionGrid = [];
// }
}
// ===== DOM CREATION =====
function createBodyNode(config) {
const node = document.createElement('div');
node.className = 'particle-body';
node.style.backgroundColor = config.color;
return node;
}
function createCircleNode(config) {
if (config.showMovementCircle === false) {
return undefined;
}
if (options.showVisionGrid === true && this.vision === undefined) {
this.vision = document.createElement('div');
this.vision.className = 'particle-vision';
this.container.appendChild(this.vision);
const node = document.createElement('div');
node.className = 'particle-movement-circle';
node.style.borderColor = config.color;
return node;
}
this.visionGridPoints = calculateVisionGridPoints(this.particle);
function createContainerNode(config) {
const node = document.createElement('div');
node.className = 'particle-container';
return node;
}
this.visionGridPoints.forEach(point => {
this.vision.appendChild(point.div);
});
function createVisionNode(config) {
if (config.showVisionGrid === false) {
return undefined;
}
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 = [];
const node = document.createElement('div');
node.className = 'particle-vision';
return node;
}
function createVisionGridNodes(config, grids) {
if (config.showVisionGrid === false) {
return undefined;
}
return grids.visionGrid.forEach((acc, { x, y }) => {
const div = document.createElement('div');
div.className = 'particle-vision-dot';
// div.style.backgroundColor = config.color;
div.style.left = `${x + config.visionRadius}px`;
div.style.top = `${y + config.visionRadius}px`;
if (acc[x] === undefined) {
acc[x] = {};
}
acc[x][y] = div;
return acc;
}, {});
}
Particle.prototype.move = function() {
// ===== CALCULATIONS =====
function updateArc(arc, { bounds, randomize, speed }) {
// Randomly change radius and rotation direction.
if (this.interval <= 0) {
this.interval = random.num(RAD.t90, RAD.t360);
if (arc.length <= 0) {
arc.length = random.num(RAD.t90, RAD.t360);
if (this.options.randomize === true) {
this.arc = moveArc(this.arc, random.num(100, 200));
if (randomize === true) {
arc = moveArc(arc, random.num(100, 200));
if (random.bool(0.8)) {
this.particle.clockwise = !this.particle.clockwise;
this.arc = changeDirection(this.arc);
arc.clockwise = !arc.clockwise;
arc = changeDirection(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
const delta = speed / arc.radius;
arc.length -= delta;
arc.theta += (arc.clockwise ? -delta : +delta);
arc.theta = (arc.theta > 0 ? arc.theta % RAD.t360 : RAD.t360 + arc.theta);
arc.endX = arc.centerX + arc.radius * Math.cos(arc.theta);
arc.endY = arc.centerY - arc.radius * Math.sin(arc.theta);
// // Overflow.
if (arc.endX < 0) {
arc.endX += bounds.width;
arc.centerX += bounds.width
} else if (arc.endX > bounds.width) {
arc.endX -= bounds.width;
arc.centerX -= 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
if (arc.endY < 0) {
arc.endY += bounds.height;
arc.centerY += bounds.height
} else if (arc.endY > bounds.height) {
arc.endY -= bounds.height;
arc.centerY -= bounds.height
}
return arc;
}
// function updateVisionGrid(visionGrid, globalGrid, particle) {
// return visionGrid.reduce((acc, point) => {
// // Location of point on grid
// const x = Math.round(particle.x + point.x);
// const y = Math.round(particle.y + point.y);
//
// const gridX = x - x % 5;
// const gridY = y - y % 5;
//
// // console.warn(gridX, particle.x, point.x);
// point.touch = (globalGrid[gridX] !== undefined && globalGrid[gridX][gridY] !== undefined);
//
// if (point.touch) {
// console.warn('yay');
// }
//
// return acc.concat(point);
// }, []);
// }
//
function moveArc(arc, newRadius) {
const r0 = arc.r;
const r0 = arc.radius;
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;
arc.centerX -= (r1 - r0) * Math.cos(arc.theta);
arc.centerY += (r1 - r0) * Math.sin(arc.theta);
arc.radius = 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);
arc.theta = (arc.theta + RAD.t180) % RAD.t360;
arc.centerX -= (2 * arc.radius) * Math.cos(arc.theta);
arc.centerY += (2 * arc.radius) * Math.sin(arc.theta);
return arc;
}
function calculateVisionGridPoints(particle) {
const gridSize = 5;
const visionRadius = 50;
function calculateVisionGrid(config) {
if (config.showVisionGrid === false) {
return null;
}
const r0 = Math.pow(visionRadius, 2);
const r1 = Math.pow(visionRadius - gridSize, 2);
const { gridSize: side, visionRadius: radius } = config;
const r0 = Math.pow(radius, 2);
const r1 = Math.pow(radius - side, 2);
const points = [];
for (let x = -visionRadius; x <= visionRadius; x += gridSize) {
for (let y = -visionRadius; y <= visionRadius; y += gridSize) {
for (let x = -radius; x <= radius; x += side) {
for (let y = -radius; y <= radius; y += side) {
// Half of triangle
if (x > -y) {
continue;
@ -191,56 +277,58 @@ function calculateVisionGridPoints(particle) {
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 });
points.push({ x, y, touch: false });
}
}
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)`;
// ===== RENDERING =====
function repaintContainer(node, arc) {
node.style.left = `${arc.endX}px`;
node.style.top = `${arc.endY}px`;
}
function repaintVision(arc, visionNode, particle) {
if (visionNode === undefined) {
return;
}
const rad = particle.clockwise
? RAD.t180 - arc.t
: RAD.t360 - arc.t;
function repaintBody(node, arc) {
const rad = arc.clockwise
? RAD.t180 - arc.theta
: RAD.t360 - arc.theta;
visionNode.style.transform = `rotate(${rad + RAD.t45}rad)`;
node.style.transform = `rotate(${rad + RAD.t45}rad)`;
}
function repaintCircle(arc, circleNode) {
if (circleNode === undefined) {
function repaintCircle(node, arc) {
if (node === undefined) {
return;
}
circleNode.style.width = `${2 * arc.r}px`;
circleNode.style.height = `${2 * arc.r}px`;
node.style.width = `${2 * arc.radius}px`;
node.style.height = `${2 * arc.radius}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`;
node.style.left = `-${arc.radius + arc.radius * Math.cos(arc.theta)}px`;
node.style.top = `-${arc.radius - arc.radius * Math.sin(arc.theta)}px`;
circleNode.style.borderRadius = `${arc.r}px`;
node.style.borderRadius = `${arc.radius}px`;
}
// function repaintVision(arc, visionNode, visionGrid, visionGridDivs) {
// if (visionNode === undefined) {
// return;
// }
//
// const rad = arc.clockwise
// ? RAD.t180 - arc.theta
// : RAD.t360 - arc.theta;
//
// // visionNode.style.transform = `rotate(${rad + RAD.t45}rad)`;
//
// visionGrid.forEach(({ x, y, touch }) => {
// if (touch === true) {
// console.warn(x, y)
// visionGridDivs[x][y].style.background = 'black';
// }
// });
// }
export default Particle;

Loading…
Cancel
Save