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.
 
 

253 lines
7.2 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.
import Rx, { Observable } from 'rxjs';
import AnimationBase from './animation0';
import DOM from './dom';
import Store from './store';
const speed = 6;
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,
radius,
theta
} = store.get();
if (interval <= 0) {
const particleX = circleX + radius * Math.cos(theta);
const particleY = circleY - radius * Math.sin(theta);
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;
// }
if (Math.random() < 0.5) {
clockwise = !clockwise;
theta = (theta > Math.PI ? theta - Math.PI : theta + Math.PI);
radius *= -1;
}
circleX = particleX - radius * Math.cos(theta);
circleY = particleY + radius * Math.sin(theta);
}
// interval -= 1;
// TODO store delta
const delta = speed / radius;
theta += (clockwise ? -delta : +delta);
theta %= t360;
store.set({
clockwise,
interval,
circleX,
circleY,
radius,
theta
});
// detectWall(store);
}
function detectWall(store) {
// const { circleX, circleY, radius, theta } = store.get();
// const len = visionGridPoints.length;
//
// for (let i = 0; i < len; i++) {
// const { x, y } = visionGridPoints[i];
// if (grid[x] && grid[x][y] && grid[x][y].type === 'wall') {
// console.warn("Wall detected", x, y);
// return true;
// }
// }
//
// return false;
}
function transformMovementCircle(store) {
// TODO UPDATE ONLY IF THETA CHANGED - MUST HAVE PREVSTATE
const { circleX, circleY, radius, theta } = store.get();
movementCircle.style.left = `${circleX - radius}px`;
movementCircle.style.top = `${circleY - radius}px`;
movementCircle.style.height = `${2 * radius}px`;
movementCircle.style.width = `${2 * radius}px`;
movementCircle.style.borderRadius = `${radius}px`;
}
function transformParticle(store) {
const { clockwise, circleX, circleY, radius, theta } = store.get();
const rad = clockwise ? Math.PI - theta : t360 - theta;
particle.style.left = `${circleX + radius * Math.cos(theta)}px`;
particle.style.top = `${circleY - radius * Math.sin(theta)}px`;
particle.style.transform = `rotate(${rad}rad)`;
}
function transformVisionGrid(store) {
const { clockwise, circleX, circleY, radius, theta } = store.get();
const particleX = circleX + radius * Math.cos(theta);
const particleY = circleY - radius * Math.sin(theta);
const r0 = Math.min(theta, theta - Math.PI);
const r1 = Math.max(theta, theta + Math.PI);
visionGridPoints.forEach(({ x, y, alpha, div }) => {
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 + particleX}px`;
div.style.top = `${-y + particleY}px`;
});
}
function calculateVisionGridPoints() {
const visionRadius = 50;
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 visionRadiusSquared = Math.pow(visionRadius, 2);
return squareGrid.reduce((acc, point) => {
if ((Math.pow(point.x, 2) + Math.pow(point.y, 2)) > visionRadiusSquared) {
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: true,
interval: 10,
circleX: 300,
circleY: 300,
radius: 150,
theta: 0
});
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(0)
// .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;