import Rx, { Observable } from 'rxjs'; import { RAD } from './enums'; 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), } // ===== Constructor ===== function Particle(parent, bounds, config, globalGrid) { this.config = Object.assign({ bounds, color: random.color(), gridSize: 5, randomize: false, showMovementCircle: false, showVisionGrid: false, speed: 4, visionRadius: 50 }, config); this.grids = { global: globalGrid, vision: calculateVisionGrid(this.config) }; this.arc = { centerX: random.num(0, bounds.width), centerY: random.num(0, bounds.height), clockwise: random.bool(), 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.nodes.parent.removeChild(this.nodes.container); return this; } Particle.prototype.nextFrame = function() { // this.visionGrid = updateVisionGrid(this.visionGrid, this.globalGrid, this.particle); this.arc = updateArc(this.arc, this.config); 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.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 (showMovementCircle === false && this.nodes.circle !== undefined) { this.nodes.container.removeChild(this.nodes.vision); delete this.nodes.vision; } // 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; } const node = document.createElement('div'); node.className = 'particle-movement-circle'; node.style.borderColor = config.color; return node; } function createContainerNode(config) { const node = document.createElement('div'); node.className = 'particle-container'; return node; } function createVisionNode(config) { if (config.showVisionGrid === false) { return undefined; } 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; }, {}); } // ===== CALCULATIONS ===== function updateArc(arc, { bounds, randomize, speed }) { // Randomly change radius and rotation direction. if (arc.length <= 0) { arc.length = random.num(RAD.t90, RAD.t360); if (randomize === true) { arc = moveArc(arc, random.num(100, 200)); if (random.bool(0.8)) { arc.clockwise = !arc.clockwise; arc = changeDirection(arc); } } } // Ensure constant velocity and theta between 0 and 2π. 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 (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.radius; const r1 = newRadius; // Moves arc center to new radius while keeping theta constant. 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.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 calculateVisionGrid(config) { if (config.showVisionGrid === false) { return null; } const { gridSize: side, visionRadius: radius } = config; const r0 = Math.pow(radius, 2); const r1 = Math.pow(radius - side, 2); const points = []; for (let x = -radius; x <= radius; x += side) { for (let y = -radius; y <= radius; y += side) { // 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; } points.push({ x, y, touch: false }); } } return points; } // ===== RENDERING ===== function repaintContainer(node, arc) { node.style.left = `${arc.endX}px`; node.style.top = `${arc.endY}px`; } function repaintBody(node, arc) { const rad = arc.clockwise ? RAD.t180 - arc.theta : RAD.t360 - arc.theta; node.style.transform = `rotate(${rad + RAD.t45}rad)`; } function repaintCircle(node, arc) { if (node === undefined) { return; } node.style.width = `${2 * arc.radius}px`; node.style.height = `${2 * arc.radius}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`; 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;