|
|
|
@ -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; |
|
|
|
|