@guinetik/gcanvas 1.0.4 → 1.0.5
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/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/coordinates.html +698 -0
- package/dist/cube3d.html +23 -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 +517 -0
- 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/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 +398 -0
- package/dist/isometric.html +34 -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/coordinates.js +840 -0
- package/dist/js/cube3d.js +789 -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/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 +863 -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/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/scene.js +304 -0
- package/dist/js/scenes.js +320 -0
- package/dist/js/schrodinger.js +410 -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/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/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/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/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
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Study 005 - Hex Flow
|
|
3
|
+
*
|
|
4
|
+
* A hexagonal grid that breathes, warps, and flows with
|
|
5
|
+
* wave interference patterns. Hypnotic honeycomb energy.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Hexagonal tiling that fills the screen
|
|
9
|
+
* - Multi-wave interference displacement
|
|
10
|
+
* - Color cycling based on position + time
|
|
11
|
+
* - Rotation and scale pulsing per cell
|
|
12
|
+
* - Click to shift color palette
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { gcanvas } from "/gcanvas.es.min.js";
|
|
16
|
+
|
|
17
|
+
const CONFIG = {
|
|
18
|
+
// Hex grid
|
|
19
|
+
hexSize: 35,
|
|
20
|
+
|
|
21
|
+
// Wave settings
|
|
22
|
+
waveSpeed: 2.0,
|
|
23
|
+
waveFreq: 0.015,
|
|
24
|
+
waveAmp: 12,
|
|
25
|
+
|
|
26
|
+
// Rotation
|
|
27
|
+
rotationSpeed: 0.8,
|
|
28
|
+
rotationAmp: 0.4,
|
|
29
|
+
|
|
30
|
+
// Scale pulsing
|
|
31
|
+
scaleAmp: 0.3,
|
|
32
|
+
|
|
33
|
+
// Color
|
|
34
|
+
hueSpeed: 30,
|
|
35
|
+
hueSpread: 120,
|
|
36
|
+
saturation: 85,
|
|
37
|
+
lightness: 55,
|
|
38
|
+
|
|
39
|
+
// Style
|
|
40
|
+
strokeWidth: 2,
|
|
41
|
+
fillAlpha: 0.7,
|
|
42
|
+
|
|
43
|
+
bgColor: "#0a0a12",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const HEX_ANGLE = Math.PI / 3;
|
|
47
|
+
const SQRT3 = Math.sqrt(3);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Draw a single hexagon with rotation
|
|
51
|
+
*/
|
|
52
|
+
function drawHex(ctx, x, y, size, rotation, fillColor, strokeColor) {
|
|
53
|
+
if (size < 2) return;
|
|
54
|
+
|
|
55
|
+
ctx.save();
|
|
56
|
+
ctx.translate(x, y);
|
|
57
|
+
ctx.rotate(rotation);
|
|
58
|
+
|
|
59
|
+
ctx.beginPath();
|
|
60
|
+
for (let i = 0; i < 6; i++) {
|
|
61
|
+
const angle = HEX_ANGLE * i - Math.PI / 6;
|
|
62
|
+
const hx = size * Math.cos(angle);
|
|
63
|
+
const hy = size * Math.sin(angle);
|
|
64
|
+
if (i === 0) {
|
|
65
|
+
ctx.moveTo(hx, hy);
|
|
66
|
+
} else {
|
|
67
|
+
ctx.lineTo(hx, hy);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
ctx.closePath();
|
|
71
|
+
|
|
72
|
+
ctx.fillStyle = fillColor;
|
|
73
|
+
ctx.fill();
|
|
74
|
+
|
|
75
|
+
ctx.strokeStyle = strokeColor;
|
|
76
|
+
ctx.lineWidth = CONFIG.strokeWidth;
|
|
77
|
+
ctx.stroke();
|
|
78
|
+
|
|
79
|
+
ctx.restore();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Convert axial hex coords to pixel
|
|
84
|
+
*/
|
|
85
|
+
function hexToPixel(q, r, size) {
|
|
86
|
+
const x = size * (3/2 * q);
|
|
87
|
+
const y = size * (SQRT3/2 * q + SQRT3 * r);
|
|
88
|
+
return { x, y };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Initialize
|
|
92
|
+
window.addEventListener("load", () => {
|
|
93
|
+
const canvas = document.getElementById("game");
|
|
94
|
+
if (!canvas) return;
|
|
95
|
+
|
|
96
|
+
const game = gcanvas({ canvas, bg: CONFIG.bgColor, fluid: true });
|
|
97
|
+
const gameInstance = game.game;
|
|
98
|
+
|
|
99
|
+
let hexGrid = [];
|
|
100
|
+
let time = Math.random() * 100;
|
|
101
|
+
let hueOffset = 0;
|
|
102
|
+
|
|
103
|
+
function buildGrid(w, h) {
|
|
104
|
+
hexGrid = [];
|
|
105
|
+
const size = CONFIG.hexSize;
|
|
106
|
+
|
|
107
|
+
// Use diagonal to ensure full coverage during any transform
|
|
108
|
+
const diagonal = Math.sqrt(w * w + h * h);
|
|
109
|
+
const cols = Math.ceil(diagonal / (size * 1.5)) + 4;
|
|
110
|
+
const rows = Math.ceil(diagonal / (size * SQRT3)) + 4;
|
|
111
|
+
|
|
112
|
+
const startQ = -Math.floor(cols / 2);
|
|
113
|
+
const startR = -Math.floor(rows / 2);
|
|
114
|
+
|
|
115
|
+
for (let q = startQ; q < startQ + cols; q++) {
|
|
116
|
+
for (let r = startR; r < startR + rows; r++) {
|
|
117
|
+
const pos = hexToPixel(q, r, size);
|
|
118
|
+
hexGrid.push({
|
|
119
|
+
q, r,
|
|
120
|
+
baseX: pos.x,
|
|
121
|
+
baseY: pos.y,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
buildGrid(gameInstance.width, gameInstance.height);
|
|
128
|
+
|
|
129
|
+
window.addEventListener("resize", () => {
|
|
130
|
+
buildGrid(gameInstance.width, gameInstance.height);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
gameInstance.clear = function () {
|
|
134
|
+
const ctx = this.ctx;
|
|
135
|
+
const w = this.width;
|
|
136
|
+
const h = this.height;
|
|
137
|
+
const cx = w / 2;
|
|
138
|
+
const cy = h / 2;
|
|
139
|
+
|
|
140
|
+
// Clear
|
|
141
|
+
ctx.fillStyle = CONFIG.bgColor;
|
|
142
|
+
ctx.fillRect(0, 0, w, h);
|
|
143
|
+
|
|
144
|
+
const t = time;
|
|
145
|
+
|
|
146
|
+
for (const hex of hexGrid) {
|
|
147
|
+
// World position
|
|
148
|
+
const wx = hex.baseX + cx;
|
|
149
|
+
const wy = hex.baseY + cy;
|
|
150
|
+
|
|
151
|
+
// Distance from center for radial effects
|
|
152
|
+
const dx = hex.baseX;
|
|
153
|
+
const dy = hex.baseY;
|
|
154
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
155
|
+
const angle = Math.atan2(dy, dx);
|
|
156
|
+
|
|
157
|
+
// Multi-wave displacement
|
|
158
|
+
const wave1 = Math.sin(dist * CONFIG.waveFreq - t * CONFIG.waveSpeed);
|
|
159
|
+
const wave2 = Math.sin(dist * CONFIG.waveFreq * 0.7 + t * CONFIG.waveSpeed * 0.5 + angle * 3);
|
|
160
|
+
const wave3 = Math.cos(angle * 6 - t * 1.5) * Math.sin(dist * 0.02);
|
|
161
|
+
|
|
162
|
+
const combinedWave = (wave1 + wave2 * 0.6 + wave3 * 0.4) / 2;
|
|
163
|
+
|
|
164
|
+
// Displacement
|
|
165
|
+
const displaceX = Math.cos(angle) * combinedWave * CONFIG.waveAmp;
|
|
166
|
+
const displaceY = Math.sin(angle) * combinedWave * CONFIG.waveAmp;
|
|
167
|
+
|
|
168
|
+
const finalX = wx + displaceX;
|
|
169
|
+
const finalY = wy + displaceY;
|
|
170
|
+
|
|
171
|
+
// Scale pulsing
|
|
172
|
+
const scalePulse = 1 + combinedWave * CONFIG.scaleAmp;
|
|
173
|
+
const size = CONFIG.hexSize * scalePulse;
|
|
174
|
+
|
|
175
|
+
// Rotation
|
|
176
|
+
const rotation = combinedWave * CONFIG.rotationAmp + t * 0.1;
|
|
177
|
+
|
|
178
|
+
// Color based on position + time
|
|
179
|
+
const hue = (hueOffset + dist * 0.3 + angle * 20 + t * CONFIG.hueSpeed) % 360;
|
|
180
|
+
const sat = CONFIG.saturation + combinedWave * 15;
|
|
181
|
+
const light = CONFIG.lightness + combinedWave * 20;
|
|
182
|
+
|
|
183
|
+
const fillColor = `hsla(${hue}, ${sat}%, ${light}%, ${CONFIG.fillAlpha})`;
|
|
184
|
+
const strokeColor = `hsla(${(hue + 30) % 360}, ${sat}%, ${Math.min(light + 20, 80)}%, 0.9)`;
|
|
185
|
+
|
|
186
|
+
drawHex(ctx, finalX, finalY, size * 0.9, rotation, fillColor, strokeColor);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Draw center glow
|
|
190
|
+
const gradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, 150);
|
|
191
|
+
gradient.addColorStop(0, `hsla(${(hueOffset + t * 50) % 360}, 100%, 70%, 0.15)`);
|
|
192
|
+
gradient.addColorStop(1, "transparent");
|
|
193
|
+
ctx.fillStyle = gradient;
|
|
194
|
+
ctx.beginPath();
|
|
195
|
+
ctx.arc(cx, cy, 150, 0, Math.PI * 2);
|
|
196
|
+
ctx.fill();
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
game.on("update", (dt) => {
|
|
200
|
+
time += dt;
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Click to shift palette
|
|
204
|
+
gameInstance.events.on("click", () => {
|
|
205
|
+
hueOffset = (hueOffset + 60) % 360;
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
game.start();
|
|
209
|
+
});
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Study 006 - Psychedelic Drop
|
|
3
|
+
*
|
|
4
|
+
* A grid of particles that reacts to a "drop" in the center, creating
|
|
5
|
+
* psychedelic ripples and interference patterns.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Grid-based particle system
|
|
9
|
+
* - Wave propagation math (sine waves based on distance)
|
|
10
|
+
* - Dynamic color cycling (HSL)
|
|
11
|
+
* - Mouse interaction to move the "drop" source
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { gcanvas } from "/gcanvas.es.min.js";
|
|
15
|
+
|
|
16
|
+
// Configuration
|
|
17
|
+
const CONFIG = {
|
|
18
|
+
// Grid settings
|
|
19
|
+
spacing: 20,
|
|
20
|
+
baseSize: 4,
|
|
21
|
+
|
|
22
|
+
// Wave settings
|
|
23
|
+
waveSpeed: 3.0,
|
|
24
|
+
waveFreq: 0.05,
|
|
25
|
+
amplitude: 15,
|
|
26
|
+
|
|
27
|
+
// Color settings
|
|
28
|
+
colorSpeed: 50,
|
|
29
|
+
baseHue: 0,
|
|
30
|
+
hueRange: 180,
|
|
31
|
+
saturation: 80,
|
|
32
|
+
lightness: 60,
|
|
33
|
+
|
|
34
|
+
// Interaction
|
|
35
|
+
dropDecay: 0.95,
|
|
36
|
+
|
|
37
|
+
// Background
|
|
38
|
+
bgColor: "#050505",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A single particle in the grid
|
|
43
|
+
*/
|
|
44
|
+
class DropParticle {
|
|
45
|
+
constructor(x, y, col, row) {
|
|
46
|
+
this.x = x;
|
|
47
|
+
this.y = y;
|
|
48
|
+
this.baseX = x;
|
|
49
|
+
this.baseY = y;
|
|
50
|
+
this.col = col;
|
|
51
|
+
this.row = row;
|
|
52
|
+
this.size = CONFIG.baseSize;
|
|
53
|
+
this.hue = 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
update(dt, time, dropX, dropY) {
|
|
57
|
+
// Calculate distance from the "drop" point
|
|
58
|
+
const dx = this.baseX - dropX;
|
|
59
|
+
const dy = this.baseY - dropY;
|
|
60
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
61
|
+
|
|
62
|
+
// Calculate wave effect
|
|
63
|
+
// sin(distance * frequency - time * speed)
|
|
64
|
+
const angle = dist * CONFIG.waveFreq - time * CONFIG.waveSpeed;
|
|
65
|
+
const wave = Math.sin(angle);
|
|
66
|
+
|
|
67
|
+
// Secondary interference wave
|
|
68
|
+
const wave2 = Math.cos(angle * 0.5 + time);
|
|
69
|
+
|
|
70
|
+
// Displacement
|
|
71
|
+
const displacement = wave * CONFIG.amplitude;
|
|
72
|
+
const displacement2 = wave2 * (CONFIG.amplitude * 0.5);
|
|
73
|
+
|
|
74
|
+
// this.x = this.baseX + (dx / dist) * displacement; // Radial displacement
|
|
75
|
+
// this.y = this.baseY + (dy / dist) * displacement;
|
|
76
|
+
|
|
77
|
+
// Vertical/Z-like displacement simulated by size and color
|
|
78
|
+
this.size = CONFIG.baseSize + (wave + 1) * 3 + (wave2 + 1);
|
|
79
|
+
|
|
80
|
+
// Color calculation
|
|
81
|
+
// Base hue + wave offset + time cycling
|
|
82
|
+
this.hue = (CONFIG.baseHue + dist * 0.5 + time * CONFIG.colorSpeed) % 360;
|
|
83
|
+
|
|
84
|
+
// Opacity based on wave peaks
|
|
85
|
+
this.alpha = 0.3 + (wave + 1) * 0.35;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
render(ctx) {
|
|
89
|
+
ctx.fillStyle = `hsla(${this.hue}, ${CONFIG.saturation}%, ${CONFIG.lightness}%, ${this.alpha})`;
|
|
90
|
+
|
|
91
|
+
// Draw circle
|
|
92
|
+
ctx.beginPath();
|
|
93
|
+
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
|
94
|
+
ctx.fill();
|
|
95
|
+
|
|
96
|
+
// Optional: Draw a small connection line to neighbors if close enough (too expensive for many particles)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Initialize
|
|
101
|
+
window.addEventListener("load", () => {
|
|
102
|
+
const canvas = document.getElementById("game");
|
|
103
|
+
if (!canvas) return;
|
|
104
|
+
|
|
105
|
+
const game = gcanvas({ canvas, bg: CONFIG.bgColor, fluid: true });
|
|
106
|
+
const gameInstance = game.game;
|
|
107
|
+
|
|
108
|
+
let particles = [];
|
|
109
|
+
let dropX = 0;
|
|
110
|
+
let dropY = 0;
|
|
111
|
+
let time = 0;
|
|
112
|
+
|
|
113
|
+
function createGrid(w, h) {
|
|
114
|
+
particles = [];
|
|
115
|
+
const cols = Math.ceil(w / CONFIG.spacing);
|
|
116
|
+
const rows = Math.ceil(h / CONFIG.spacing);
|
|
117
|
+
|
|
118
|
+
// Center the grid
|
|
119
|
+
const startX = (w - (cols - 1) * CONFIG.spacing) / 2;
|
|
120
|
+
const startY = (h - (rows - 1) * CONFIG.spacing) / 2;
|
|
121
|
+
|
|
122
|
+
for (let i = 0; i < cols; i++) {
|
|
123
|
+
for (let j = 0; j < rows; j++) {
|
|
124
|
+
const x = startX + i * CONFIG.spacing;
|
|
125
|
+
const y = startY + j * CONFIG.spacing;
|
|
126
|
+
particles.push(new DropParticle(x, y, i, j));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Initial setup
|
|
132
|
+
dropX = gameInstance.width / 2;
|
|
133
|
+
dropY = gameInstance.height / 2;
|
|
134
|
+
createGrid(gameInstance.width, gameInstance.height);
|
|
135
|
+
|
|
136
|
+
// Handle resize
|
|
137
|
+
let resizeTimeout = null;
|
|
138
|
+
window.addEventListener("resize", () => {
|
|
139
|
+
clearTimeout(resizeTimeout);
|
|
140
|
+
resizeTimeout = setTimeout(() => {
|
|
141
|
+
dropX = gameInstance.width / 2;
|
|
142
|
+
dropY = gameInstance.height / 2;
|
|
143
|
+
createGrid(gameInstance.width, gameInstance.height);
|
|
144
|
+
}, 100);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Custom render loop
|
|
148
|
+
gameInstance.clear = function() {
|
|
149
|
+
// Standard clear
|
|
150
|
+
this.ctx.fillStyle = CONFIG.bgColor;
|
|
151
|
+
this.ctx.fillRect(0, 0, this.width, this.height);
|
|
152
|
+
|
|
153
|
+
// Render all particles
|
|
154
|
+
for (const p of particles) {
|
|
155
|
+
p.render(this.ctx);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
game.on("update", (dt) => {
|
|
160
|
+
time += dt;
|
|
161
|
+
|
|
162
|
+
// Update all particles
|
|
163
|
+
for (const p of particles) {
|
|
164
|
+
p.update(dt, time, dropX, dropY);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Mouse interaction
|
|
169
|
+
let isDragging = false;
|
|
170
|
+
|
|
171
|
+
gameInstance.events.on("mousedown", () => {
|
|
172
|
+
isDragging = true;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
gameInstance.events.on("mouseup", () => {
|
|
176
|
+
isDragging = false;
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
gameInstance.events.on("mousemove", (e) => {
|
|
180
|
+
if (!isDragging) return;
|
|
181
|
+
dropX = e.x;
|
|
182
|
+
dropY = e.y;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Click to change palette
|
|
186
|
+
gameInstance.events.on("click", () => {
|
|
187
|
+
CONFIG.baseHue = Math.random() * 360;
|
|
188
|
+
CONFIG.waveFreq = 0.02 + Math.random() * 0.08;
|
|
189
|
+
CONFIG.waveSpeed = 1.0 + Math.random() * 4.0;
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
game.start();
|
|
193
|
+
});
|
|
194
|
+
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Study 009 - Interference
|
|
3
|
+
*
|
|
4
|
+
* Fast, colorful wave interference with sharp contrasts.
|
|
5
|
+
* No white wash - pure RGB separation with dark gaps.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Sparse grid for performance + visual clarity
|
|
9
|
+
* - High-speed wave propagation
|
|
10
|
+
* - Sharp color bands, not blurry white
|
|
11
|
+
* - Click to randomize
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { gcanvas } from "/gcanvas.es.min.js";
|
|
15
|
+
|
|
16
|
+
const CONFIG = {
|
|
17
|
+
spacing: 25,
|
|
18
|
+
baseSize: 3,
|
|
19
|
+
|
|
20
|
+
// Wave settings - FAST
|
|
21
|
+
numEmitters: 4,
|
|
22
|
+
emitterSpeed: 1.5,
|
|
23
|
+
waveFreq: 0.04,
|
|
24
|
+
waveSpeed: 8.0,
|
|
25
|
+
|
|
26
|
+
// Chromatic
|
|
27
|
+
rgbSpatialOffset: 6,
|
|
28
|
+
|
|
29
|
+
// Rotation
|
|
30
|
+
globalRotationSpeed: 1.2,
|
|
31
|
+
|
|
32
|
+
bgColor: "#000",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
class Emitter {
|
|
36
|
+
constructor(index, total) {
|
|
37
|
+
this.baseAngle = (index / total) * Math.PI * 2;
|
|
38
|
+
this.orbitRadius = 0.3;
|
|
39
|
+
this.freq = 0.03 + Math.random() * 0.03;
|
|
40
|
+
this.phase = Math.random() * Math.PI * 2;
|
|
41
|
+
this.speed = 6 + Math.random() * 4;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getPosition(time, cx, cy, radius) {
|
|
45
|
+
const angle = this.baseAngle + time * CONFIG.emitterSpeed;
|
|
46
|
+
return {
|
|
47
|
+
x: cx + Math.cos(angle) * this.orbitRadius * radius,
|
|
48
|
+
y: cy + Math.sin(angle) * this.orbitRadius * radius,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
wave(x, y, time, ex, ey) {
|
|
53
|
+
const dist = Math.hypot(x - ex, y - ey);
|
|
54
|
+
return Math.sin(dist * this.freq - time * this.speed + this.phase);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
window.addEventListener("load", () => {
|
|
59
|
+
const canvas = document.getElementById("game");
|
|
60
|
+
if (!canvas) return;
|
|
61
|
+
|
|
62
|
+
const game = gcanvas({ canvas, bg: CONFIG.bgColor, fluid: true });
|
|
63
|
+
const gi = game.game;
|
|
64
|
+
|
|
65
|
+
let grid = [];
|
|
66
|
+
let emitters = [];
|
|
67
|
+
let time = Math.random() * 100;
|
|
68
|
+
|
|
69
|
+
function init() {
|
|
70
|
+
emitters = [];
|
|
71
|
+
for (let i = 0; i < CONFIG.numEmitters; i++) {
|
|
72
|
+
emitters.push(new Emitter(i, CONFIG.numEmitters));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function buildGrid(w, h) {
|
|
77
|
+
grid = [];
|
|
78
|
+
// Use the diagonal as the grid size so rotation never shows black corners
|
|
79
|
+
const diagonal = Math.sqrt(w * w + h * h);
|
|
80
|
+
const cols = Math.ceil(diagonal / CONFIG.spacing) + 2;
|
|
81
|
+
const rows = Math.ceil(diagonal / CONFIG.spacing) + 2;
|
|
82
|
+
|
|
83
|
+
// Center the oversized grid
|
|
84
|
+
const ox = (w - (cols - 1) * CONFIG.spacing) / 2;
|
|
85
|
+
const oy = (h - (rows - 1) * CONFIG.spacing) / 2;
|
|
86
|
+
|
|
87
|
+
for (let i = 0; i < cols; i++) {
|
|
88
|
+
for (let j = 0; j < rows; j++) {
|
|
89
|
+
grid.push({ x: ox + i * CONFIG.spacing, y: oy + j * CONFIG.spacing });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
init();
|
|
95
|
+
buildGrid(gi.width, gi.height);
|
|
96
|
+
|
|
97
|
+
window.addEventListener("resize", () => {
|
|
98
|
+
buildGrid(gi.width, gi.height);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
gi.clear = function () {
|
|
102
|
+
const ctx = this.ctx;
|
|
103
|
+
const w = this.width;
|
|
104
|
+
const h = this.height;
|
|
105
|
+
const cx = w / 2;
|
|
106
|
+
const cy = h / 2;
|
|
107
|
+
const maxR = Math.max(w, h) * 0.5;
|
|
108
|
+
|
|
109
|
+
// Hard clear - no trails, sharp image
|
|
110
|
+
ctx.fillStyle = CONFIG.bgColor;
|
|
111
|
+
ctx.fillRect(0, 0, w, h);
|
|
112
|
+
|
|
113
|
+
const epos = emitters.map(e => e.getPosition(time, cx, cy, maxR));
|
|
114
|
+
|
|
115
|
+
const cosG = Math.cos(time * CONFIG.globalRotationSpeed);
|
|
116
|
+
const sinG = Math.sin(time * CONFIG.globalRotationSpeed);
|
|
117
|
+
|
|
118
|
+
for (const p of grid) {
|
|
119
|
+
// Rotate around center
|
|
120
|
+
const dx = p.x - cx;
|
|
121
|
+
const dy = p.y - cy;
|
|
122
|
+
const rx = cx + dx * cosG - dy * sinG;
|
|
123
|
+
const ry = cy + dx * sinG + dy * cosG;
|
|
124
|
+
|
|
125
|
+
// Get wave from each emitter with phase offsets for RGB
|
|
126
|
+
let wR = 0, wG = 0, wB = 0;
|
|
127
|
+
for (let i = 0; i < emitters.length; i++) {
|
|
128
|
+
const e = emitters[i];
|
|
129
|
+
const ep = epos[i];
|
|
130
|
+
wR += e.wave(rx, ry, time, ep.x, ep.y);
|
|
131
|
+
wG += e.wave(rx, ry, time + 0.3, ep.x, ep.y);
|
|
132
|
+
wB += e.wave(rx, ry, time + 0.6, ep.x, ep.y);
|
|
133
|
+
}
|
|
134
|
+
wR /= emitters.length;
|
|
135
|
+
wG /= emitters.length;
|
|
136
|
+
wB /= emitters.length;
|
|
137
|
+
|
|
138
|
+
// Only draw if wave is positive (creates dark gaps)
|
|
139
|
+
const threshold = 0.1;
|
|
140
|
+
|
|
141
|
+
// Size pulses with wave
|
|
142
|
+
const avgWave = (wR + wG + wB) / 3;
|
|
143
|
+
const size = CONFIG.baseSize + Math.max(0, avgWave) * 5;
|
|
144
|
+
|
|
145
|
+
// Offset direction for RGB split
|
|
146
|
+
const angle = Math.atan2(ry - cy, rx - cx);
|
|
147
|
+
const offX = Math.cos(angle + Math.PI/2) * CONFIG.rgbSpatialOffset;
|
|
148
|
+
const offY = Math.sin(angle + Math.PI/2) * CONFIG.rgbSpatialOffset;
|
|
149
|
+
|
|
150
|
+
// Draw RGB circles only when wave > threshold
|
|
151
|
+
if (wR > threshold) {
|
|
152
|
+
const intensity = Math.floor(wR * 255);
|
|
153
|
+
ctx.fillStyle = `rgb(${intensity}, 0, 0)`;
|
|
154
|
+
ctx.beginPath();
|
|
155
|
+
ctx.arc(rx - offX, ry - offY, size, 0, Math.PI * 2);
|
|
156
|
+
ctx.fill();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (wG > threshold) {
|
|
160
|
+
const intensity = Math.floor(wG * 255);
|
|
161
|
+
ctx.fillStyle = `rgb(0, ${intensity}, 0)`;
|
|
162
|
+
ctx.beginPath();
|
|
163
|
+
ctx.arc(rx, ry, size, 0, Math.PI * 2);
|
|
164
|
+
ctx.fill();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (wB > threshold) {
|
|
168
|
+
const intensity = Math.floor(wB * 255);
|
|
169
|
+
ctx.fillStyle = `rgb(0, 0, ${intensity})`;
|
|
170
|
+
ctx.beginPath();
|
|
171
|
+
ctx.arc(rx + offX, ry + offY, size, 0, Math.PI * 2);
|
|
172
|
+
ctx.fill();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
game.on("update", (dt) => {
|
|
178
|
+
time += dt;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
gi.events.on("click", () => {
|
|
182
|
+
for (const e of emitters) {
|
|
183
|
+
e.freq = 0.02 + Math.random() * 0.05;
|
|
184
|
+
e.speed = 5 + Math.random() * 6;
|
|
185
|
+
e.phase = Math.random() * Math.PI * 2;
|
|
186
|
+
e.orbitRadius = 0.2 + Math.random() * 0.3;
|
|
187
|
+
}
|
|
188
|
+
CONFIG.globalRotationSpeed = 0.8 + Math.random() * 1.0;
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
game.start();
|
|
192
|
+
});
|