From 5e9039efadabdc2c5085bd6394de366720f62842 Mon Sep 17 00:00:00 2001 From: Lani Aung Date: Thu, 5 Jun 2025 22:07:26 -0700 Subject: [PATCH] Much better consistency and I finally figured out that substrate was multiplying draw calls --- src/crack.ts | 28 +++++++++++----------------- src/sand-painter.ts | 32 +++++++++++++++++++++----------- src/state.ts | 37 +++++++++++++++++++++++++++++++++---- src/substrate.ts | 8 ++------ www/main.js | 8 ++++---- 5 files changed, 71 insertions(+), 42 deletions(-) diff --git a/src/crack.ts b/src/crack.ts index a9530fc..1fccc3d 100644 --- a/src/crack.ts +++ b/src/crack.ts @@ -9,23 +9,16 @@ export default class Crack { private state: State constructor(state: State, x: number, y: number, angle: number) { - this.painter = new SandPainter(state.getRandomColor(), state.isWithinBoundary.bind(state)) + this.painter = new SandPainter(state.getRandomColor()) this.state = state - this.x = x - this.y = y - this.angle = angle - this.init() - } - - init() { - const flip = Math.random() < 0.5 - this.start(this.x, this.y, this.angle + (90 + Math.floor(Math.random() * 4.1 - 2)) * (flip ? -1 : 1)) + this.start(x, y, angle) } start(x: number, y: number, angle: number) { this.x = x + .61 * Math.cos(angle * Math.PI / 180) this.y = y + .61 * Math.sin(angle * Math.PI / 180) - this.angle = angle + const flip = Math.random() < 0.5 + this.angle = angle + (90 + Math.floor(Math.random() * 4.1 - 2)) * (flip ? -1 : 1) } draw() { @@ -39,16 +32,17 @@ export default class Crack { const x = Math.floor(this.x) const y = Math.floor(this.y) if (this.state.isWithinBoundary(x, y)) { - const seed = this.state.grid[x][y] - if (seed > 10000 || Math.abs(seed - this.angle) < 5) { + const angle = this.state.grid[x][y] + if (angle > 10000) { this.state.grid[x][y] = Math.floor(this.angle) this.state.seeds.push({x, y}) - } else if (Math.abs(seed - this.angle) > 2) { - this.init() - this.state.addCrack() + } else if (this.state.grid[x][y] != Math.floor(this.angle)) { + const entry = this.state.getNewEntry() + this.start(entry.x, entry.y, entry.angle) } } else { - this.init() + const entry = this.state.getNewEntry() + this.start(entry.x, entry.y, entry.angle) this.state.addCrack() } } diff --git a/src/sand-painter.ts b/src/sand-painter.ts index 591e813..7a9165a 100644 --- a/src/sand-painter.ts +++ b/src/sand-painter.ts @@ -3,25 +3,35 @@ import State from "./state" export default class SandPainter { private color: number private gain: number - private readonly boundaryCheck: (x: number, y: number) => boolean - constructor(color: number, boundaryCheck: (x: number, y: number) => boolean) { + constructor(color: number) { this.color = color - this.boundaryCheck = boundaryCheck this.gain = Math.random() / 10 } - render(x: number, y: number, angle: number) { - let open = true, paintX: number, paintY: number + render(x: number, y: number, angle: number, state: State) { + let open = true, endX = x, endY = y while (open) { - paintX = x + .81 * Math.sin(angle * Math.PI / 180) - paintY = y - .81 * Math.cos(angle * Math.PI / 180) - - const gridX = Math.floor(paintX) - const gridY = Math.floor(paintY) - if (this.boundaryCheck(gridX, gridY)) { + endX += .81 * Math.sin(angle * Math.PI / 180) + endY -= .81 * Math.cos(angle * Math.PI / 180) + const gridX = Math.floor(endX) + const gridY = Math.floor(endY) + if (state.isWithinBoundary(gridX, gridY)) { + open = state.grid[gridX][gridY] > 10000 + } else { + open = false } } + + const grains = 64 + const hex = this.color.toString(16) + const w = this.gain / (grains - 1) // some kind of multiplier + const context = state.canvas.getContext("2d") + for (let i = 0; i < grains; i++) { + const alpha = (.1 - i / (grains * 10)) * 255 + context.fillStyle = `#${hex.padStart(6, "0")}${alpha.toString(16).padStart(2, "0")}` + + } } } \ No newline at end of file diff --git a/src/state.ts b/src/state.ts index 9399e82..297126f 100644 --- a/src/state.ts +++ b/src/state.ts @@ -14,13 +14,16 @@ export default class State { this.maxCracks = maxCracks this.grid = [] this.cracks = [] + this.seeds = [] this.init() } init() { - this.seeds = [] - this.cracks = [] + this.cracks.length = 0 + this.grid.length = 0 + this.seeds.length = 0 + const height = this.canvas.height const width = this.canvas.width const context = this.canvas.getContext("2d") @@ -28,7 +31,7 @@ export default class State { context.fillRect(0, 0, width, height) for (let x = 0; x < width; x++) { - if (!this.grid[x]) this.grid[x] = [] + this.grid[x] = [] for (let y = 0; y < height; y++) { this.grid[x][y] = 10001 @@ -45,7 +48,10 @@ export default class State { } addCrack() { - if (this.cracks.length < this.maxCracks) this.cracks.push(new Crack(this)) + if (this.cracks.length >= this.maxCracks) return + + const {x, y, angle} = this.getNewEntry() + this.cracks.push(new Crack(this, x, y, angle)) } getRandomColor(): number { @@ -55,4 +61,27 @@ export default class State { isWithinBoundary(x: number, y: number): boolean { return x >= 0 && x < this.canvas.width && y >= 0 && y < this.canvas.height } + + getNewEntry(): {x: number, y: number, angle: number} { + if (!this.seeds.length) { + const x = Math.floor(Math.random() * this.canvas.width) + const y = Math.floor(Math.random() * this.canvas.height) + let angle = this.grid[x][y] + + if (angle > 10000) { + angle = Math.floor(Math.random() * 360) + this.grid[x][y] = angle + } + + return {x, y, angle} + } + + const randomIndex = Math.floor(Math.random() * this.seeds.length) + const entry = this.seeds.splice(randomIndex, 1)[0] + return { + x: entry.x, + y: entry.y, + angle: this.grid[entry.x][entry.y] + } + } } \ No newline at end of file diff --git a/src/substrate.ts b/src/substrate.ts index 81a1d16..921adcd 100644 --- a/src/substrate.ts +++ b/src/substrate.ts @@ -23,11 +23,7 @@ export default class Substrate { requestAnimationFrame(draw) } - setInterval(() => { - me.state.init() - requestAnimationFrame(draw) - }, 120 * 1000) - - requestAnimationFrame(draw) + setInterval(() => me.state.init(), 120 * 1000) + draw() } } \ No newline at end of file diff --git a/www/main.js b/www/main.js index 1b88598..71fb5f0 100644 --- a/www/main.js +++ b/www/main.js @@ -16,7 +16,7 @@ \**********************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _sand_painter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./sand-painter */ \"./src/sand-painter.ts\");\n\nvar Crack = /** @class */ (function () {\n function Crack(state) {\n this.x = 0;\n this.y = 0;\n this.angle = 0;\n this.painter = new _sand_painter__WEBPACK_IMPORTED_MODULE_0__[\"default\"](state.getRandomColor(), state.isWithinBoundary.bind(state));\n this.state = state;\n this.init();\n }\n Crack.prototype.init = function () {\n var index = Math.floor(Math.random() * this.state.seeds.length);\n var _a = this.state.seeds.splice(index, 1)[0], x = _a.x, y = _a.y;\n var flip = Math.random() < 0.5;\n var angle = this.state.grid[x][y];\n this.start(x, y, angle + (90 + Math.floor(Math.random() * 4.1 - 2)) * (flip ? -1 : 1));\n };\n Crack.prototype.start = function (x, y, angle) {\n this.x = x + .61 * Math.cos(angle * Math.PI / 180);\n this.y = y + .61 * Math.sin(angle * Math.PI / 180);\n this.angle = angle;\n };\n Crack.prototype.draw = function () {\n this.x += .42 * Math.cos(this.angle * Math.PI / 180);\n this.y += .42 * Math.sin(this.angle * Math.PI / 180);\n var context = this.state.canvas.getContext(\"2d\");\n context.fillStyle = \"#000\";\n context.fillRect(this.x, this.y, 1, 1);\n var x = Math.floor(this.x);\n var y = Math.floor(this.y);\n if (this.state.isWithinBoundary(x, y)) {\n var seed = this.state.grid[x][y];\n if (seed > 10000 || Math.abs(seed - this.angle) < 5) {\n this.state.grid[x][y] = Math.floor(this.angle);\n this.state.seeds.push({ x: x, y: y });\n }\n else if (Math.abs(seed - this.angle) > 2) {\n this.init();\n this.state.addCrack();\n }\n }\n else {\n this.init();\n this.state.addCrack();\n }\n };\n return Crack;\n}());\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Crack);\n\n\n//# sourceURL=webpack:///./src/crack.ts?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _sand_painter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./sand-painter */ \"./src/sand-painter.ts\");\n\nvar Crack = /** @class */ (function () {\n function Crack(state, x, y, angle) {\n this.x = 0;\n this.y = 0;\n this.angle = 0;\n this.painter = new _sand_painter__WEBPACK_IMPORTED_MODULE_0__[\"default\"](state.getRandomColor());\n this.state = state;\n this.start(x, y, angle);\n }\n Crack.prototype.start = function (x, y, angle) {\n this.x = x + .61 * Math.cos(angle * Math.PI / 180);\n this.y = y + .61 * Math.sin(angle * Math.PI / 180);\n var flip = Math.random() < 0.5;\n this.angle = angle + (90 + Math.floor(Math.random() * 4.1 - 2)) * (flip ? -1 : 1);\n };\n Crack.prototype.draw = function () {\n this.x += .42 * Math.cos(this.angle * Math.PI / 180);\n this.y += .42 * Math.sin(this.angle * Math.PI / 180);\n var context = this.state.canvas.getContext(\"2d\");\n context.fillStyle = \"#000\";\n context.fillRect(this.x, this.y, 1, 1);\n var x = Math.floor(this.x);\n var y = Math.floor(this.y);\n if (this.state.isWithinBoundary(x, y)) {\n var angle = this.state.grid[x][y];\n if (angle > 10000) {\n this.state.grid[x][y] = Math.floor(this.angle);\n this.state.seeds.push({ x: x, y: y });\n }\n else if (this.state.grid[x][y] != Math.floor(this.angle)) {\n var entry = this.state.getNewEntry();\n this.start(entry.x, entry.y, entry.angle);\n }\n }\n else {\n var entry = this.state.getNewEntry();\n this.start(entry.x, entry.y, entry.angle);\n this.state.addCrack();\n }\n };\n return Crack;\n}());\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Crack);\n\n\n//# sourceURL=webpack:///./src/crack.ts?"); /***/ }), @@ -36,7 +36,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _sub \*****************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\nvar SandPainter = /** @class */ (function () {\n function SandPainter(color, boundaryCheck) {\n this.color = color;\n this.boundaryCheck = boundaryCheck;\n this.gain = Math.random() / 10;\n }\n SandPainter.prototype.render = function (x, y, angle) {\n var open = true, paintX, paintY;\n while (open) {\n paintX = x + .81 * Math.sin(angle * Math.PI / 180);\n paintY = y - .81 * Math.cos(angle * Math.PI / 180);\n var gridX = Math.floor(paintX);\n var gridY = Math.floor(paintY);\n if (this.boundaryCheck(gridX, gridY)) {\n }\n }\n };\n return SandPainter;\n}());\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (SandPainter);\n\n\n//# sourceURL=webpack:///./src/sand-painter.ts?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\nvar SandPainter = /** @class */ (function () {\n function SandPainter(color) {\n this.color = color;\n this.gain = Math.random() / 10;\n }\n SandPainter.prototype.render = function (x, y, angle, state) {\n var open = true, endX = x, endY = y;\n while (open) {\n endX += .81 * Math.sin(angle * Math.PI / 180);\n endY -= .81 * Math.cos(angle * Math.PI / 180);\n var gridX = Math.floor(endX);\n var gridY = Math.floor(endY);\n if (state.isWithinBoundary(gridX, gridY)) {\n open = state.grid[gridX][gridY] > 10000;\n }\n else {\n open = false;\n }\n }\n var grains = 64;\n var hex = this.color.toString(16);\n var w = this.gain / (grains - 1);\n var context = state.canvas.getContext(\"2d\");\n for (var i = 0; i < grains; i++) {\n var alpha = .1 - i / (grains * 10);\n context.fillStyle = \"\";\n }\n };\n return SandPainter;\n}());\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (SandPainter);\n\n\n//# sourceURL=webpack:///./src/sand-painter.ts?"); /***/ }), @@ -46,7 +46,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac \**********************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _crack__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./crack */ \"./src/crack.ts\");\n\nvar State = /** @class */ (function () {\n function State(colors, maxCracks, canvas) {\n this.canvas = canvas;\n this.colors = colors;\n this.maxCracks = maxCracks;\n this.grid = [];\n this.cracks = [];\n this.init();\n }\n State.prototype.init = function () {\n this.seeds = [];\n this.cracks = [];\n var height = this.canvas.height;\n var width = this.canvas.width;\n var context = this.canvas.getContext(\"2d\");\n context.fillStyle = \"rgb(255 255 255 / 95%)\";\n context.fillRect(0, 0, width, height);\n for (var x = 0; x < width; x++) {\n if (!this.grid[x])\n this.grid[x] = [];\n for (var y = 0; y < height; y++) {\n this.grid[x][y] = 10001;\n }\n }\n for (var i = 0; i < 3; i++) {\n var x = Math.floor(Math.random() * width);\n var y = Math.floor(Math.random() * height);\n this.grid[x][y] = Math.floor(Math.random() * 360);\n this.seeds.push({ x: x, y: y });\n }\n this.cracks = [new _crack__WEBPACK_IMPORTED_MODULE_0__[\"default\"](this), new _crack__WEBPACK_IMPORTED_MODULE_0__[\"default\"](this), new _crack__WEBPACK_IMPORTED_MODULE_0__[\"default\"](this)];\n };\n State.prototype.addCrack = function () {\n if (this.cracks.length < this.maxCracks)\n this.cracks.push(new _crack__WEBPACK_IMPORTED_MODULE_0__[\"default\"](this));\n };\n State.prototype.getRandomColor = function () {\n return this.colors[Math.floor(Math.random() * this.colors.length)];\n };\n State.prototype.isWithinBoundary = function (x, y) {\n return x >= 0 && x < this.canvas.width && y >= 0 && y < this.canvas.height;\n };\n return State;\n}());\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (State);\n\n\n//# sourceURL=webpack:///./src/state.ts?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _crack__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./crack */ \"./src/crack.ts\");\n\nvar State = /** @class */ (function () {\n function State(colors, maxCracks, canvas) {\n this.canvas = canvas;\n this.colors = colors;\n this.maxCracks = maxCracks;\n this.grid = [];\n this.cracks = [];\n this.seeds = [];\n this.init();\n }\n State.prototype.init = function () {\n this.cracks.length = 0;\n this.grid.length = 0;\n this.seeds.length = 0;\n var height = this.canvas.height;\n var width = this.canvas.width;\n var context = this.canvas.getContext(\"2d\");\n context.fillStyle = \"rgb(255 255 255 / 95%)\";\n context.fillRect(0, 0, width, height);\n for (var x = 0; x < width; x++) {\n this.grid[x] = [];\n for (var y = 0; y < height; y++) {\n this.grid[x][y] = 10001;\n }\n }\n for (var i = 0; i < 3; i++) {\n var x = Math.floor(Math.random() * width);\n var y = Math.floor(Math.random() * height);\n var angle = Math.floor(Math.random() * 360);\n this.cracks.push(new _crack__WEBPACK_IMPORTED_MODULE_0__[\"default\"](this, x, y, angle));\n this.grid[x][y] = angle;\n }\n };\n State.prototype.addCrack = function () {\n if (this.cracks.length >= this.maxCracks)\n return;\n var _a = this.getNewEntry(), x = _a.x, y = _a.y, angle = _a.angle;\n this.cracks.push(new _crack__WEBPACK_IMPORTED_MODULE_0__[\"default\"](this, x, y, angle));\n };\n State.prototype.getRandomColor = function () {\n return this.colors[Math.floor(Math.random() * this.colors.length)];\n };\n State.prototype.isWithinBoundary = function (x, y) {\n return x >= 0 && x < this.canvas.width && y >= 0 && y < this.canvas.height;\n };\n State.prototype.getNewEntry = function () {\n if (!this.seeds.length) {\n var x = Math.floor(Math.random() * this.canvas.width);\n var y = Math.floor(Math.random() * this.canvas.height);\n var angle = this.grid[x][y];\n if (angle > 10000) {\n angle = Math.floor(Math.random() * 360);\n this.grid[x][y] = angle;\n }\n return { x: x, y: y, angle: angle };\n }\n var randomIndex = Math.floor(Math.random() * this.seeds.length);\n var entry = this.seeds.splice(randomIndex, 1)[0];\n return {\n x: entry.x,\n y: entry.y,\n angle: this.grid[entry.x][entry.y]\n };\n };\n return State;\n}());\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (State);\n\n\n//# sourceURL=webpack:///./src/state.ts?"); /***/ }), @@ -56,7 +56,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac \**************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _state__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./state */ \"./src/state.ts\");\n// TS attempt of http://www.complexification.net/gallery/machines/substrate/index.php by laniaung.me\n\nvar Substrate = /** @class */ (function () {\n function Substrate(maxCracks, colors) {\n if (!(colors === null || colors === void 0 ? void 0 : colors.length))\n colors = [0x3a1e3e, 0x7c2d3b, 0xb94f3c, 0xf4a462, 0xf9c54e]; // https://colormagic.app/palette/671eeb6c42273fc4c1bb1ac7\n var canvas = document.createElement(\"canvas\");\n canvas.width = document.body.clientWidth;\n canvas.height = document.body.clientHeight;\n document.body.appendChild(canvas);\n this.state = new _state__WEBPACK_IMPORTED_MODULE_0__[\"default\"](colors, maxCracks, canvas);\n this.init();\n }\n Substrate.prototype.init = function () {\n var me = this;\n var draw = function () {\n for (var _i = 0, _a = me.state.cracks; _i < _a.length; _i++) {\n var crack = _a[_i];\n crack.draw();\n }\n requestAnimationFrame(draw);\n };\n setInterval(function () {\n me.state.init();\n requestAnimationFrame(draw);\n }, 120 * 1000);\n requestAnimationFrame(draw);\n };\n return Substrate;\n}());\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Substrate);\n\n\n//# sourceURL=webpack:///./src/substrate.ts?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _state__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./state */ \"./src/state.ts\");\n// TS attempt of http://www.complexification.net/gallery/machines/substrate/index.php by laniaung.me\n\nvar Substrate = /** @class */ (function () {\n function Substrate(maxCracks, colors) {\n if (!(colors === null || colors === void 0 ? void 0 : colors.length))\n colors = [0x3a1e3e, 0x7c2d3b, 0xb94f3c, 0xf4a462, 0xf9c54e]; // https://colormagic.app/palette/671eeb6c42273fc4c1bb1ac7\n var canvas = document.createElement(\"canvas\");\n canvas.width = document.body.clientWidth;\n canvas.height = document.body.clientHeight;\n document.body.appendChild(canvas);\n this.state = new _state__WEBPACK_IMPORTED_MODULE_0__[\"default\"](colors, maxCracks, canvas);\n this.init();\n }\n Substrate.prototype.init = function () {\n var me = this;\n var draw = function () {\n for (var _i = 0, _a = me.state.cracks; _i < _a.length; _i++) {\n var crack = _a[_i];\n crack.draw();\n }\n requestAnimationFrame(draw);\n };\n setInterval(function () { return me.state.init(); }, 120 * 1000);\n draw();\n };\n return Substrate;\n}());\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Substrate);\n\n\n//# sourceURL=webpack:///./src/substrate.ts?"); /***/ })