@guinetik/gcanvas 1.0.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CNAME +1 -0
- package/dist/aizawa.html +27 -0
- package/dist/animations.html +31 -0
- package/dist/basic.html +38 -0
- package/dist/baskara.html +31 -0
- package/dist/bezier.html +35 -0
- package/dist/beziersignature.html +29 -0
- package/dist/blackhole.html +28 -0
- package/dist/blob.html +35 -0
- package/dist/clifford.html +25 -0
- package/dist/cmb.html +24 -0
- package/dist/coordinates.html +698 -0
- package/dist/cube3d.html +23 -0
- package/dist/dadras.html +26 -0
- package/dist/dejong.html +25 -0
- package/dist/demos.css +303 -0
- package/dist/dino.html +42 -0
- package/dist/easing.html +28 -0
- package/dist/events.html +195 -0
- package/dist/fluent.html +647 -0
- package/dist/fluid-simple.html +22 -0
- package/dist/fluid.html +37 -0
- package/dist/fractals.html +36 -0
- package/dist/gameobjects.html +626 -0
- package/dist/gcanvas.es.js +14368 -9093
- package/dist/gcanvas.es.min.js +1 -1
- package/dist/gcanvas.umd.js +1 -1
- package/dist/gcanvas.umd.min.js +1 -1
- package/dist/genart.html +26 -0
- package/dist/gendream.html +26 -0
- package/dist/group.html +36 -0
- package/dist/halvorsen.html +27 -0
- package/dist/home.html +587 -0
- package/dist/hyperbolic001.html +23 -0
- package/dist/hyperbolic002.html +23 -0
- package/dist/hyperbolic003.html +23 -0
- package/dist/hyperbolic004.html +23 -0
- package/dist/hyperbolic005.html +22 -0
- package/dist/index.html +446 -0
- package/dist/isometric.html +34 -0
- package/dist/js/aizawa.js +425 -0
- package/dist/js/animations.js +452 -0
- package/dist/js/basic.js +204 -0
- package/dist/js/baskara.js +751 -0
- package/dist/js/bezier.js +692 -0
- package/dist/js/beziersignature.js +241 -0
- package/dist/js/blackhole/accretiondisk.obj.js +379 -0
- package/dist/js/blackhole/blackhole.obj.js +318 -0
- package/dist/js/blackhole/index.js +409 -0
- package/dist/js/blackhole/particle.js +56 -0
- package/dist/js/blackhole/starfield.obj.js +218 -0
- package/dist/js/blob.js +2276 -0
- package/dist/js/clifford.js +236 -0
- package/dist/js/cmb.js +594 -0
- package/dist/js/coordinates.js +840 -0
- package/dist/js/cube3d.js +789 -0
- package/dist/js/dadras.js +405 -0
- package/dist/js/dejong.js +257 -0
- package/dist/js/dino.js +1420 -0
- package/dist/js/easing.js +477 -0
- package/dist/js/fluent.js +183 -0
- package/dist/js/fluid-simple.js +253 -0
- package/dist/js/fluid.js +527 -0
- package/dist/js/fractals.js +932 -0
- package/dist/js/fractalworker.js +93 -0
- package/dist/js/gameobjects.js +176 -0
- package/dist/js/genart.js +268 -0
- package/dist/js/gendream.js +209 -0
- package/dist/js/group.js +140 -0
- package/dist/js/halvorsen.js +405 -0
- package/dist/js/hyperbolic001.js +310 -0
- package/dist/js/hyperbolic002.js +388 -0
- package/dist/js/hyperbolic003.js +319 -0
- package/dist/js/hyperbolic004.js +345 -0
- package/dist/js/hyperbolic005.js +340 -0
- package/dist/js/info-toggle.js +25 -0
- package/dist/js/isometric.js +851 -0
- package/dist/js/kerr.js +1547 -0
- package/dist/js/lavalamp.js +590 -0
- package/dist/js/layout.js +354 -0
- package/dist/js/lorenz.js +425 -0
- package/dist/js/mondrian.js +285 -0
- package/dist/js/opacity.js +275 -0
- package/dist/js/painter.js +484 -0
- package/dist/js/particles-showcase.js +514 -0
- package/dist/js/particles.js +299 -0
- package/dist/js/patterns.js +397 -0
- package/dist/js/penrose/artifact.js +69 -0
- package/dist/js/penrose/blackhole.js +121 -0
- package/dist/js/penrose/constants.js +73 -0
- package/dist/js/penrose/game.js +943 -0
- package/dist/js/penrose/lore.js +278 -0
- package/dist/js/penrose/penrosescene.js +892 -0
- package/dist/js/penrose/ship.js +216 -0
- package/dist/js/penrose/sounds.js +211 -0
- package/dist/js/penrose/voidparticle.js +55 -0
- package/dist/js/penrose/voidscene.js +258 -0
- package/dist/js/penrose/voidship.js +144 -0
- package/dist/js/penrose/wormhole.js +46 -0
- package/dist/js/pipeline.js +555 -0
- package/dist/js/plane3d.js +256 -0
- package/dist/js/platformer.js +1579 -0
- package/dist/js/rossler.js +480 -0
- package/dist/js/scene.js +304 -0
- package/dist/js/scenes.js +320 -0
- package/dist/js/schrodinger.js +706 -0
- package/dist/js/schwarzschild.js +1015 -0
- package/dist/js/shapes.js +628 -0
- package/dist/js/space/alien.js +171 -0
- package/dist/js/space/boom.js +98 -0
- package/dist/js/space/boss.js +353 -0
- package/dist/js/space/buff.js +73 -0
- package/dist/js/space/bullet.js +102 -0
- package/dist/js/space/constants.js +85 -0
- package/dist/js/space/game.js +1884 -0
- package/dist/js/space/hud.js +112 -0
- package/dist/js/space/laserbeam.js +179 -0
- package/dist/js/space/lightning.js +277 -0
- package/dist/js/space/minion.js +192 -0
- package/dist/js/space/missile.js +212 -0
- package/dist/js/space/player.js +430 -0
- package/dist/js/space/powerup.js +90 -0
- package/dist/js/space/starfield.js +58 -0
- package/dist/js/space/starpower.js +90 -0
- package/dist/js/spacetime.js +559 -0
- package/dist/js/sphere3d.js +229 -0
- package/dist/js/sprite.js +473 -0
- package/dist/js/starfaux/config.js +118 -0
- package/dist/js/starfaux/enemy.js +353 -0
- package/dist/js/starfaux/hud.js +78 -0
- package/dist/js/starfaux/index.js +482 -0
- package/dist/js/starfaux/laser.js +182 -0
- package/dist/js/starfaux/player.js +468 -0
- package/dist/js/starfaux/terrain.js +560 -0
- package/dist/js/study001.js +275 -0
- package/dist/js/study002.js +366 -0
- package/dist/js/study003.js +331 -0
- package/dist/js/study004.js +389 -0
- package/dist/js/study005.js +209 -0
- package/dist/js/study006.js +194 -0
- package/dist/js/study007.js +192 -0
- package/dist/js/study008.js +413 -0
- package/dist/js/svgtween.js +204 -0
- package/dist/js/tde/accretiondisk.js +471 -0
- package/dist/js/tde/blackhole.js +219 -0
- package/dist/js/tde/blackholescene.js +209 -0
- package/dist/js/tde/config.js +59 -0
- package/dist/js/tde/index.js +820 -0
- package/dist/js/tde/jets.js +290 -0
- package/dist/js/tde/lensedstarfield.js +154 -0
- package/dist/js/tde/tdestar.js +297 -0
- package/dist/js/tde/tidalstream.js +372 -0
- package/dist/js/tde_old/blackhole.obj.js +354 -0
- package/dist/js/tde_old/debris.obj.js +791 -0
- package/dist/js/tde_old/flare.obj.js +239 -0
- package/dist/js/tde_old/index.js +448 -0
- package/dist/js/tde_old/star.obj.js +812 -0
- package/dist/js/tetris/config.js +157 -0
- package/dist/js/tetris/grid.js +286 -0
- package/dist/js/tetris/index.js +1195 -0
- package/dist/js/tetris/renderer.js +634 -0
- package/dist/js/tetris/tetrominos.js +280 -0
- package/dist/js/thomas.js +394 -0
- package/dist/js/tiles.js +312 -0
- package/dist/js/tweendemo.js +79 -0
- package/dist/js/visibility.js +102 -0
- package/dist/kerr.html +28 -0
- package/dist/lavalamp.html +27 -0
- package/dist/layouts.html +37 -0
- package/dist/logo.svg +4 -0
- package/dist/loop.html +84 -0
- package/dist/lorenz.html +27 -0
- package/dist/mondrian.html +32 -0
- package/dist/og_image.png +0 -0
- package/dist/opacity.html +36 -0
- package/dist/painter.html +39 -0
- package/dist/particles-showcase.html +28 -0
- package/dist/particles.html +24 -0
- package/dist/patterns.html +33 -0
- package/dist/penrose-game.html +31 -0
- package/dist/pipeline.html +737 -0
- package/dist/plane3d.html +24 -0
- package/dist/platformer.html +43 -0
- package/dist/rossler.html +27 -0
- package/dist/scene-interactivity-test.html +220 -0
- package/dist/scene.html +33 -0
- package/dist/scenes.html +96 -0
- package/dist/schrodinger.html +27 -0
- package/dist/schwarzschild.html +27 -0
- package/dist/shapes.html +16 -0
- package/dist/space.html +85 -0
- package/dist/spacetime.html +27 -0
- package/dist/sphere3d.html +24 -0
- package/dist/sprite.html +18 -0
- package/dist/starfaux.html +22 -0
- package/dist/study001.html +23 -0
- package/dist/study002.html +23 -0
- package/dist/study003.html +23 -0
- package/dist/study004.html +23 -0
- package/dist/study005.html +22 -0
- package/dist/study006.html +24 -0
- package/dist/study007.html +24 -0
- package/dist/study008.html +22 -0
- package/dist/svgtween.html +29 -0
- package/dist/tde.html +28 -0
- package/dist/tetris3d.html +25 -0
- package/dist/thomas.html +27 -0
- package/dist/tiles.html +28 -0
- package/dist/transforms.html +400 -0
- package/dist/tween.html +45 -0
- package/dist/visibility.html +33 -0
- package/package.json +1 -1
- package/readme.md +30 -22
- package/src/game/objects/go.js +7 -0
- package/src/game/objects/index.js +2 -0
- package/src/game/objects/isometric-scene.js +53 -3
- package/src/game/objects/layoutscene.js +57 -0
- package/src/game/objects/mask.js +241 -0
- package/src/game/objects/scene.js +19 -0
- package/src/game/objects/wrapper.js +14 -2
- package/src/game/pipeline.js +17 -0
- package/src/game/ui/button.js +101 -16
- package/src/game/ui/theme.js +0 -6
- package/src/game/ui/togglebutton.js +25 -14
- package/src/game/ui/tooltip.js +12 -4
- package/src/index.js +3 -0
- package/src/io/gesture.js +409 -0
- package/src/io/index.js +4 -1
- package/src/io/keys.js +9 -1
- package/src/io/screen.js +476 -0
- package/src/math/attractors.js +664 -0
- package/src/math/heat.js +106 -0
- package/src/math/index.js +1 -0
- package/src/mixins/draggable.js +15 -19
- package/src/painter/painter.shapes.js +11 -5
- package/src/particle/particle-system.js +165 -1
- package/src/physics/index.js +26 -0
- package/src/physics/physics-updaters.js +333 -0
- package/src/physics/physics.js +375 -0
- package/src/shapes/image.js +5 -5
- package/src/shapes/index.js +2 -0
- package/src/shapes/parallelogram.js +147 -0
- package/src/shapes/righttriangle.js +115 -0
- package/src/shapes/svg.js +281 -100
- package/src/shapes/text.js +22 -6
- package/src/shapes/transformable.js +5 -0
- package/src/sound/effects.js +807 -0
- package/src/sound/index.js +13 -0
- package/src/webgl/index.js +7 -0
- package/src/webgl/shaders/clifford-point-shaders.js +131 -0
- package/src/webgl/shaders/dejong-point-shaders.js +131 -0
- package/src/webgl/shaders/point-sprite-shaders.js +152 -0
- package/src/webgl/webgl-clifford-renderer.js +477 -0
- package/src/webgl/webgl-dejong-renderer.js +472 -0
- package/src/webgl/webgl-line-renderer.js +391 -0
- package/src/webgl/webgl-particle-renderer.js +410 -0
- package/types/index.d.ts +30 -2
- package/types/io.d.ts +217 -0
- package/types/physics.d.ts +299 -0
- package/types/shapes.d.ts +8 -0
- package/types/webgl.d.ts +188 -109
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Study 003 - Circuit
|
|
3
|
+
*
|
|
4
|
+
* Generative circuit patterns with traversing dots.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Random maze-like circuit connections
|
|
8
|
+
* - Ring nodes at grid intersections
|
|
9
|
+
* - White dots that traverse the circuit paths
|
|
10
|
+
* - Click to regenerate
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { gcanvas, Easing } from "/gcanvas.es.min.js";
|
|
14
|
+
|
|
15
|
+
// Configuration
|
|
16
|
+
const CONFIG = {
|
|
17
|
+
// Grid settings
|
|
18
|
+
gridSpacing: 40,
|
|
19
|
+
nodeRadius: 8,
|
|
20
|
+
nodeLineWidth: 2.5,
|
|
21
|
+
nodeColor: "rgba(255, 255, 255, 0.5)",
|
|
22
|
+
nodeFill: "#000",
|
|
23
|
+
|
|
24
|
+
// Path settings
|
|
25
|
+
pathWidth: 1.5,
|
|
26
|
+
pathColor: "rgba(255, 255, 255, 0.4)",
|
|
27
|
+
connectionProbability: 0.5,
|
|
28
|
+
|
|
29
|
+
// Traveler settings
|
|
30
|
+
travelerRadius: 4,
|
|
31
|
+
travelerColor: "#fff",
|
|
32
|
+
travelerCount: 0.25, // Percentage of connected nodes with travelers
|
|
33
|
+
travelerSpeedMin: 30, // Pixels per second
|
|
34
|
+
travelerSpeedMax: 120,
|
|
35
|
+
|
|
36
|
+
// Background
|
|
37
|
+
bgColor: "#000",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Traveler - moves along circuit paths
|
|
42
|
+
*/
|
|
43
|
+
class Traveler {
|
|
44
|
+
constructor(startNode, graph, spacing) {
|
|
45
|
+
this.graph = graph;
|
|
46
|
+
this.spacing = spacing;
|
|
47
|
+
this.currentNode = startNode;
|
|
48
|
+
this.targetNode = null;
|
|
49
|
+
this.x = startNode.x;
|
|
50
|
+
this.y = startNode.y;
|
|
51
|
+
this.progress = 0;
|
|
52
|
+
this.isMoving = false;
|
|
53
|
+
this.startX = this.x;
|
|
54
|
+
this.startY = this.y;
|
|
55
|
+
this.endX = this.x;
|
|
56
|
+
this.endY = this.y;
|
|
57
|
+
this.waitTime = Math.random() * 0.5;
|
|
58
|
+
// Random speed for this traveler
|
|
59
|
+
this.speed = CONFIG.travelerSpeedMin +
|
|
60
|
+
Math.random() * (CONFIG.travelerSpeedMax - CONFIG.travelerSpeedMin);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
pickNextTarget() {
|
|
64
|
+
const neighbors = this.graph.getConnections(this.currentNode.col, this.currentNode.row);
|
|
65
|
+
if (neighbors.length === 0) return false;
|
|
66
|
+
|
|
67
|
+
// Pick random connected neighbor
|
|
68
|
+
const next = neighbors[Math.floor(Math.random() * neighbors.length)];
|
|
69
|
+
this.targetNode = this.graph.getNode(next.col, next.row);
|
|
70
|
+
|
|
71
|
+
if (!this.targetNode) return false;
|
|
72
|
+
|
|
73
|
+
this.startX = this.x;
|
|
74
|
+
this.startY = this.y;
|
|
75
|
+
this.endX = this.targetNode.x;
|
|
76
|
+
this.endY = this.targetNode.y;
|
|
77
|
+
this.progress = 0;
|
|
78
|
+
this.isMoving = true;
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
update(dt) {
|
|
83
|
+
if (this.isMoving) {
|
|
84
|
+
const distance = Math.sqrt(
|
|
85
|
+
Math.pow(this.endX - this.startX, 2) + Math.pow(this.endY - this.startY, 2)
|
|
86
|
+
);
|
|
87
|
+
const duration = distance / this.speed;
|
|
88
|
+
this.progress += dt / duration;
|
|
89
|
+
|
|
90
|
+
if (this.progress >= 1) {
|
|
91
|
+
this.progress = 1;
|
|
92
|
+
this.isMoving = false;
|
|
93
|
+
this.x = this.endX;
|
|
94
|
+
this.y = this.endY;
|
|
95
|
+
this.currentNode = this.targetNode;
|
|
96
|
+
this.waitTime = 0.1 + Math.random() * 0.3;
|
|
97
|
+
} else {
|
|
98
|
+
const t = Easing.easeInOutQuad(this.progress);
|
|
99
|
+
this.x = this.startX + (this.endX - this.startX) * t;
|
|
100
|
+
this.y = this.startY + (this.endY - this.startY) * t;
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
this.waitTime -= dt;
|
|
104
|
+
if (this.waitTime <= 0) {
|
|
105
|
+
this.pickNextTarget();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Circuit Graph - manages nodes and connections
|
|
113
|
+
*/
|
|
114
|
+
class CircuitGraph {
|
|
115
|
+
constructor() {
|
|
116
|
+
this.nodes = new Map();
|
|
117
|
+
this.connections = new Map();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
clear() {
|
|
121
|
+
this.nodes.clear();
|
|
122
|
+
this.connections.clear();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
addNode(col, row, x, y) {
|
|
126
|
+
const key = `${col},${row}`;
|
|
127
|
+
this.nodes.set(key, { col, row, x, y });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
getNode(col, row) {
|
|
131
|
+
return this.nodes.get(`${col},${row}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
addConnection(col1, row1, col2, row2) {
|
|
135
|
+
const key1 = `${col1},${row1}`;
|
|
136
|
+
const key2 = `${col2},${row2}`;
|
|
137
|
+
|
|
138
|
+
if (!this.connections.has(key1)) {
|
|
139
|
+
this.connections.set(key1, []);
|
|
140
|
+
}
|
|
141
|
+
if (!this.connections.has(key2)) {
|
|
142
|
+
this.connections.set(key2, []);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Add bidirectional connection
|
|
146
|
+
this.connections.get(key1).push({ col: col2, row: row2 });
|
|
147
|
+
this.connections.get(key2).push({ col: col1, row: row1 });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
getConnections(col, row) {
|
|
151
|
+
return this.connections.get(`${col},${row}`) || [];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
hasConnection(col1, row1, col2, row2) {
|
|
155
|
+
const conns = this.getConnections(col1, row1);
|
|
156
|
+
return conns.some(c => c.col === col2 && c.row === row2);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Initialize
|
|
161
|
+
window.addEventListener("load", () => {
|
|
162
|
+
const canvas = document.getElementById("game");
|
|
163
|
+
if (!canvas) return;
|
|
164
|
+
|
|
165
|
+
const game = gcanvas({ canvas, bg: CONFIG.bgColor, fluid: true });
|
|
166
|
+
const scene = game.scene("main");
|
|
167
|
+
const gameInstance = game.game;
|
|
168
|
+
|
|
169
|
+
// State
|
|
170
|
+
let gridCols = 0;
|
|
171
|
+
let gridRows = 0;
|
|
172
|
+
let offsetX = 0;
|
|
173
|
+
let offsetY = 0;
|
|
174
|
+
let graph = new CircuitGraph();
|
|
175
|
+
let travelers = [];
|
|
176
|
+
|
|
177
|
+
// Debounced resize
|
|
178
|
+
let resizeTimeout = null;
|
|
179
|
+
let needsRebuild = false;
|
|
180
|
+
|
|
181
|
+
const handleResize = () => {
|
|
182
|
+
clearTimeout(resizeTimeout);
|
|
183
|
+
resizeTimeout = setTimeout(() => {
|
|
184
|
+
gameInstance.ctx.fillStyle = CONFIG.bgColor;
|
|
185
|
+
gameInstance.ctx.fillRect(0, 0, gameInstance.width, gameInstance.height);
|
|
186
|
+
needsRebuild = true;
|
|
187
|
+
}, 100);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
window.addEventListener("resize", handleResize);
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Generate random circuit
|
|
194
|
+
*/
|
|
195
|
+
function generateCircuit() {
|
|
196
|
+
const spacing = CONFIG.gridSpacing;
|
|
197
|
+
const padding = spacing * 1.5;
|
|
198
|
+
const w = gameInstance.width;
|
|
199
|
+
const h = gameInstance.height;
|
|
200
|
+
|
|
201
|
+
gridCols = Math.floor((w - padding * 2) / spacing);
|
|
202
|
+
gridRows = Math.floor((h - padding * 2) / spacing);
|
|
203
|
+
offsetX = (w - gridCols * spacing) / 2;
|
|
204
|
+
offsetY = (h - gridRows * spacing) / 2;
|
|
205
|
+
|
|
206
|
+
// Clear previous
|
|
207
|
+
graph.clear();
|
|
208
|
+
travelers = [];
|
|
209
|
+
|
|
210
|
+
// Create nodes
|
|
211
|
+
for (let row = 0; row <= gridRows; row++) {
|
|
212
|
+
for (let col = 0; col <= gridCols; col++) {
|
|
213
|
+
const x = col * spacing + offsetX;
|
|
214
|
+
const y = row * spacing + offsetY;
|
|
215
|
+
graph.addNode(col, row, x, y);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Create random connections (only right and down to avoid duplicates)
|
|
220
|
+
for (let row = 0; row <= gridRows; row++) {
|
|
221
|
+
for (let col = 0; col <= gridCols; col++) {
|
|
222
|
+
// Connect right
|
|
223
|
+
if (col < gridCols && Math.random() < CONFIG.connectionProbability) {
|
|
224
|
+
graph.addConnection(col, row, col + 1, row);
|
|
225
|
+
}
|
|
226
|
+
// Connect down
|
|
227
|
+
if (row < gridRows && Math.random() < CONFIG.connectionProbability) {
|
|
228
|
+
graph.addConnection(col, row, col, row + 1);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Create travelers on random connected nodes
|
|
234
|
+
const nodeList = Array.from(graph.nodes.values());
|
|
235
|
+
const connectedNodes = nodeList.filter(n => graph.getConnections(n.col, n.row).length > 0);
|
|
236
|
+
const numTravelers = Math.floor(connectedNodes.length * CONFIG.travelerCount);
|
|
237
|
+
|
|
238
|
+
for (let i = 0; i < numTravelers && connectedNodes.length > 0; i++) {
|
|
239
|
+
const idx = Math.floor(Math.random() * connectedNodes.length);
|
|
240
|
+
const node = connectedNodes.splice(idx, 1)[0];
|
|
241
|
+
travelers.push(new Traveler(node, graph, spacing));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Draw the circuit (nodes and paths)
|
|
247
|
+
*/
|
|
248
|
+
function drawCircuit(ctx) {
|
|
249
|
+
const spacing = CONFIG.gridSpacing;
|
|
250
|
+
|
|
251
|
+
// Draw connections first (under nodes)
|
|
252
|
+
ctx.strokeStyle = CONFIG.pathColor;
|
|
253
|
+
ctx.lineWidth = CONFIG.pathWidth;
|
|
254
|
+
ctx.lineCap = "round";
|
|
255
|
+
|
|
256
|
+
const drawnConnections = new Set();
|
|
257
|
+
|
|
258
|
+
for (const [key, node] of graph.nodes) {
|
|
259
|
+
const connections = graph.getConnections(node.col, node.row);
|
|
260
|
+
|
|
261
|
+
for (const conn of connections) {
|
|
262
|
+
// Avoid drawing same connection twice
|
|
263
|
+
const connKey = [key, `${conn.col},${conn.row}`].sort().join("-");
|
|
264
|
+
if (drawnConnections.has(connKey)) continue;
|
|
265
|
+
drawnConnections.add(connKey);
|
|
266
|
+
|
|
267
|
+
const targetNode = graph.getNode(conn.col, conn.row);
|
|
268
|
+
if (!targetNode) continue;
|
|
269
|
+
|
|
270
|
+
ctx.beginPath();
|
|
271
|
+
ctx.moveTo(node.x, node.y);
|
|
272
|
+
ctx.lineTo(targetNode.x, targetNode.y);
|
|
273
|
+
ctx.stroke();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Draw nodes (only where connections exist)
|
|
278
|
+
ctx.fillStyle = CONFIG.nodeFill;
|
|
279
|
+
ctx.strokeStyle = CONFIG.nodeColor;
|
|
280
|
+
ctx.lineWidth = CONFIG.nodeLineWidth;
|
|
281
|
+
|
|
282
|
+
for (const [key, node] of graph.nodes) {
|
|
283
|
+
// Only draw nodes that have at least one connection
|
|
284
|
+
const connections = graph.getConnections(node.col, node.row);
|
|
285
|
+
if (connections.length === 0) continue;
|
|
286
|
+
|
|
287
|
+
ctx.beginPath();
|
|
288
|
+
ctx.arc(node.x, node.y, CONFIG.nodeRadius, 0, Math.PI * 2);
|
|
289
|
+
ctx.fill();
|
|
290
|
+
ctx.stroke();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Draw travelers (on top of everything)
|
|
294
|
+
ctx.fillStyle = CONFIG.travelerColor;
|
|
295
|
+
for (const traveler of travelers) {
|
|
296
|
+
ctx.beginPath();
|
|
297
|
+
ctx.arc(traveler.x, traveler.y, CONFIG.travelerRadius, 0, Math.PI * 2);
|
|
298
|
+
ctx.fill();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Generate initial circuit
|
|
303
|
+
generateCircuit();
|
|
304
|
+
|
|
305
|
+
// Custom render - no trail effect, just redraw
|
|
306
|
+
gameInstance.clear = function () {
|
|
307
|
+
this.ctx.fillStyle = CONFIG.bgColor;
|
|
308
|
+
this.ctx.fillRect(0, 0, this.width, this.height);
|
|
309
|
+
drawCircuit(this.ctx);
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// Update loop
|
|
313
|
+
game.on("update", (dt, ctx) => {
|
|
314
|
+
if (needsRebuild) {
|
|
315
|
+
needsRebuild = false;
|
|
316
|
+
generateCircuit();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Update travelers
|
|
320
|
+
for (const traveler of travelers) {
|
|
321
|
+
traveler.update(dt);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Click to regenerate
|
|
326
|
+
game.on("click", () => {
|
|
327
|
+
generateCircuit();
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
game.start();
|
|
331
|
+
});
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Study 004 - Hex Bloom
|
|
3
|
+
*
|
|
4
|
+
* Radial hexagon explosion with Tweenetik animations.
|
|
5
|
+
* Inspired by @okazz_
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Hexagons bloom radially from center
|
|
9
|
+
* - Color transition: black → colorful → white
|
|
10
|
+
* - Tweenetik-powered staggered animations
|
|
11
|
+
* - Click to trigger new bloom
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { gcanvas, Tweenetik, Easing } from "/gcanvas.es.min.js";
|
|
15
|
+
|
|
16
|
+
// Configuration
|
|
17
|
+
const CONFIG = {
|
|
18
|
+
// Grid settings
|
|
19
|
+
hexRadius: 25,
|
|
20
|
+
hexSpacing: 2,
|
|
21
|
+
|
|
22
|
+
// Animation settings
|
|
23
|
+
bloomDuration: 0.8,
|
|
24
|
+
staggerDelay: 0.03, // Delay per ring
|
|
25
|
+
scaleFrom: 0,
|
|
26
|
+
scaleTo: 1,
|
|
27
|
+
|
|
28
|
+
// Colors
|
|
29
|
+
colors: [
|
|
30
|
+
"#FF3B30", "#FF9500", "#FFCC00", "#34C759", "#00C7BE",
|
|
31
|
+
"#007AFF", "#5856D6", "#AF52DE", "#FF2D55", "#FF6B6B",
|
|
32
|
+
"#4ECDC4", "#45B7D1", "#96CEB4", "#FFEAA7", "#DDA0DD",
|
|
33
|
+
],
|
|
34
|
+
|
|
35
|
+
// Background
|
|
36
|
+
bgColor: "#000",
|
|
37
|
+
bloomColor: "#fff",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Hexagon math
|
|
41
|
+
const HEX_ANGLE = Math.PI / 3;
|
|
42
|
+
const HEX_HEIGHT_RATIO = Math.sqrt(3);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Calculate hexagon grid position (axial coordinates to pixel)
|
|
46
|
+
*/
|
|
47
|
+
function hexToPixel(q, r, size, centerX, centerY) {
|
|
48
|
+
const x = size * (3 / 2 * q);
|
|
49
|
+
const y = size * (HEX_HEIGHT_RATIO / 2 * q + HEX_HEIGHT_RATIO * r);
|
|
50
|
+
return { x: centerX + x, y: centerY + y };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Draw a hexagon
|
|
55
|
+
*/
|
|
56
|
+
function drawHexagon(ctx, x, y, radius, fillColor, strokeColor, lineWidth) {
|
|
57
|
+
ctx.beginPath();
|
|
58
|
+
for (let i = 0; i < 6; i++) {
|
|
59
|
+
const angle = HEX_ANGLE * i - Math.PI / 6;
|
|
60
|
+
const hx = x + radius * Math.cos(angle);
|
|
61
|
+
const hy = y + radius * Math.sin(angle);
|
|
62
|
+
if (i === 0) {
|
|
63
|
+
ctx.moveTo(hx, hy);
|
|
64
|
+
} else {
|
|
65
|
+
ctx.lineTo(hx, hy);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
ctx.closePath();
|
|
69
|
+
|
|
70
|
+
if (fillColor) {
|
|
71
|
+
ctx.fillStyle = fillColor;
|
|
72
|
+
ctx.fill();
|
|
73
|
+
}
|
|
74
|
+
if (strokeColor && lineWidth > 0) {
|
|
75
|
+
ctx.strokeStyle = strokeColor;
|
|
76
|
+
ctx.lineWidth = lineWidth;
|
|
77
|
+
ctx.stroke();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Draw a star/asterisk shape
|
|
83
|
+
*/
|
|
84
|
+
function drawStar(ctx, x, y, radius, points, color, lineWidth) {
|
|
85
|
+
ctx.strokeStyle = color;
|
|
86
|
+
ctx.lineWidth = lineWidth;
|
|
87
|
+
ctx.lineCap = "round";
|
|
88
|
+
|
|
89
|
+
for (let i = 0; i < points; i++) {
|
|
90
|
+
const angle = (Math.PI * 2 / points) * i - Math.PI / 2;
|
|
91
|
+
ctx.beginPath();
|
|
92
|
+
ctx.moveTo(x, y);
|
|
93
|
+
ctx.lineTo(x + Math.cos(angle) * radius, y + Math.sin(angle) * radius);
|
|
94
|
+
ctx.stroke();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Hex cell state for animation
|
|
100
|
+
*/
|
|
101
|
+
class HexCell {
|
|
102
|
+
constructor(q, r, x, y, distFromCenter) {
|
|
103
|
+
this.q = q;
|
|
104
|
+
this.r = r;
|
|
105
|
+
this.x = x;
|
|
106
|
+
this.y = y;
|
|
107
|
+
this.distFromCenter = distFromCenter;
|
|
108
|
+
|
|
109
|
+
// Animation state
|
|
110
|
+
this.scale = 0;
|
|
111
|
+
this.opacity = 0;
|
|
112
|
+
this.colorPhase = 0; // 0 = colored, 1 = white
|
|
113
|
+
this.rotation = 0;
|
|
114
|
+
|
|
115
|
+
// Visual properties
|
|
116
|
+
this.color = CONFIG.colors[Math.floor(Math.random() * CONFIG.colors.length)];
|
|
117
|
+
this.isStar = Math.random() < 0.15; // 15% chance to be a star
|
|
118
|
+
this.starPoints = 4 + Math.floor(Math.random() * 4); // 4-7 points
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
draw(ctx, baseRadius) {
|
|
122
|
+
if (this.opacity <= 0 || this.scale <= 0) return;
|
|
123
|
+
|
|
124
|
+
const radius = baseRadius * this.scale;
|
|
125
|
+
|
|
126
|
+
ctx.save();
|
|
127
|
+
ctx.globalAlpha = this.opacity;
|
|
128
|
+
ctx.translate(this.x, this.y);
|
|
129
|
+
ctx.rotate(this.rotation);
|
|
130
|
+
|
|
131
|
+
// Interpolate color from colorful to white based on colorPhase
|
|
132
|
+
const r = Math.floor(this.hexToRgb(this.color).r + (255 - this.hexToRgb(this.color).r) * this.colorPhase);
|
|
133
|
+
const g = Math.floor(this.hexToRgb(this.color).g + (255 - this.hexToRgb(this.color).g) * this.colorPhase);
|
|
134
|
+
const b = Math.floor(this.hexToRgb(this.color).b + (255 - this.hexToRgb(this.color).b) * this.colorPhase);
|
|
135
|
+
const currentColor = `rgb(${r},${g},${b})`;
|
|
136
|
+
|
|
137
|
+
if (this.isStar) {
|
|
138
|
+
drawStar(ctx, 0, 0, radius, this.starPoints, currentColor, 2);
|
|
139
|
+
} else {
|
|
140
|
+
// Draw hexagon with stroke and optional fill
|
|
141
|
+
const fillAlpha = this.colorPhase;
|
|
142
|
+
const fillColor = fillAlpha > 0.1 ? `rgba(${r},${g},${b},${fillAlpha})` : null;
|
|
143
|
+
const strokeColor = currentColor;
|
|
144
|
+
drawHexagon(ctx, 0, 0, radius, fillColor, strokeColor, 2);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
ctx.restore();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
hexToRgb(hex) {
|
|
151
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
152
|
+
return result ? {
|
|
153
|
+
r: parseInt(result[1], 16),
|
|
154
|
+
g: parseInt(result[2], 16),
|
|
155
|
+
b: parseInt(result[3], 16)
|
|
156
|
+
} : { r: 255, g: 255, b: 255 };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Initialize
|
|
161
|
+
window.addEventListener("load", () => {
|
|
162
|
+
const canvas = document.getElementById("game");
|
|
163
|
+
if (!canvas) return;
|
|
164
|
+
|
|
165
|
+
const game = gcanvas({ canvas, bg: CONFIG.bgColor, fluid: true });
|
|
166
|
+
const scene = game.scene("main");
|
|
167
|
+
const gameInstance = game.game;
|
|
168
|
+
|
|
169
|
+
// State
|
|
170
|
+
let hexCells = [];
|
|
171
|
+
let bgWhiteness = 0; // 0 = black, 1 = white
|
|
172
|
+
let isAnimating = false;
|
|
173
|
+
let phase = "bloom"; // "bloom" or "collapse"
|
|
174
|
+
|
|
175
|
+
// Debounced resize
|
|
176
|
+
let resizeTimeout = null;
|
|
177
|
+
let needsRebuild = false;
|
|
178
|
+
|
|
179
|
+
const handleResize = () => {
|
|
180
|
+
clearTimeout(resizeTimeout);
|
|
181
|
+
resizeTimeout = setTimeout(() => {
|
|
182
|
+
needsRebuild = true;
|
|
183
|
+
}, 100);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
window.addEventListener("resize", handleResize);
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Generate hexagon grid
|
|
190
|
+
*/
|
|
191
|
+
function generateGrid() {
|
|
192
|
+
const w = gameInstance.width;
|
|
193
|
+
const h = gameInstance.height;
|
|
194
|
+
const centerX = w / 2;
|
|
195
|
+
const centerY = h / 2;
|
|
196
|
+
const size = CONFIG.hexRadius + CONFIG.hexSpacing;
|
|
197
|
+
|
|
198
|
+
hexCells = [];
|
|
199
|
+
|
|
200
|
+
// Calculate how many rings we need to cover the screen
|
|
201
|
+
const maxDist = Math.sqrt(w * w + h * h) / 2;
|
|
202
|
+
const rings = Math.ceil(maxDist / (size * HEX_HEIGHT_RATIO)) + 2;
|
|
203
|
+
|
|
204
|
+
// Generate hexagonal grid using axial coordinates
|
|
205
|
+
for (let q = -rings; q <= rings; q++) {
|
|
206
|
+
for (let r = -rings; r <= rings; r++) {
|
|
207
|
+
const pos = hexToPixel(q, r, size, centerX, centerY);
|
|
208
|
+
|
|
209
|
+
// Skip if too far from visible area
|
|
210
|
+
if (pos.x < -size * 2 || pos.x > w + size * 2 ||
|
|
211
|
+
pos.y < -size * 2 || pos.y > h + size * 2) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const distFromCenter = Math.sqrt(
|
|
216
|
+
Math.pow(pos.x - centerX, 2) + Math.pow(pos.y - centerY, 2)
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
hexCells.push(new HexCell(q, r, pos.x, pos.y, distFromCenter));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Sort by distance from center for staggered animation
|
|
224
|
+
hexCells.sort((a, b) => a.distFromCenter - b.distFromCenter);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Trigger bloom animation (center → edges, black → white)
|
|
229
|
+
*/
|
|
230
|
+
function triggerBloom() {
|
|
231
|
+
if (isAnimating) return;
|
|
232
|
+
isAnimating = true;
|
|
233
|
+
phase = "bloom";
|
|
234
|
+
|
|
235
|
+
// Reset all cells for bloom
|
|
236
|
+
for (const cell of hexCells) {
|
|
237
|
+
cell.scale = 0;
|
|
238
|
+
cell.opacity = 0;
|
|
239
|
+
cell.colorPhase = 0;
|
|
240
|
+
cell.rotation = (Math.random() - 0.5) * 0.5;
|
|
241
|
+
cell.color = CONFIG.colors[Math.floor(Math.random() * CONFIG.colors.length)];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
bgWhiteness = 0;
|
|
245
|
+
|
|
246
|
+
// Find max distance for normalization
|
|
247
|
+
const maxDist = hexCells.length > 0 ?
|
|
248
|
+
hexCells[hexCells.length - 1].distFromCenter : 1;
|
|
249
|
+
|
|
250
|
+
// Animate each cell with Tweenetik (inner to outer)
|
|
251
|
+
let lastDelay = 0;
|
|
252
|
+
for (const cell of hexCells) {
|
|
253
|
+
const normalizedDist = cell.distFromCenter / maxDist;
|
|
254
|
+
const delay = normalizedDist * CONFIG.staggerDelay * hexCells.length * 0.3;
|
|
255
|
+
lastDelay = Math.max(lastDelay, delay);
|
|
256
|
+
|
|
257
|
+
// Scale up
|
|
258
|
+
Tweenetik.to(cell, { scale: 1, opacity: 1 }, CONFIG.bloomDuration * 0.6, Easing.easeOutBack, {
|
|
259
|
+
delay: delay,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Color transition (colorful → white)
|
|
263
|
+
Tweenetik.to(cell, { colorPhase: 1 }, CONFIG.bloomDuration * 0.8, Easing.easeInOutQuad, {
|
|
264
|
+
delay: delay + CONFIG.bloomDuration * 0.3,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Slight rotation
|
|
268
|
+
Tweenetik.to(cell, { rotation: 0 }, CONFIG.bloomDuration, Easing.easeOutQuad, {
|
|
269
|
+
delay: delay,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Wait for all hexagons to finish, then trigger collapse
|
|
274
|
+
const totalBloomTime = lastDelay + CONFIG.bloomDuration * 1.1;
|
|
275
|
+
const randomPause = 100 + Math.random() * 100;
|
|
276
|
+
setTimeout(() => {
|
|
277
|
+
isAnimating = false;
|
|
278
|
+
setTimeout(() => triggerCollapse(), randomPause);
|
|
279
|
+
}, totalBloomTime * 1000);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Trigger collapse animation (edges → center, white → black)
|
|
284
|
+
*/
|
|
285
|
+
function triggerCollapse() {
|
|
286
|
+
if (isAnimating) return;
|
|
287
|
+
isAnimating = true;
|
|
288
|
+
phase = "collapse";
|
|
289
|
+
|
|
290
|
+
// Find max distance for normalization
|
|
291
|
+
const maxDist = hexCells.length > 0 ?
|
|
292
|
+
hexCells[hexCells.length - 1].distFromCenter : 1;
|
|
293
|
+
|
|
294
|
+
// Animate each cell (outer to inner - reverse order)
|
|
295
|
+
let lastDelay = 0;
|
|
296
|
+
for (const cell of hexCells) {
|
|
297
|
+
const normalizedDist = cell.distFromCenter / maxDist;
|
|
298
|
+
// Reverse: outer cells animate first
|
|
299
|
+
const delay = (1 - normalizedDist) * CONFIG.staggerDelay * hexCells.length * 0.3;
|
|
300
|
+
lastDelay = Math.max(lastDelay, delay);
|
|
301
|
+
|
|
302
|
+
// Color transition back (white → colorful)
|
|
303
|
+
Tweenetik.to(cell, { colorPhase: 0 }, CONFIG.bloomDuration * 0.5, Easing.easeOutQuad, {
|
|
304
|
+
delay: delay,
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Scale down and fade out
|
|
308
|
+
Tweenetik.to(cell, { scale: 0, opacity: 0 }, CONFIG.bloomDuration * 0.6, Easing.easeInBack, {
|
|
309
|
+
delay: delay + CONFIG.bloomDuration * 0.3,
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// Rotation
|
|
313
|
+
Tweenetik.to(cell, { rotation: (Math.random() - 0.5) * 0.5 }, CONFIG.bloomDuration, Easing.easeInQuad, {
|
|
314
|
+
delay: delay,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Wait for all hexagons to finish collapsing, then trigger bloom
|
|
319
|
+
const totalCollapseTime = lastDelay + CONFIG.bloomDuration * 1.1;
|
|
320
|
+
const randomPause = 100 + Math.random() * 400;
|
|
321
|
+
setTimeout(() => {
|
|
322
|
+
isAnimating = false;
|
|
323
|
+
setTimeout(() => triggerBloom(), randomPause);
|
|
324
|
+
}, totalCollapseTime * 1000);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Reset to black (for resize)
|
|
329
|
+
*/
|
|
330
|
+
function resetToBlack() {
|
|
331
|
+
Tweenetik.killAll();
|
|
332
|
+
|
|
333
|
+
for (const cell of hexCells) {
|
|
334
|
+
cell.scale = 0;
|
|
335
|
+
cell.opacity = 0;
|
|
336
|
+
cell.colorPhase = 0;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
bgWhiteness = 0;
|
|
340
|
+
isAnimating = false;
|
|
341
|
+
phase = "bloom";
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Generate initial grid
|
|
345
|
+
generateGrid();
|
|
346
|
+
|
|
347
|
+
// Custom render
|
|
348
|
+
gameInstance.clear = function () {
|
|
349
|
+
// Background color interpolated between black and white
|
|
350
|
+
const bg = Math.floor(bgWhiteness * 255);
|
|
351
|
+
this.ctx.fillStyle = `rgb(${bg},${bg},${bg})`;
|
|
352
|
+
this.ctx.fillRect(0, 0, this.width, this.height);
|
|
353
|
+
|
|
354
|
+
// Draw all hex cells
|
|
355
|
+
for (const cell of hexCells) {
|
|
356
|
+
cell.draw(this.ctx, CONFIG.hexRadius);
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
// Update loop
|
|
361
|
+
game.on("update", (dt) => {
|
|
362
|
+
if (needsRebuild) {
|
|
363
|
+
needsRebuild = false;
|
|
364
|
+
resetToBlack();
|
|
365
|
+
generateGrid();
|
|
366
|
+
// Auto-trigger bloom after rebuild
|
|
367
|
+
setTimeout(() => triggerBloom(), 100);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Update Tweenetik
|
|
371
|
+
Tweenetik.updateAll(dt);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// Click to bloom
|
|
375
|
+
game.on("click", () => {
|
|
376
|
+
if (bgWhiteness > 0.9) {
|
|
377
|
+
// If already white, reset and bloom again
|
|
378
|
+
resetToBlack();
|
|
379
|
+
setTimeout(() => triggerBloom(), 50);
|
|
380
|
+
} else {
|
|
381
|
+
triggerBloom();
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// Auto-start bloom
|
|
386
|
+
setTimeout(() => triggerBloom(), 500);
|
|
387
|
+
|
|
388
|
+
game.start();
|
|
389
|
+
});
|