parent
65b8e275b4
commit
ce06797630
11 changed files with 975 additions and 1472 deletions
@ -1,279 +0,0 @@ |
||||
// 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.
|
||||
|
||||
// This algorithm travels a random period of time around a random arc.
|
||||
// If a wall is detected, a 180-degree turn is executed.
|
||||
|
||||
import Rx, { Observable } from 'rxjs'; |
||||
import AnimationBase from './animation0'; |
||||
import DOM from './dom'; |
||||
import Store from './store'; |
||||
|
||||
const speed = 4; |
||||
const visionRadius = 50; |
||||
const grid = {}; |
||||
const t45 = Math.PI / 4; |
||||
const t90 = Math.PI / 2; |
||||
const t270 = 3 * Math.PI / 2; |
||||
const t360 = Math.PI * 2; |
||||
|
||||
const movementCircle = document.createElement('div'); |
||||
movementCircle.className = 'anim3-movement-circle'; |
||||
|
||||
const visionGrid = document.createElement('div'); |
||||
visionGrid.className = 'anim3-vision-grid'; |
||||
|
||||
const particle = document.createElement('div'); |
||||
particle.className = 'anim3-particle'; |
||||
|
||||
const visionGridPoints = calculateVisionGridPoints(); |
||||
|
||||
function move(store) { |
||||
let { |
||||
clockwise, |
||||
interval, |
||||
circleX, |
||||
circleY, |
||||
particleX, |
||||
particleY, |
||||
radius, |
||||
theta |
||||
} = store.get(); |
||||
|
||||
// If wall detected, tight 180
|
||||
if (detectWall(store) && radius !== 20) { |
||||
const radius0 = radius; |
||||
radius = 20; |
||||
interval = Math.PI * 20 / speed; |
||||
circleX -= (radius - radius0) * Math.cos(theta); |
||||
circleY += (radius - radius0) * Math.sin(theta); |
||||
} else if (interval <= 0) { |
||||
const radius0 = radius; |
||||
|
||||
interval = Math.round(Math.random() * 10) + 50; |
||||
radius = Math.round(Math.random() * 200) + 50; |
||||
|
||||
if (Math.random() < 0.5) { |
||||
clockwise = !clockwise; |
||||
theta = (theta + Math.PI) % t360; |
||||
circleX -= (radius + radius0) * Math.cos(theta); |
||||
circleY += (radius + radius0) * Math.sin(theta); |
||||
} else { |
||||
circleX -= (radius - radius0) * Math.cos(theta); |
||||
circleY += (radius - radius0) * Math.sin(theta); |
||||
} |
||||
} |
||||
|
||||
interval -= 1; |
||||
|
||||
const delta = speed / radius; |
||||
theta += (clockwise ? -delta : +delta); |
||||
theta = (theta > 0 ? theta % t360 : t360 - theta); |
||||
|
||||
particleX = circleX + radius * Math.cos(theta); |
||||
particleY = circleY - radius * Math.sin(theta); |
||||
|
||||
store.set({ |
||||
clockwise, |
||||
interval, |
||||
circleX, |
||||
circleY, |
||||
particleX, |
||||
particleY, |
||||
radius, |
||||
theta |
||||
}); |
||||
} |
||||
|
||||
function detectWall(store) { |
||||
const len = visionGridPoints.length; |
||||
|
||||
const { particleX, particleY } = store.get(); |
||||
|
||||
const gridX = particleX - particleX % 5; |
||||
const gridY = particleY - particleY % 5; |
||||
|
||||
for (let i = 0; i < len; i++) { |
||||
const { x, y } = visionGridPoints[i]; |
||||
const xx = x + gridX; |
||||
const yy = y + gridY; |
||||
|
||||
if (grid[xx] && grid[xx][yy] && grid[xx][yy].type === 'wall') { |
||||
console.warn("Wall detected", xx, yy); |
||||
// TODO this goes somewhere else
|
||||
// DOM.container.dispatchEvent(new CustomEvent('stop'));
|
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
function transformMovementCircle(store) { |
||||
// TODO UPDATE ONLY IF THETA CHANGED - MUST HAVE PREVSTATE
|
||||
const { circleX, circleY, radius, theta } = store.get(); |
||||
const r = Math.abs(radius); |
||||
|
||||
movementCircle.style.left = `${circleX - r}px`; |
||||
movementCircle.style.top = `${circleY - r}px`; |
||||
movementCircle.style.height = `${2 * r}px`; |
||||
movementCircle.style.width = `${2 * r}px`; |
||||
movementCircle.style.borderRadius = `${r}px`; |
||||
} |
||||
|
||||
function transformParticle(store) { |
||||
const { clockwise, particleX, particleY, theta } = store.get(); |
||||
const rad = clockwise ? Math.PI - theta : t360 - theta; |
||||
|
||||
particle.style.left = `${particleX}px`; |
||||
particle.style.top = `${particleY}px`; |
||||
particle.style.transform = `rotate(${rad}rad)`; |
||||
} |
||||
|
||||
function transformVisionGrid(store) { |
||||
const { |
||||
circleX, |
||||
circleY, |
||||
clockwise, |
||||
particleX, |
||||
particleY, |
||||
radius, |
||||
theta |
||||
} = store.get(); |
||||
|
||||
const r0 = Math.min(theta, theta - Math.PI); |
||||
const r1 = Math.max(theta, theta + Math.PI); |
||||
|
||||
const gridX = particleX - particleX % 5; |
||||
const gridY = particleY - particleY % 5; |
||||
|
||||
visionGridPoints.forEach(({ x, y, alpha, div }, i) => { |
||||
if (alpha >= 0 && alpha <= r0) { |
||||
div.style.display = (clockwise ? 'none' : 'block'); |
||||
} else if (alpha >= theta && alpha <= r1) { |
||||
div.style.display = (clockwise ? 'none' : 'block'); |
||||
} else { |
||||
div.style.display = (clockwise ? 'block' : 'none'); |
||||
} |
||||
|
||||
div.style.left = `${x + gridX}px`; |
||||
div.style.top = `${-y + gridY}px`; |
||||
}); |
||||
} |
||||
|
||||
function calculateVisionGridPoints() { |
||||
const gridSize = 5; |
||||
|
||||
const squareGrid = []; |
||||
for (let x = -visionRadius; x <= visionRadius; x += gridSize) { |
||||
for (let y = -visionRadius; y <= visionRadius; y += gridSize) { |
||||
let alpha = Math.atan(y / x); |
||||
|
||||
if (x === 0 && y === 0) { |
||||
alpha = 0; |
||||
} else if (x === 0 && y < 0) { |
||||
alpha = t270; |
||||
} else if (y === 0 && x < 0) { |
||||
alpha = Math.PI; |
||||
} else if (x === 0 && y > 0) { |
||||
alpha = t90; |
||||
} else if (x < 0 && y < 0) { |
||||
alpha = alpha + Math.PI; |
||||
} else if (x <= 0) { |
||||
alpha = Math.PI + alpha; |
||||
} else if (y < 0) { |
||||
alpha = 2 * Math.PI + alpha; |
||||
} |
||||
|
||||
squareGrid.push({ x, y, alpha }); |
||||
} |
||||
} |
||||
|
||||
const r0 = Math.pow(visionRadius, 2); |
||||
const r1 = Math.pow(visionRadius - gridSize, 2); |
||||
|
||||
return squareGrid.reduce((acc, point) => { |
||||
const p = Math.pow(point.x, 2) + Math.pow(point.y, 2); |
||||
if (p > r0 || p < r1) { |
||||
return acc; |
||||
} |
||||
|
||||
const div = document.createElement('div'); |
||||
div.className = 'anim3-dot'; |
||||
|
||||
acc.push(Object.assign(point, { div })); |
||||
return acc; |
||||
}, []); |
||||
} |
||||
|
||||
function reset() { |
||||
while (DOM.container.childNodes.length) { |
||||
DOM.container.removeChild(DOM.container.firstChild); |
||||
} |
||||
|
||||
const store = new Store({ |
||||
clockwise: false, |
||||
interval: 10, |
||||
circleX: 300, |
||||
circleY: 300, |
||||
radius: 270, |
||||
theta: Math.PI * 3 / 4 |
||||
}); |
||||
|
||||
move(store); |
||||
|
||||
transformParticle(store); |
||||
// transformMovementCircle(store);
|
||||
// transformVisionGrid(store);
|
||||
|
||||
DOM.container.appendChild(particle); |
||||
DOM.container.appendChild(movementCircle); |
||||
|
||||
visionGridPoints.forEach(point => { |
||||
DOM.container.appendChild(point.div); |
||||
}); |
||||
|
||||
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 stop$ = Rx.Observable.fromEvent(DOM.container, 'stop'); |
||||
const fps$ = Rx.Observable.interval(1000 / 32) |
||||
.map(_ => store) |
||||
// .take(300)
|
||||
// .take(15)
|
||||
.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(transformParticle); |
||||
// fps$.subscribe(transformMovementCircle);
|
||||
fps$.subscribe(transformVisionGrid); |
||||
}; |
||||
|
||||
const Animation3 = Object.assign({}, AnimationBase, { init, reset }); |
||||
|
||||
export default Animation3; |
@ -1,131 +0,0 @@ |
||||
// Scare mechanic, single particle.
|
||||
import Rx, { Observable } from 'rxjs'; |
||||
import AnimationBase from './animationBase'; |
||||
import DOM from './dom'; |
||||
import Store from './store'; |
||||
|
||||
const evtScare = (scareX, scareY) => new CustomEvent("scare", { detail: { scareX, scareY } }); |
||||
|
||||
const particleDiv = document.createElement('div'); |
||||
particleDiv.className = 'anim2-particle'; |
||||
|
||||
function checkScare([evt, store]) { |
||||
const state = store.get(); |
||||
|
||||
const { evtX, evtY } = DOM.getEventOffsetCoords(evt, DOM.containerBounds); |
||||
const diffX = Math.abs(state.x - evtX); |
||||
const diffY = Math.abs(state.y - evtY); |
||||
|
||||
if (evt.target === particleDiv) { |
||||
DOM.container.dispatchEvent(evtScare(evtX, evtY)); |
||||
} |
||||
}; |
||||
|
||||
function move(acc, i) { |
||||
let { x, y, dx, dy } = acc; |
||||
|
||||
const east = DOM.containerBounds.width - particleDiv.offsetWidth; |
||||
const south = DOM.containerBounds.height - particleDiv.offsetHeight; |
||||
|
||||
x += dx; |
||||
y += dy; |
||||
|
||||
if (x < 0) { |
||||
x = Math.abs(x); |
||||
dx = -dx; |
||||
} |
||||
|
||||
if (x > east) { |
||||
x = Math.round(2 * east - x); |
||||
dx = -dx; |
||||
} |
||||
|
||||
if (y < 0) { |
||||
y = Math.abs(y); |
||||
dy = -dy; |
||||
} |
||||
|
||||
if (y > south) { |
||||
y = Math.round(2 * south - y); |
||||
dy = -dy; |
||||
} |
||||
|
||||
return { x, y, dx, dy }; |
||||
}; |
||||
|
||||
function flee([evt, store]) { |
||||
const initialState = store.get(); |
||||
const fleeRadius = 200; |
||||
const { scareX, scareY } = evt.detail; |
||||
const fps$ = Rx.Observable.interval(1000 / 32); |
||||
|
||||
const frames$ = fps$ |
||||
.scan(move, initialState) |
||||
.takeWhile(state => { |
||||
const xDanger = Math.abs(initialState.x - state.x) < fleeRadius; |
||||
const yDanger = Math.abs(initialState.y - state.y) < fleeRadius; |
||||
|
||||
return xDanger && yDanger; |
||||
}) |
||||
|
||||
frames$.last().subscribe(finalState => { |
||||
store.set(finalState); |
||||
store.set(randomMoveVector()); |
||||
}); |
||||
|
||||
frames$.subscribe(state => { |
||||
particleDiv.style.left = `${state.x}px`; |
||||
particleDiv.style.top = `${state.y}px`; |
||||
}); |
||||
}; |
||||
|
||||
function randomMoveVector() { |
||||
const speed = 10; |
||||
let dx = Math.round(Math.random() * speed); |
||||
let dy = Math.pow(Math.pow(speed, 2) - Math.pow(dx, 2), 0.5); |
||||
|
||||
const negX = Math.random() < 0.5 ? -1 : 1; |
||||
const negY = Math.random() < 0.5 ? -1 : 1; |
||||
|
||||
dx *= negX; |
||||
dy *= negY; |
||||
|
||||
return { dx, dy }; |
||||
} |
||||
|
||||
function reset() { |
||||
if (particleDiv.parentNode) { |
||||
DOM.container.removeChild(particleDiv); |
||||
} |
||||
|
||||
const { dx, dy } = randomMoveVector(); |
||||
const store = new Store({ x: 0, y: 0, dx, dy }); |
||||
const state = store.get(); |
||||
|
||||
particleDiv.style.top = `${state.y}px`; |
||||
particleDiv.style.left = `${state.x}px`; |
||||
|
||||
DOM.container.appendChild(particleDiv); |
||||
|
||||
return store; |
||||
}; |
||||
|
||||
function init() { |
||||
const store = reset(); |
||||
|
||||
const click$ = Rx.Observable |
||||
.fromEvent(DOM.container, 'click') |
||||
// .do(DOM.calcBounds)
|
||||
.do(DOM.highlight) |
||||
.map(evt => [evt, store]) |
||||
.subscribe(checkScare); |
||||
|
||||
Rx.Observable |
||||
.fromEvent(DOM.container, 'scare') |
||||
.map(evt => [evt, store]) |
||||
.subscribe(flee); |
||||
}; |
||||
|
||||
const Animation2 = Object.assign({}, AnimationBase, { init, reset }); |
||||
|
||||
export default Animation2; |
@ -1,292 +0,0 @@ |
||||
// 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.
|
||||
|
||||
// Another tricky part was the wall avoidance and vision grid.
|
||||
|
||||
// This algorithm travels a random period of time around a random arc.
|
||||
// If a wall is detected, a 180-degree turn is executed.
|
||||
|
||||
import Rx, { Observable } from 'rxjs'; |
||||
import AnimationBase from './animation0'; |
||||
import DOM from './dom'; |
||||
import Store from './store'; |
||||
|
||||
const speed = 4; |
||||
const grid = {}; |
||||
const t45 = Math.PI / 4; |
||||
const t90 = Math.PI / 2; |
||||
const t270 = 3 * Math.PI / 2; |
||||
const t360 = Math.PI * 2; |
||||
|
||||
const movementCircle = document.createElement('div'); |
||||
movementCircle.className = 'anim3-movement-circle'; |
||||
|
||||
const particle = document.createElement('div'); |
||||
particle.className = 'anim3-particle'; |
||||
|
||||
// const visionGridPoints = calculateVisionGridPoints();
|
||||
|
||||
function move(store) { |
||||
let { |
||||
arc, |
||||
clockwise, |
||||
frame, |
||||
particleX, |
||||
particleY, |
||||
} = store.get(); |
||||
|
||||
const delta = speed / arc.r; |
||||
arc.t += (clockwise ? -delta : +delta); |
||||
arc.t = (arc.t > 0 ? arc.t % t360 : t360 - arc.t); |
||||
|
||||
// const intersections = detectWall(store);
|
||||
//
|
||||
// if (intersections.length > 0) {
|
||||
// const { xs, ys } = intersections.reduce(
|
||||
// ({ xs, ys }, {x, y}) => ({ xs: xs + x, ys: ys + y }),
|
||||
// { xs: 0, ys: 0 }
|
||||
// );
|
||||
//
|
||||
// const avgX = xs / intersections.length;
|
||||
// const avgY = ys / intersections.length;
|
||||
//
|
||||
// const v = Math.atan((particleY - avgY) / (particleX - avgX));
|
||||
// // console.warn(Math.round(v * 180 / Math.PI))
|
||||
//
|
||||
// const modifier = Math.max(Math.round(v * 180 / Math.PI), 20);
|
||||
//
|
||||
// arc = modifyArc(arc, 20);
|
||||
// // } else if (Math.random() < 0.005) {
|
||||
// // console.warn('changing direction')
|
||||
// // clockwise = !clockwise;
|
||||
// // arc = changeDirection(arc);
|
||||
// } else {
|
||||
// arc = modifyArc(arc, 300);
|
||||
// }
|
||||
|
||||
particleX = arc.x + arc.r * Math.cos(arc.t); |
||||
particleY = arc.y - arc.r * Math.sin(arc.t); |
||||
|
||||
frame += 1; |
||||
|
||||
store.set({ |
||||
arc, |
||||
clockwise, |
||||
frame, |
||||
particleX, |
||||
particleY, |
||||
}); |
||||
} |
||||
|
||||
// generate next arc:
|
||||
// starting point will be locked
|
||||
// starting angle will be locked
|
||||
// therefore tangential
|
||||
function modifyArc(arc, newRadius) { |
||||
const r0 = arc.r; |
||||
const r1 = newRadius; |
||||
|
||||
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 + Math.PI) % t360; |
||||
arc.x -= (2 * arc.r) * Math.cos(arc.t); |
||||
arc.y += (2 * arc.r) * Math.sin(arc.t); |
||||
|
||||
return arc; |
||||
} |
||||
|
||||
// function detectWall(store) {
|
||||
// const len = visionGridPoints.length;
|
||||
//
|
||||
// const { arc, clockwise, particleX, particleY } = store.get();
|
||||
//
|
||||
// const r0 = Math.min(arc.t, arc.t - Math.PI);
|
||||
// const r1 = Math.max(arc.t, arc.t + Math.PI);
|
||||
//
|
||||
// const gridX = particleX - particleX % 5;
|
||||
// const gridY = particleY - particleY % 5;
|
||||
//
|
||||
// return visionGridPoints.reduce((acc, point) => {
|
||||
// const xx = gridX + point.x;
|
||||
// const yy = gridY - point.y;
|
||||
// const alpha = point.alpha;
|
||||
//
|
||||
// if (grid[xx] && grid[xx][yy] && grid[xx][yy].type === 'wall') {
|
||||
// if (clockwise === false && alpha >= 0 && alpha <= r0) {
|
||||
// acc.push(point);
|
||||
// } else if (clockwise === false && alpha >= arc.t && alpha <= r1) {
|
||||
// acc.push(point);
|
||||
// } else if (clockwise === true) {
|
||||
// acc.push(point);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return acc;
|
||||
// }, []);
|
||||
// }
|
||||
|
||||
function transformParticle(store) { |
||||
const { arc, clockwise, particleX, particleY } = store.get(); |
||||
const rad = clockwise ? Math.PI - arc.t : t360 - arc.t; |
||||
|
||||
particle.style.left = `${particleX}px`; |
||||
particle.style.top = `${particleY}px`; |
||||
particle.style.transform = `rotate(${rad}rad)`; |
||||
} |
||||
|
||||
function transformVisionGrid(store) { |
||||
const { |
||||
arc, |
||||
clockwise, |
||||
particleX, |
||||
particleY, |
||||
radius, |
||||
} = store.get(); |
||||
|
||||
const r0 = Math.min(arc.t, arc.t - Math.PI); |
||||
const r1 = Math.max(arc.t, arc.t + Math.PI); |
||||
|
||||
const gridX = particleX - particleX % 5; |
||||
const gridY = particleY - particleY % 5; |
||||
|
||||
visionGridPoints.forEach(({ x, y, alpha, div }, i) => { |
||||
if (alpha >= 0 && alpha <= r0) { |
||||
div.style.display = (clockwise ? 'none' : 'block'); |
||||
// div.className = (clockwise ? 'anim3-dot removed' : 'anim3-dot');
|
||||
} else if (alpha >= arc.t && alpha <= r1) { |
||||
div.style.display = (clockwise ? 'none' : 'block'); |
||||
// div.className = (clockwise ? 'anim3-dot removed' : 'anim3-dot');
|
||||
} else { |
||||
div.style.display = (clockwise ? 'block' : 'none'); |
||||
// div.className = (clockwise ? 'anim3-dot' : 'anim3-dot removed');
|
||||
} |
||||
|
||||
div.style.left = `${x + gridX}px`; |
||||
div.style.top = `${-y + gridY}px`; |
||||
}); |
||||
} |
||||
|
||||
// function calculateVisionGridPoints() {
|
||||
// const gridSize = 5;
|
||||
// const visionRadius = 50;
|
||||
//
|
||||
// const squareGrid = [];
|
||||
// for (let x = -visionRadius; x <= visionRadius; x += gridSize) {
|
||||
// for (let y = -visionRadius; y <= visionRadius; y += gridSize) {
|
||||
// let alpha = Math.atan(y / x);
|
||||
//
|
||||
// if (x === 0 && y === 0) {
|
||||
// alpha = 0;
|
||||
// } else if (x === 0 && y < 0) {
|
||||
// alpha = t270;
|
||||
// } else if (y === 0 && x < 0) {
|
||||
// alpha = Math.PI;
|
||||
// } else if (x === 0 && y > 0) {
|
||||
// alpha = t90;
|
||||
// } else if (x < 0 && y < 0) {
|
||||
// alpha = alpha + Math.PI;
|
||||
// } else if (x <= 0) {
|
||||
// alpha = Math.PI + alpha;
|
||||
// } else if (y < 0) {
|
||||
// alpha = 2 * Math.PI + alpha;
|
||||
// }
|
||||
//
|
||||
// squareGrid.push({ x, y, alpha });
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// const r0 = Math.pow(visionRadius, 2);
|
||||
// const r1 = Math.pow(visionRadius - gridSize, 2);
|
||||
//
|
||||
// return squareGrid.reduce((acc, point) => {
|
||||
// const p = Math.pow(point.x, 2) + Math.pow(point.y, 2);
|
||||
// if (p > r0 || p < r1) {
|
||||
// return acc;
|
||||
// }
|
||||
//
|
||||
// const div = document.createElement('div');
|
||||
// div.className = 'anim3-dot';
|
||||
//
|
||||
// acc.push(Object.assign(point, { div }));
|
||||
// return acc;
|
||||
// }, []);
|
||||
// }
|
||||
|
||||
function reset() { |
||||
while (DOM.container.childNodes.length) { |
||||
DOM.container.removeChild(DOM.container.firstChild); |
||||
} |
||||
|
||||
const store = new Store({ |
||||
arc: { |
||||
r: Math.round(Math.random() * 200) + 100, |
||||
t: Math.random() * t360, |
||||
x: 300, |
||||
y: 300, |
||||
}, |
||||
clockwise: false, |
||||
}); |
||||
|
||||
move(store); |
||||
|
||||
transformParticle(store); |
||||
// transformMovementCircle(store);
|
||||
// transformVisionGrid(store);
|
||||
|
||||
DOM.container.appendChild(particle); |
||||
DOM.container.appendChild(movementCircle); |
||||
|
||||
// visionGridPoints.forEach(point => {
|
||||
// DOM.container.appendChild(point.div);
|
||||
// });
|
||||
|
||||
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 stop$ = Rx.Observable.fromEvent(DOM.container, 'stop'); |
||||
const fps$ = Rx.Observable.interval(1000 / 32) |
||||
.map(i => store.bind(null, i)) |
||||
// .take(300)
|
||||
// .take(15)
|
||||
.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(transformParticle); |
||||
fps$.subscribe(transformVisionGrid); |
||||
// fps$.subscribe(transformMovementCircle);
|
||||
}; |
||||
|
||||
const Animation3 = Object.assign({}, AnimationBase, { init, reset }); |
||||
|
||||
export default Animation3; |
@ -0,0 +1,95 @@ |
||||
import Rx, { Observable } from 'rxjs'; |
||||
import Grid from './grid'; |
||||
import Particle from './particle'; |
||||
import Store from './store'; |
||||
import Controls from './controls'; |
||||
import { CONTROLS, ENTITIES } from './enums'; |
||||
|
||||
function Animation3a() { |
||||
this.options = { |
||||
cohesion: true, |
||||
count: 2, |
||||
maxCount: 5, |
||||
showVisionGrid: true, |
||||
speed: 4 |
||||
}; |
||||
|
||||
this.container = document.getElementById('animation3a'); |
||||
this.bounds = this.container.getBoundingClientRect(); |
||||
|
||||
this.particles = []; |
||||
this.globalGrid = new Grid(); |
||||
|
||||
const controls = new Controls( |
||||
document.getElementById('controls3a'), |
||||
this.options |
||||
); |
||||
|
||||
controls.mount().subscribe(this.subscriber.bind(this)); |
||||
|
||||
this.updateAnimating(this.options.animating); |
||||
this.updateCount(this.options.count); |
||||
|
||||
// TODO extract Arc
|
||||
// TODO X dimension modified by core UI, maybe recalc grid in animation start?
|
||||
// TODO remove bottom padding from Disqus
|
||||
// TODO ANIM2ab randomize hazards
|
||||
// TODO fix "hangup" small radius evade bug
|
||||
|
||||
// TODO ANIM3a perf Scale vision grid to 1000 particles
|
||||
// TODO hazard grid, particles grid
|
||||
// TODO completely seal Particle
|
||||
|
||||
// TODO ANIM3a cohesion
|
||||
// TODO ANIM3b separation
|
||||
// TODO ANIM3c alignment
|
||||
}; |
||||
|
||||
Animation3a.prototype.subscriber = function({ key, value }) { |
||||
switch(key) { |
||||
case CONTROLS.ANIMATING: this.updateAnimating(value); break; |
||||
case CONTROLS.COUNT: this.updateCount(value); break; |
||||
case CONTROLS.SPEED: this.updateSpeed(value); break; |
||||
} |
||||
} |
||||
|
||||
Animation3a.prototype.nextFrame = function() { |
||||
this.particles.forEach(p => { |
||||
const prevX = p.arc.endX; |
||||
const prevY = p.arc.endY; |
||||
|
||||
p.nextFrame(this.globalGrid); |
||||
|
||||
this.globalGrid.deletePoint({ x: prevX, y: prevY, type: ENTITIES.PARTICLE }); |
||||
this.globalGrid.setPoint({ x: p.arc.endX, y: p.arc.endY, type: ENTITIES.PARTICLE }, p); |
||||
}); |
||||
} |
||||
|
||||
Animation3a.prototype.updateAnimating = function(isAnimating) { |
||||
this.options.animating = isAnimating; |
||||
|
||||
if (isAnimating) { |
||||
const fps$ = Rx.Observable.interval(1000 / 32) |
||||
.takeWhile(_ => this.options.animating); |
||||
|
||||
fps$.subscribe(this.nextFrame.bind(this)); |
||||
} |
||||
} |
||||
|
||||
Animation3a.prototype.updateCount = function(count) { |
||||
while (this.particles.length > count) { |
||||
delete this.particles.pop().remove(); |
||||
} |
||||
|
||||
while (this.particles.length < count) { |
||||
const p = new Particle(this.container, this.bounds, this.options, this.globalGrid); |
||||
this.particles.push(p); |
||||
} |
||||
} |
||||
|
||||
Animation3a.prototype.updateSpeed = function(value) { |
||||
this.options.speed = value; |
||||
this.particles.forEach(p => p.updateConfig({ speed: value })); |
||||
} |
||||
|
||||
export default Animation3a; |
File diff suppressed because one or more lines are too long
@ -0,0 +1,31 @@ |
||||
function getKey({ x, y, type }) { |
||||
const gridX = x - x % 5; |
||||
const gridY = y - y % 5; |
||||
|
||||
return `${gridX}-${gridY}`; |
||||
} |
||||
|
||||
export default function Grid() { |
||||
this.points = {}; |
||||
this.gridSize = 5; |
||||
}; |
||||
|
||||
Grid.prototype.setPoint = function({ x, y, type }, detail) { |
||||
this.points[getKey({ x, y, type })] = detail; |
||||
}; |
||||
|
||||
Grid.prototype.getPoint = function({ x, y, type }) { |
||||
return this.points[getKey({ x, y, type })]; |
||||
} |
||||
|
||||
Grid.prototype.deletePoint = function({ x, y, type }) { |
||||
delete this.points[getKey({ x, y, type })]; |
||||
}; |
||||
|
||||
// Grid.prototype.setArea = function({ x, y, w, h, type }) {
|
||||
// for (let i = x; i <= (x + w); i += this.gridSize) {
|
||||
// for (let j = y; j <= (y + h); j += this.gridSize) {
|
||||
// this.setPoint({ x: i, y: j, type });
|
||||
// }
|
||||
// }
|
||||
// };
|
@ -1,16 +1,18 @@ |
||||
import Rx, { Observable } from 'rxjs'; |
||||
import Controls from './controls'; |
||||
import Animation1a from './animation1a'; |
||||
import Animation1b from './animation1b'; |
||||
import Animation2a from './animation2a'; |
||||
import Animation2b from './animation2b'; |
||||
// import Animation1a from './animation1a';
|
||||
// import Animation1b from './animation1b';
|
||||
// import Animation2a from './animation2a';
|
||||
// import Animation2b from './animation2b';
|
||||
import Animation3a from './animation3a'; |
||||
|
||||
require('../css/reset.scss'); |
||||
require('../css/index.scss'); |
||||
require('../css/particle.scss'); |
||||
require('../css/controls.scss'); |
||||
|
||||
new Animation1a(); |
||||
new Animation1b(); |
||||
new Animation2a(); |
||||
new Animation2b(); |
||||
// new Animation1a();
|
||||
// new Animation1b();
|
||||
// new Animation2a();
|
||||
// new Animation2b();
|
||||
new Animation3a(); |
||||
|
Loading…
Reference in new issue