You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
292 lines
8.5 KiB
292 lines
8.5 KiB
// 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;
|
|
|