// Single particle movement. // Goal: per-frame decisions // 20 x 20 grid // The trickiest portion of this iteration was animating the curved paths. Calculating arc length (for scalar speed) along an elliptical // geometry is quite difficult, so the current solution uses circular paths, which change radius and sometimes rotation after a random // number of frames have emitted. A smoothstep cubic curve was considered, but maintaining consistent entry and exit angles will affect // performance for large particle counts. import Rx, { Observable } from 'rxjs'; import AnimationBase from './animation0'; import DOM from './dom'; import Store from './store'; const speed = 2; const visionRadius = 50; const grid = {}; const movementCircle = document.createElement('div'); movementCircle.className = 'anim3-movement-circle'; const visionCircle = document.createElement('div'); // visionCircle.className = 'anim3-vision-circle'; const particle = document.createElement('div'); particle.className = 'anim3-particle'; function move(store) { let { clockwise, interval, particleX, particleY, radius, theta } = store.get(); if (interval <= 0) { interval = Math.round(Math.random() * 10) + 50; radius = Math.round(Math.random() * 200) + 50 // IF SEEING A WALL: tight 180 // if ( < visionRadius // if (Math.random() < 0.3) { // console.warn("turn!") // state.radius = 20; // state.interval = Math.PI * 20 / speed; // } detectWall(store); if (Math.random() < 0.5) { clockwise = !clockwise; theta = (theta > Math.PI ? theta - Math.PI : theta + Math.PI); } } interval -= 1; const prevTheta = theta; const delta = speed / radius; theta += (clockwise ? -delta : delta); particleX -= radius * Math.cos(theta) - radius * Math.cos(prevTheta); particleY -= radius * Math.sin(theta) - radius * Math.sin(prevTheta); store.set({ clockwise, interval, particleX, particleY, radius, theta }); } function detectWall(store) { const { particleX, particleY, radius, theta } = store.get(); } function paintMovementCircle(store) { // TODO UPDATE ONLY IF THETA CHANGED - MUST HAVE PREVSTATE const { particleX, particleY, radius, theta } = store.get(); movementCircle.style.left = `${particleX - radius - radius * Math.cos(theta)}px`; movementCircle.style.top = `${particleY - radius + radius * Math.sin(theta)}px`; movementCircle.style.height = `${2 * radius}px`; movementCircle.style.width = `${2 * radius}px`; movementCircle.style.borderRadius = `${radius}px`; } function paintParticle(store) { const { clockwise, particleX, particleY, theta } = store.get(); const rad = (clockwise ? theta + Math.PI / 2 : theta - Math.PI / 2); particle.style.left = `${particleX}px`; particle.style.top = `${particleY}px`; particle.style.transform = `rotate(${rad}rad)`; } function reset() { while (DOM.container.childNodes.length) { DOM.container.removeChild(DOM.container.firstChild); } const store = new Store({ clockwise: false, interval: 10, particleX: 300, particleY: 150, radius: 150, theta: Math.PI / 2 }); move(store); DOM.container.appendChild(particle); DOM.container.appendChild(movementCircle); return store; }; function init() { const store = reset(); for (let x = 0; x <= 600; x += 5) { grid[x] = {}; for (let y = 0; y <= 600; y += 5) { grid[x][y] = { type: null }; if (x === 0 || y === 0 || x === 600 || y === 600) { grid[x][y] = { type: 'wall' }; } } } const a = Math.round(Math.random() * 180) + 20; const b = Math.round(Math.random() * 180) + 20; const stop$ = Rx.Observable.fromEvent(DOM.container, 'stop'); const fps$ = Rx.Observable.interval(1000 / 32) .map(_ => store) // .take(100) .takeUntil(stop$); console.error("CLICK TO STOP"); const click$ = Rx.Observable.fromEvent(DOM.container, 'click'); click$.subscribe(() => { DOM.container.dispatchEvent(new CustomEvent('stop')); }); fps$.subscribe(move); fps$.subscribe(paintParticle); // fps$.subscribe(paintMovementCircle); }; const Animation3 = Object.assign({}, AnimationBase, { init, reset }); export default Animation3;