|
|
|
@ -1,10 +1,11 @@
|
|
|
|
|
import { getContext2D, Touch } from '@shadryx/pptk';
|
|
|
|
|
import { PixelPerfectCanvas, PixelPerfectTouch } from '@shadryx/pptk/solid';
|
|
|
|
|
import { createEffect, createSignal, onCleanup } from 'solid-js';
|
|
|
|
|
import { State, StateBuffer } from './simulation/state.js';
|
|
|
|
|
import { Cell, CellType, State, StateBuffer } from './simulation/state.js';
|
|
|
|
|
|
|
|
|
|
const PALETTE = {
|
|
|
|
|
red: [0xf9, 0x44, 0x35],
|
|
|
|
|
red: [0xf4, 0x9d, 0x40],
|
|
|
|
|
brightRed: [0xff, 0xd0, 0xa0],
|
|
|
|
|
dark: 'rgba(0, 0, 0, 0.1)',
|
|
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
@ -15,12 +16,19 @@ export default function Simulation() {
|
|
|
|
|
const [state, setState] = createSignal(new State(width(), height()));
|
|
|
|
|
const [touchMap, setTouchMap] = createSignal(new Map<number, Touch>());
|
|
|
|
|
|
|
|
|
|
function circle(state: StateBuffer, x: number, y: number, value: number) {
|
|
|
|
|
function circle(state: StateBuffer, x: number, y: number, type: number) {
|
|
|
|
|
function createCell(): Cell {
|
|
|
|
|
return {
|
|
|
|
|
type,
|
|
|
|
|
moved: false,
|
|
|
|
|
variation: Math.random(),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
const radius = 2;
|
|
|
|
|
for (let dy = -radius; dy <= radius; dy++) {
|
|
|
|
|
for (let dx = -radius; dx <= radius; dx++) {
|
|
|
|
|
if (dx ** 2 + dy ** 2 <= radius ** 2) {
|
|
|
|
|
state.set(x + dx, y + dy, value);
|
|
|
|
|
state.set(x + dx, y + dy, createCell());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -101,7 +109,7 @@ export default function Simulation() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function draw(canvas: HTMLCanvasElement, state: State) {
|
|
|
|
|
const context = getContext2D(canvas);
|
|
|
|
|
const context = canvas.getContext('2d')!;
|
|
|
|
|
const width = state.width;
|
|
|
|
|
const height = state.height;
|
|
|
|
|
|
|
|
|
@ -109,45 +117,101 @@ function draw(canvas: HTMLCanvasElement, state: State) {
|
|
|
|
|
|
|
|
|
|
const buffer = state.get();
|
|
|
|
|
|
|
|
|
|
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
|
|
|
|
|
|
|
|
function set(x: number, y: number, r: number, g: number, b: number, a: number) {
|
|
|
|
|
const offset = (y * canvas.width + x) * 4;
|
|
|
|
|
imageData.data[offset] = r;
|
|
|
|
|
imageData.data[offset + 1] = g;
|
|
|
|
|
imageData.data[offset + 2] = b;
|
|
|
|
|
imageData.data[offset + 3] = a;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (let y = 0; y < height; y++) {
|
|
|
|
|
for (let x = 0; x < width; x++) {
|
|
|
|
|
const cell = buffer.get(x, y);
|
|
|
|
|
if (cell === 0 || cell === -1) continue;
|
|
|
|
|
|
|
|
|
|
if (cell === 1) {
|
|
|
|
|
const e = edge(buffer, x, y);
|
|
|
|
|
|
|
|
|
|
const lightness = 0.75 + 0.25 * Math.max(e[0] * -0.5 + e[1] * -0.5, 0);
|
|
|
|
|
|
|
|
|
|
context.fillStyle = `rgb(${PALETTE.red.map(v => v * lightness).join(',')})`;
|
|
|
|
|
if (!cell) continue;
|
|
|
|
|
|
|
|
|
|
if (cell.type === CellType.SAND) {
|
|
|
|
|
const [dx, dy] = edge(buffer, x, y);
|
|
|
|
|
const lightness = 0.9 * Math.max(dx * 0.5 + dy * 0.5, 0);
|
|
|
|
|
const variation = 0.95 + 0.05 * (cell.variation ?? 1);
|
|
|
|
|
|
|
|
|
|
set(
|
|
|
|
|
x,
|
|
|
|
|
y,
|
|
|
|
|
Math.round(lerp(PALETTE.red[0], PALETTE.brightRed[0], lightness) * variation),
|
|
|
|
|
Math.round(lerp(PALETTE.red[1], PALETTE.brightRed[1], lightness) * variation),
|
|
|
|
|
Math.round(lerp(PALETTE.red[2], PALETTE.brightRed[2], lightness) * variation),
|
|
|
|
|
255
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
context.fillRect(x, y, 1, 1);
|
|
|
|
|
|
|
|
|
|
if (buffer.get(x + 1, y) === 0) {
|
|
|
|
|
context.fillStyle = PALETTE.dark;
|
|
|
|
|
context.fillRect(x + 1, y, 1, 1);
|
|
|
|
|
if (!buffer.get(x + 1, y)) {
|
|
|
|
|
set(x + 1, y, 0, 0, 0, 40);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
context.putImageData(imageData, 0, 0);
|
|
|
|
|
|
|
|
|
|
// const rawContext = canvas.getContext('2d');
|
|
|
|
|
// const particles = state.get().buffer.reduce((acc, val) => acc + +!!val, 0);
|
|
|
|
|
// rawContext?.fillText(particles.toString(), 10, 10);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const NEIGHBORS = [
|
|
|
|
|
[-1, 0],
|
|
|
|
|
[1, 0],
|
|
|
|
|
[0, 1],
|
|
|
|
|
[0, -1],
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
function edge(buffer: StateBuffer, x: number, y: number): [number, number] {
|
|
|
|
|
const NEIGHBORS = [buffer.get(x, y), -1];
|
|
|
|
|
const currentCell = buffer.get(x, y);
|
|
|
|
|
function isNeighbor(cell: Cell): boolean {
|
|
|
|
|
return cell?.type === currentCell?.type || cell?.type === CellType.WALL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let dx = 0;
|
|
|
|
|
let dy = 0;
|
|
|
|
|
if (!NEIGHBORS.includes(buffer.get(x - 1, y))) dx -= 1;
|
|
|
|
|
if (!NEIGHBORS.includes(buffer.get(x + 1, y))) dx += 1;
|
|
|
|
|
if (!NEIGHBORS.includes(buffer.get(x, y - 1))) dy -= 1;
|
|
|
|
|
if (!NEIGHBORS.includes(buffer.get(x, y + 1))) dy += 1;
|
|
|
|
|
for (const neighbor of NEIGHBORS) {
|
|
|
|
|
if (isNeighbor(buffer.get(x + neighbor[0], y + neighbor[1]))) {
|
|
|
|
|
dx += neighbor[0];
|
|
|
|
|
dy += neighbor[1];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// if (!isNeighbor(buffer.get(x - 1, y))) dx -= 1;
|
|
|
|
|
// if (!isNeighbor(buffer.get(x + 1, y))) dx += 1;
|
|
|
|
|
// if (!isNeighbor(buffer.get(x, y - 1))) dy -= 1;
|
|
|
|
|
// if (!isNeighbor(buffer.get(x, y + 1))) dy += 1;
|
|
|
|
|
|
|
|
|
|
const length = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
|
if (length === 0) return [0, 0];
|
|
|
|
|
|
|
|
|
|
return [dx / length, dy / length];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function edgeBlended(buffer: StateBuffer, x: number, y: number): [number, number] {
|
|
|
|
|
let [sx, sy] = edge(buffer, x, y);
|
|
|
|
|
let n = 1;
|
|
|
|
|
const currentCell = buffer.get(x, y);
|
|
|
|
|
|
|
|
|
|
for (const neighbor of NEIGHBORS) {
|
|
|
|
|
const x2 = x + neighbor[0];
|
|
|
|
|
const y2 = y + neighbor[1];
|
|
|
|
|
|
|
|
|
|
if (buffer.get(x2, y2)?.type === currentCell?.type) {
|
|
|
|
|
const [dx, dy] = edge(buffer, x2, y2);
|
|
|
|
|
sx += dx * 0.5;
|
|
|
|
|
sy += dy * 0.5;
|
|
|
|
|
n += 0.5;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [sx / n, sy / n];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function lerp(a: number, b: number, amount: number): number {
|
|
|
|
|
return a * (1 - amount) + b * amount;
|
|
|
|
|
}
|
|
|
|
|